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


最新评论