尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python 高级编程 027:四大推导式全攻略

Python 高级编程 027:四大推导式全攻略 Python 高级编程 027四大推导式全攻略 写在前面为什么我们需要推导式Bilibili 同步视频 列表推导式最常用的一行代码神器 ✨ 什么是列表推导式 传统写法 vs 推导式写法 语法解析看懂这行魔法 进阶玩法复杂逻辑也能搞定 列表推导式的三大优势⚡ 生成器表达式小括号里的大秘密 小括号的骗局 生成器到底是个啥 生成器怎么用 什么时候用生成器表达式 字典推导式键值互换的魔法 字典推导式是啥 经典示例键值互换 语法解析 进阶玩法带条件的字典推导式 字典推导式的应用场景 集合推导式去重\转换一步到位 集合推导式是啥 示例提取字典的所有key 这跟直接用set\(\)有啥区别 集合推导式的应用场景 性能大比拼推导式真的更快吗⏱️ 测试方法说明⚔️ 第一轮列表推导式 vs for循环\append 为什么推导式更快⚔️ 第二轮内存占用大比拼 性能总结 总结与避坑指南 四大推导式快速回顾 最佳实践什么时候用什么✅ 优先用推导式的场景❌ 不建议用推导式的场景 避坑指南这些坑千万别踩坑1️⃣小括号不是元组推导式坑2️⃣生成器只能遍历一次坑3️⃣字典推导式注意key重复坑4️⃣不要为了用推导式而用推导式 写在最后 写在前面为什么我们需要推导式哈喽各位Pythoner们 今天咱们来聊一个超级实用的话题——推导式Comprehension。相信很多小伙伴在写Python的时候都有过这样的经历为了生成一个列表吭哧吭哧写了好几行for循环结果一看大佬的代码**一行就搞定了**没错这就是推导式的魔力✨ 它不仅能让你的代码变得更加简洁优雅还能在很多场景下提升性能。可以说掌握了推导式你的Python代码就离Pythonic更近了一大步今天这篇文章咱们就来一次性搞定Python中的四大推导式列表推导式List Comprehension—— 最常用必须掌握⚡生成器表达式Generator Expression—— 内存救星字典推导式Dict Comprehension—— 键值转换神器集合推导式Set Comprehension—— 去重转换一步到位准备好了吗让我们一起开启推导式的奇妙之旅吧**小提示**本文所有代码均基于Python 3.x建议大家边看边敲动手实践才是最快的学习方式哦~ Bilibili 同步视频Python 高级编程 027四大推导式全攻略 列表推导式最常用的一行代码神器 ✨首先登场的是我们的头号选手——列表推导式 它也叫列表生成式是Python中最常用、最实用的推导式没有之一 什么是列表推导式简单来说列表推导式就是用一行代码来生成一个列表的语法糖。 它可以让我们把原本需要好几行的for循环append操作浓缩成一行既简洁又高效咱们先来看一个最经典的例子提取1到20之间的所有奇数。 传统写法 vs 推导式写法首先是我们最熟悉的传统for循环写法# 初始化一个空列表odd_list[]# 循环遍历判断并添加foriinrange(21):ifi%21:odd_list.append(i)print(odd_list)# 输出[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]嗯逻辑很清晰对吧先建一个空列表然后循环判断符合条件就append进去。但是...写了这么多行就为了生成一个列表是不是有点**“杀鸡用牛刀”**的感觉别急看看列表推导式是怎么干的# 一行代码搞定odd_list[iforiinrange(21)ifi%21]print(odd_list)# 输出[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]print(type(odd_list))# class list哇塞 看到没**一行代码就搞定了**而且输出结果完全一样这也太爽了吧~ 语法解析看懂这行魔法可能有的小伙伴会说“这写的啥呀看不懂看不懂...” 别急咱们来拆解一下其实超级简单列表推导式的基本语法格式是这样的[表达式for变量in可迭代对象if条件]咱们对照着上面的例子来看i—— 就是表达式代表最终要放进列表里的东西这里就是i本身for i in range(21)—— 就是循环部分跟普通的for循环一模一样if i % 2 1—— 就是条件判断只有满足条件的才会被放进列表是不是一下子就明白了 说白了就是把for循环压缩到了方括号里先循环再判断最后把表达式的结果放进列表。注意注意注意重要的事情说三遍if条件是写在后面的写在前面会报错哦~ ❌ 进阶玩法复杂逻辑也能搞定有的小伙伴可能会问“那如果逻辑比较复杂怎么办总不能都塞在一行里吧” 问得好其实列表推导式的表达式部分可以是任意的Python表达式包括函数调用咱们来看一个例子# 定义一个处理函数defhandle(item):对输入的数字进行平方处理returnitem*item# 需求筛选奇数并对奇数做平方odd_square[handle(i)foriinrange(21)ifi%21]print(odd_square)# 输出[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]看到没我们直接在表达式的位置调用了handle(i)函数这样就可以处理更复杂的逻辑了是不是很灵活 列表推导式的三大优势说了这么多列表推导式到底好在哪呢咱们来总结一下代码简洁 ✨—— 一行顶好几行代码量直接减少看起来更清爽性能更高 ⚡—— 比普通的for循环append要快因为它是Python底层优化过的语义清晰 —— 熟练之后一眼就能看明白你要生成什么样的列表实际上在很多场景下列表推导式完全可以替代Python内置的map、filter函数而且可读性更强但是列表推导式也不能滥用如果里面的逻辑过于复杂嵌套了好多层那代码的可读性就会大打折扣。记住代码的可读性永远是第一位的一般来说如果推导式超过了一行或者嵌套了两层以上的循环那就要考虑是不是该拆成普通的for循环了~⚡ 生成器表达式小括号里的大秘密 好啦列表推导式咱们已经聊得差不多了~ 接下来登场的是一个既熟悉又陌生的家伙——生成器表达式 小括号的骗局首先我想问大家一个问题如果把列表推导式的方括号[]改成小括号()会发生什么很多小伙伴第一反应可能是“那还用说肯定是元组推导式呗” 或者是不是集合推导式嘿嘿都不对 让我们来实际运行一下看看# 把方括号改成小括号odd_gen(iforiinrange(21)ifi%21)print(type(odd_gen))# 猜猜输出是什么# A. tuple B. set C. list D. generator答案揭晓D. generatorprint(type(odd_gen))# class generatorprint(odd_gen)# generator object genexpr at 0x...没错把方括号换成小括号之后它就不再是列表了而是变成了一个生成器Generator这就是我们今天要说的生成器表达式~ 生成器到底是个啥可能有的小伙伴对生成器还不太了解没关系咱们这里先简单说一下后面会有专门的章节详细讲解~ 简单来说生成器是一种特殊的迭代器它的特点是惰性计算Lazy Evaluation。什么意思呢就是说列表推导式一次性把所有元素都生成出来放到内存里 生成器表达式不会立即生成所有元素而是等你要的时候才一个个变出来 这就好比列表推导式就像去快餐店点了一份全家桶所有炸鸡都摆在你面前想吃直接拿 —— 方便但占地方内存生成器表达式就像去了一家现做现卖的小店你要一个人家给你做一个 —— 慢一点但不占地方还新鲜 生成器怎么用生成器是可迭代的所以最常用的方式就是用for循环遍历odd_gen(iforiinrange(21)ifi%21)# 用for循环遍历生成器fornuminodd_gen:print(num,end )# 输出1 3 5 7 9 11 13 15 17 19看到没输出的结果跟列表推导式一模一样但是它在内存里占用的空间却小得多~如果你想把生成器转成列表也很简单直接用list()包一下就行odd_gen(iforiinrange(21)ifi%21)odd_listlist(odd_gen)print(odd_list)# 输出[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]print(type(odd_list))# class list这是因为list()的构造函数接受任何可迭代对象而生成器正好就是可迭代的~ 什么时候用生成器表达式既然生成器这么省内存那是不是以后都用生成器表达式就好了当然不是存在即合理它们各有各的适用场景场景推荐使用原因数据量小需要频繁访问元素列表推导式 ✅列表可以通过索引随机访问生成器只能顺序遍历数据量巨大百万级以上生成器表达式 ✅节省大量内存不会一次性把数据都加载到内存里只需要遍历一次生成器表达式 ✅反正只过一遍用生成器更省内存需要多次遍历、排序、切片等操作列表推导式 ✅生成器只能遍历一次用完就空了重要提醒生成器只能遍历一次遍历完之后就空了再想遍历就得重新生成一个。这一点大家一定要注意不然很容易踩坑️ 字典推导式键值互换的魔法 接下来咱们来看看字典推导式 如果你已经掌握了列表推导式那字典推导式对你来说简直就是小菜一碟~ ️ 字典推导式是啥顾名思义字典推导式就是用一行代码快速生成一个字典的语法。它的思路跟列表推导式几乎一模一样只不过列表推导式用方括号[]生成的是列表字典推导式用大括号{}里面是键值对key:value生成的是字典是不是很好理解 经典示例键值互换咱们来看一个最经典的例子把字典的key和value互换位置。假设我们有这样一个字典my_dict{Bobby:22,Tom:23,Mick:5}现在我们想把它变成年龄作为key名字作为value的形式也就是这样{22:Bobby,23:Tom,5:Mick}如果用传统的方法我们得这么写reversed_dict{}fork,vinmy_dict.items():reversed_dict[v]kprint(reversed_dict)# {22: Bobby, 23: Tom, 5: Mick}嗯也不难对吧但是用字典推导式的话一行就搞定了reversed_dict{v:kfork,vinmy_dict.items()}print(reversed_dict)# {22: Bobby, 23: Tom, 5: Mick}哇塞是不是超级简洁 一眼就能看明白你要干什么 语法解析字典推导式的基本语法是这样的{新key:新valuefork,vin字典.items()if条件}咱们对照着例子来看v: k—— 新的键值对这里就是把原来的value当key原来的key当valuefor k, v in my_dict.items()—— 遍历原字典的键值对if 条件—— 可选的条件判断跟列表推导式一样 进阶玩法带条件的字典推导式跟列表推导式一样字典推导式也可以加条件判断。比如我们只想把年龄大于等于18的键值对互换my_dict{Bobby:22,Tom:23,Mick:5}# 只保留年龄18的adult_dict{v:kfork,vinmy_dict.items()ifv18}print(adult_dict)# {22: Bobby, 23: Tom}是不是很灵活 想怎么玩就怎么玩~ 字典推导式的应用场景字典推导式在实际开发中非常实用比如键值互换—— 就像咱们上面的例子字典过滤—— 筛选出符合条件的键值对值的转换—— 对字典中所有的值做统一处理比如都转成大写、都乘以2等从其他序列生成字典—— 比如从两个列表生成一个字典咱们再来看一个值转换的例子prices{apple:5,banana:3,orange:4}# 所有价格都打8折discount_prices{k:v*0.8fork,vinprices.items()}print(discount_prices)# {apple: 4.0, banana: 2.4, orange: 3.2}看到没一行代码就搞定了所有价格的打折计算是不是超级方便 集合推导式去重转换一步到位 最后咱们来看看集合推导式 这个家伙跟字典推导式长得很像都用大括号{}但它们可不是一回事哦~ 集合推导式是啥集合推导式顾名思义就是**用一行代码快速生成一个集合Set**的语法。它跟字典推导式的区别就在于字典推导式大括号里是key:value键值对有冒号集合推导式大括号里只有单个元素没有冒号就这么简单是不是很好区分 示例提取字典的所有key咱们来看一个例子把字典中所有的key放到一个集合里。用集合推导式怎么写呢my_dict{Bobby:22,Tom:23,Mick:5}# 集合推导式my_set{kfork,vinmy_dict.items()}print(type(my_set))# class setprint(my_set)# {Bobby, Tom, Mick}看到没大括号里没有冒号只有一个k所以生成的就是一个集合 这跟直接用set()有啥区别可能有的小伙伴会说“我直接用set(my_dict.keys())不就行了干嘛还要写推导式” 嗯你说得对在这个简单的场景下两种写法都可以# 方法一集合推导式my_set1{kfork,vinmy_dict.items()}# 方法二直接用set()my_set2set(my_dict.keys())print(my_set1my_set2)# True结果是一样的对吧那为什么还要学集合推导式呢因为...集合推导式更灵活啊咱们来想一个场景如果我想提取字典中所有的key但是要把它们都转成大写并且只保留长度大于3的key怎么办用set()的话你可能得这么写my_setset()forkinmy_dict.keys():iflen(k)3:my_set.add(k.upper())print(my_set)# {BOBBY, MICK}但是用集合推导式的话一行就搞定了my_set{k.upper()forkinmy_dict.keys()iflen(k)3}print(my_set)# {BOBBY, MICK}看到没这就是集合推导式的优势你可以在里面加任意的逻辑——转换、过滤、函数调用想怎么玩就怎么玩~ 而set(my_dict.keys())这种写法虽然简单但灵活性就差多了只能做最简单的转换。 集合推导式的应用场景集合推导式在这些场景下特别好用去重转换—— 从一个列表中提取元素去重的同时做转换筛选去重—— 筛选出符合条件的元素同时自动去重复杂逻辑生成集合—— 需要对元素做多种处理后再放入集合咱们再来看一个去重转换的例子words[Apple,banana,Apple,Orange,banana,grape]# 需求去重并且全部转成小写unique_words{word.lower()forwordinwords}print(unique_words)# {apple, banana, orange, grape}一行代码去重转换全搞定是不是超级方便 性能大比拼推导式真的更快吗⏱️说了这么多推导式的好处有小伙伴可能会问“你说推导式性能高有证据吗” 哈哈必须有咱们今天就来实测一下看看推导式到底比普通的for循环快多少 测试方法说明咱们使用Python内置的timeit模块来做性能测试这个模块专门用来测量小段代码的执行时间非常准确。测试环境Python 3.x普通电脑即可复现。⚔️ 第一轮列表推导式 vs for循环append首先咱们来测试最常用的场景生成一个列表。测试需求生成一个包含1到1000000之间所有偶数的列表。importtimeit# 方法一传统for循环 appenddeftest_for_loop():even_list[]foriinrange(1000001):ifi%20:even_list.append(i)returneven_list# 方法二列表推导式deftest_list_comprehension():even_list[iforiinrange(1000001)ifi%20]returneven_list# 各运行100次取平均时间for_timetimeit.timeit(test_for_loop,number100)comp_timetimeit.timeit(test_list_comprehension,number100)print(f传统for循环耗时{for_time:.4f}秒)print(f列表推导式耗时{comp_time:.4f}秒)print(f推导式比for循环快{(for_time/comp_time):.2f}倍)大家猜猜结果会怎么样测试结果示例传统for循环耗时5.2341 秒列表推导式耗时3.8765 秒推导式比for循环快1.35 倍看到没列表推导式确实比传统的for循环append要快虽然不是数量级的差距但在数据量大的时候这个提升还是很可观的 为什么推导式更快可能有小伙伴会好奇“不都是循环吗为啥推导式就更快” 原因主要有两个底层优化 ⚙️—— 列表推导式是Python在底层专门优化过的语法执行效率更高减少函数调用 —— 传统写法每次都要调用append()方法而推导式是直接在列表构建过程中添加元素省去了函数调用的开销积少成多百万次循环下来差距就显现出来了~⚔️ 第二轮内存占用大比拼除了速度咱们再来比比内存占用。这一轮生成器表达式可要大显身手了importsys# 列表推导式生成的列表even_list[iforiinrange(1000001)ifi%20]# 生成器表达式生成的生成器even_gen(iforiinrange(1000001)ifi%20)print(f列表占用内存{sys.getsizeof(even_list)}字节)print(f生成器占用内存{sys.getsizeof(even_gen)}字节)print(f内存占用比{sys.getsizeof(even_list)/sys.getsizeof(even_gen):.0f}: 1)测试结果示例列表占用内存4194536 字节约4MB生成器占用内存112 字节内存占用比37451 : 1我的天呐这差距也太大了吧 生成器只占用了区区100多字节而列表却占用了好几MB这就是生成器的惰性计算带来的好处——它不会一次性把所有数据都放到内存里而是等你要的时候才生成。在处理海量数据的时候这可是救命的特性啊 性能总结咱们来总结一下这轮性能测试的结果对比项速度内存占用灵活性传统for循环⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐列表推导式⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐生成器表达式⭐⭐⭐遍历时⭐⭐⭐⭐⭐⭐⭐⭐结论1️⃣ 一般情况下列表推导式比传统for循环快而且代码更简洁2️⃣ 处理大数据量时生成器表达式巨省内存是处理海量数据的首选3️⃣ 没有最好的只有最合适的——根据实际场景选择才是王道 总结与避坑指南 好啦到这里Python的四大推导式咱们就全部介绍完了 是不是感觉收获满满最后咱们来做个总结再聊聊避坑指南帮助大家更好地使用这些神器~ ️ 四大推导式快速回顾先来一张全家福对比表帮大家快速回忆名称符号生成类型特点列表推导式[] 方括号list 列表最常用简洁高效支持索引访问生成器表达式() 小括号generator 生成器惰性计算巨省内存只能遍历一次字典推导式{k:v} 大括号冒号dict 字典快速生成/转换字典键值互换神器集合推导式{x} 大括号无冒号set 集合去重转换一步到位自动去重是不是一目了然 记住它们的长相就不会搞混啦~ 最佳实践什么时候用什么光记住语法还不够咱们还得知道什么时候该用什么。这里给大家总结了几条黄金法则✅ 优先用推导式的场景简单的列表/字典/集合生成—— 逻辑不复杂用推导式更简洁⚡对性能有要求的场景—— 推导式比普通for循环更快希望代码更Pythonic—— 推导式是Python的特色语法用好了显得很专业处理大数据量—— 优先考虑生成器表达式节省内存❌ 不建议用推导式的场景逻辑过于复杂—— 如果推导式超过一行或者嵌套了两层以上建议拆成for循环需要大量注释说明—— 如果一段逻辑需要写很多注释才能看懂说明它不适合用推导式团队中有很多新手—— 考虑到代码的可维护性有时候朴素的for循环反而更好核心原则推导式是为了让代码更简洁、更易读如果用了推导式反而让代码更难理解了那还不如不用记住可读性 简洁性 性能大多数情况下 避坑指南这些坑千万别踩最后咱们来聊聊使用推导式时最容易踩的坑大家一定要注意哦~ ⚠️坑1️⃣小括号不是元组推导式很多新手会误以为(x for x in range(10))是元组推导式其实不是它是生成器表达式Python中没有元组推导式这个东西想要元组的话用tuple()把生成器包一下就行# 错误这不是元组推导式是生成器not_a_tuple(xforxinrange(10))print(type(not_a_tuple))# class generator# 正确用tuple()转换生成器yes_a_tupletuple(xforxinrange(10))print(type(yes_a_tuple))# class tuple坑2️⃣生成器只能遍历一次生成器是一次性的遍历完就空了再遍历就什么都没有了gen(xforxinrange(5))# 第一次遍历print(第一次遍历,list(gen))# 第一次遍历 [0, 1, 2, 3, 4]# 第二次遍历print(第二次遍历,list(gen))# 第二次遍历 [] 空了这是很多新手容易踩的坑一定要记住️坑3️⃣字典推导式注意key重复用字典推导式的时候如果新的key有重复后面的会覆盖前面的my_dict{a:1,b:2,c:1}# 键值互换注意value1的有两个reversed_dict{v:kfork,vinmy_dict.items()}print(reversed_dict)# {1: c, 2: b}# 注意1对应的是c而不是a因为后面的覆盖了前面的这个坑在键值互换的时候特别容易踩大家要小心⚠️坑4️⃣不要为了用推导式而用推导式最后一个坑也是最重要的一个不要为了炫技而滥用推导式有的小伙伴学了推导式之后恨不得所有地方都用把代码写得又长又复杂结果别人根本看不懂。这样就本末倒置了。记住代码是写给人看的顺便能在机器上运行。 如果推导式让你的代码更难理解了那就别用 写在最后好啦今天的推导式之旅到这里就结束了回顾一下我们今天学习了✅ 列表推导式 —— 最常用的一行代码神器✅ 生成器表达式 —— 内存救星大数据处理必备✅ 字典推导式 —— 键值互换的魔法✅ 集合推导式 —— 去重转换一步到位✅ 性能对比 —— 用数据说话推导式真的更快✅ 避坑指南 —— 这些坑千万别踩希望这篇文章能帮助大家更好地掌握Python推导式写出更简洁、更高效、更Pythonic的代码如果觉得这篇文章对你有帮助别忘了点赞、收藏⭐、关注三连哦~ 你们的支持就是我更新的最大动力咱们下篇文章再见拜拜~ 相关推荐阅读Python生成器Generator详解 —— 深入理解生成器的底层原理Python迭代器Iterator完全指南 —— 搞懂迭代器和生成器的关系Python map/filter/reduce 函数教程 —— 函数式编程入门
返回列表