推导式与生成器
本节目标
学完这一节,你会知道:
- 推导式是什么,和普通循环有什么关系
- 如何写列表推导式、字典推导式和集合推导式
- 什么时候应该用普通循环而不是推导式
- 生成器表达式和列表推导式有什么区别
- 如何用推导式做简单的数据转换和过滤
推导式的目标不是“少写几行”,而是在简单场景下让数据处理更清楚。
先跑一个例子
新建文件 comprehension_demo.py,写入:
scores = [55, 80, 92, 67, 40, 88]
passed_scores = [score for score in scores if score >= 60]
score_levels = ["及格" if score >= 60 else "不及格" for score in scores]
print(passed_scores)
print(score_levels)
运行:
python3 comprehension_demo.py
你会看到:
[80, 92, 67, 88]
['不及格', '及格', '及格', '及格', '不及格', '及格']
推导式把“遍历、判断、生成新列表”写到了一行。
从普通循环到列表推导式
普通写法:
numbers = [1, 2, 3, 4, 5]
squares = []
for number in numbers:
squares.append(number ** 2)
print(squares)
列表推导式:
numbers = [1, 2, 3, 4, 5]
squares = [number ** 2 for number in numbers]
print(squares)
结构是:
[新值 for 变量 in 数据]
带条件的列表推导式
筛选偶数:
numbers = range(1, 11)
evens = [number for number in numbers if number % 2 == 0]
print(evens)
输出:
[2, 4, 6, 8, 10]
结构是:
[新值 for 变量 in 数据 if 条件]
if/else 写在前面
如果要为每个元素生成不同结果,if/else 写在前面。
numbers = [1, 2, 3, 4, 5]
labels = ["偶数" if n % 2 == 0 else "奇数" for n in numbers]
print(labels)
输出:
['奇数', '偶数', '奇数', '偶数', '奇数']
记住:
- 只筛选:
[x for x in data if 条件] - 二选一生成值:
[A if 条件 else B for x in data]
字典推导式
把名字列表转成“名字 -> 长度”的字典:
names = ["Alice", "Bob", "Charlie"]
name_lengths = {name: len(name) for name in names}
print(name_lengths)
输出:
{'Alice': 5, 'Bob': 3, 'Charlie': 7}
筛选字典:
scores = {"小明": 85, "小红": 92, "小刚": 58}
passed = {name: score for name, score in scores.items() if score >= 60}
print(passed)
集合推导式
集合推导式会自动去重。
words = ["hello", "world", "python", "hello"]
lengths = {len(word) for word in words}
print(lengths)
输出:
{5, 6}
找出文本中出现过的字母:
text = "hello world"
letters = {char for char in text if char.isalpha()}
print(letters)
生成器表达式
列表推导式会立刻创建完整列表。
生成器表达式不会一次性生成所有数据,而是用到一个生成一个。
numbers = (i ** 2 for i in range(1, 6))
print(numbers)
print(next(numbers))
print(next(numbers))
输出类似:
<generator object ...>
1
4
如果想一次看完:
print(list(numbers))
注意:生成器被遍历一次后,就会被消耗。
生成器适合什么场景?
适合数据量很大、只需要遍历一次的场景。
total = sum(i ** 2 for i in range(1, 1000001))
print(total)
这里传给 sum() 的是生成器表达式,不需要先创建一个巨大列表。
常见搭配:
numbers = [1, 3, 5, 8, 9]
has_even = any(n % 2 == 0 for n in numbers)
all_positive = all(n > 0 for n in numbers)
print(has_even)
print(all_positive)
了解即可:yield
如果生成逻辑比较复杂,可以写生成器函数。
def countdown(n):
while n > 0:
yield n
n -= 1
for number in countdown(5):
print(number)
yield 会一次返回一个值,并记住函数执行到哪里。以后处理大文件时很有用。
逐行拆解
再看开头的例子:
passed_scores = [score for score in scores if score >= 60]
遍历 scores,只保留大于等于 60 的分数。
score_levels = ["及格" if score >= 60 else "不及格" for score in scores]
每个分数都会生成一个结果:及格或不及格。
自己改一改
把 comprehension_demo.py 改成:
names = ["alice", "bob", "charlie"]
upper_names = [name.upper() for name in names]
name_lengths = {name: len(name) for name in names}
print(upper_names)
print(name_lengths)
然后继续改:
- 只保留长度大于 3 的名字
- 生成一个集合,保存所有名字长度
- 用生成器表达式计算 1 到 100 的平方和
常见错误
1. 推导式写得太复杂
如果一行代码很难读,就改回普通循环。清楚比短更重要。
2. if 位置写错
筛选:
[n for n in numbers if n > 0]
二选一:
["正数" if n > 0 else "非正数" for n in numbers]
这两种写法的 if 位置不同。
3. 生成器只能遍历一次
gen = (i for i in range(3))
print(list(gen))
print(list(gen))
第二次会得到空列表,因为生成器已经被消耗了。
4. 误以为集合推导式有顺序
集合没有固定顺序。如果需要保持顺序,用列表。
小练习
练习 1:平方列表
生成 1 到 10 的平方列表。
练习 2:筛选长单词
给定单词列表,只保留长度大于 4 的单词。
练习 3:成绩字典
给定成绩字典,只保留及格学生。
参考答案
练习 1:
squares = [i ** 2 for i in range(1, 11)]
print(squares)
练习 2:
words = ["hi", "python", "code", "learning"]
long_words = [word for word in words if len(word) > 4]
print(long_words)
练习 3:
scores = {"小明": 85, "小红": 92, "小刚": 58}
passed = {name: score for name, score in scores.items() if score >= 60}
print(passed)
小结
这一节你学会了:
- 列表推导式可以简化简单的循环生成列表
- 字典推导式可以生成键值对
- 集合推导式可以生成不重复的数据
- 生成器表达式适合大数据量、只遍历一次的场景
- 推导式不要写得太复杂,复杂逻辑优先用普通循环
下一章我们会学习文件与 IO。你会把程序的数据保存到文件里,也能从文件读取内容继续处理。
推导式很帅,但别帅过头
推导式能把简单的数据转换写得很利索,但如果一行看完需要深呼吸,就该换回普通循环了。马哥建议你先练筛选偶数、转换大小写、过滤成绩,写得清楚比写得短更重要。
还没有评论,来抢沙发吧!