一行写完一个循环的爽感,和"一行写完导致没人看懂"的惨剧,中间只隔一条可读性。这篇教你把推导式用在对的地方。
你将学到
- 列表、字典、集合推导式的写法,以及条件过滤与嵌套
- 生成器表达式省内存的原理,什么时候该用它
lambda、map/filter/reduce,以及它们和推导式的取舍sorted(key=...)、any/all/zip/enumerate的惯用法functools.partial、itertools的常用函数,并会做命令式 ↔ 推导式改写
前置知识
上一篇《异常处理》讲的是"出错怎么办"。这一篇讲怎么把一批数据变得又短又快地处理掉。假定你熟悉 for 循环和基本容器。
列表推导式
从一个可迭代对象里,按规则生成新列表。
# 命令式写法
squares = []
for x in range(5):
squares.append(x * x)
print(squares) # 输出: [0, 1, 4, 9, 16]
# 推导式写法:一句话
squares = [x * x for x in range(5)]
print(squares) # 输出: [0, 1, 4, 9, 16]
结构是 [表达式 for 变量 in 可迭代对象]。for 里的变量只在推导式内部有效,不会泄漏到外面。
条件过滤与嵌套
在末尾加 if 就是筛选,满足才保留。
evens = [x for x in range(10) if x % 2 == 0]
print(evens) # 输出: [0, 2, 4, 6, 8]
if 放在前面则变成三元表达式(每个元素都要有值):
labels = ["偶" if x % 2 == 0 else "奇" for x in range(4)]
print(labels) # 输出: ['偶', '奇', '偶', '奇']
多重 for 可以展平嵌套结构,顺序和写普通循环一致:
matrix = [[1, 2], [3, 4]]
flat = [n for row in matrix for n in row]
print(flat) # 输出: [1, 2, 3, 4]
记忆窍门:for 从左到右就是外层到内层,if 只作用于紧挨它左边的那层。
字典与集合推导式
把方括号换成花括号,写法对称。
# 字典推导式:{键: 值 for ...}
sq = {x: x * x for x in range(4)}
print(sq) # 输出: {0: 0, 1: 1, 2: 4, 3: 9}
# 常用场景:反转一个字典的键值
prices = {"apple": 3, "banana": 2}
rev = {v: k for k, v in prices.items()}
print(rev) # 输出: {3: 'apple', 2: 'banana'}
# 集合推导式:自动去重
lens = {len(w) for w in ["a", "bb", "cc", "ddd"]}
print(lens) # 输出: {1, 2, 3}
生成器表达式与内存
把方括号换成圆括号,得到的是生成器表达式:它不会立刻算出所有结果,而是你要一个才算一个。
gen = (x * x for x in range(1_000_000)) # 瞬间返回,不占大内存
print(next(gen)) # 输出: 0
print(next(gen)) # 输出: 1
# 作为函数唯一参数时,可省掉外层圆括号
total = sum(x * x for x in range(1_000_000))
print(total) # 输出: 333332833333500000
对比:[x * x for x in range(1_000_000)] 会立刻造出含一百万个元素的列表,占几十 MB;生成器只占常数级内存。所以:
- 要反复遍历、要下标、要长度 → 用列表。
- 只遍历一次、数据量大、可以流式处理 → 用生成器表达式。
什么时候别用推导式
推导式是工具,不是信仰。以下情况请老老实实写循环:
# ❌ 三层嵌套加多个条件,别人得用纸笔才能读懂
result = [x for x in data if x > 0 for y in range(x) if y < 5 if x * y % 3 == 0]
# ✅ 拆开,清晰得多
result = []
for x in data:
if x <= 0:
continue
for y in range(x):
if y < 5 and (x * y) % 3 == 0:
result.append(x)
还有两种明确不该用推导式的情况:
- 带副作用:循环里要
print、写文件、改外部变量,就老老实实写for。 - 逻辑跨多行难放:表达式塞不进一行,或者需要
try/except,用循环更合适。
一句话原则:推导式适合"把一堆数据映射/筛选成另一堆数据",不适合"做一串动作"。
map / filter / reduce 与 lambda
lambda 是匿名小函数,只能写一个表达式。
double = lambda x: x * 2
print(double(3)) # 输出: 6
map / filter 是老牌函数式工具,但在 Python 里通常推导式更清晰。
nums = [1, 2, 3, 4]
# map:对每个元素做变换
print(list(map(lambda x: x * 2, nums))) # 输出: [2, 4, 6, 8]
print([x * 2 for x in nums]) # ✅ 更 Pythonic,等价
# filter:筛选
print(list(filter(lambda x: x % 2 == 0, nums))) # 输出: [2, 4]
print([x for x in nums if x % 2 == 0]) # ✅ 等价
# reduce:把序列折叠成一个值(需要导入)
from functools import reduce
print(reduce(lambda a, b: a + b, nums)) # 输出: 10
那 map/filter 什么时候用?当已有现成函数、又不值得写推导式时:
names = [" alice ", "bob "]
print(list(map(str.strip, names))) # 输出: ['alice', 'bob']
reduce 不建议滥用,很多场景 sum/math.prod/max 更直白:
import math
print(sum([1, 2, 3, 4])) # 输出: 10
print(math.prod([1, 2, 3, 4])) # 输出: 24
sorted(key=...)、any / all / zip / enumerate
这几个是日常最常打交道的"内置函数式工具"。
words = ["banana", "kiwi", "apple"]
print(sorted(words, key=len)) # 输出: ['kiwi', 'apple', 'banana']
people = [("小明", 30), ("小红", 25)]
print(sorted(people, key=lambda p: p[1])) # 输出: [('小红', 25), ('小明', 30)]
# any:只要有一个为真就 True;all:全都为真才 True
print(any(x > 3 for x in [1, 2, 5])) # 输出: True
print(all(x > 0 for x in [1, 2, 5])) # 输出: True
print(all(x > 0 for x in [1, -2, 5])) # 输出: False
# enumerate:同时拿到下标和值(下标从 0 开始,可指定 start)
for i, w in enumerate(["a", "b"], start=1):
print(i, w)
# 输出: 1 a
# 输出: 2 b
# zip:并行遍历多个序列,按最短的停
names, ages = ["小明", "小红"], [30, 25]
print(list(zip(names, ages))) # 输出: [('小明', 30), ('小红', 25)]
print(dict(zip(names, ages))) # 输出: {'小明': 30, '小红': 25}
functools.partial 与 itertools
functools.partial 用来"冻结"函数的部分参数,造出一个新函数。
from functools import partial
def power(base, exp):
return base ** exp
square = partial(power, exp=2) # 固定指数为 2
print(square(5), square(9)) # 输出: 25 81
itertools 是迭代器工厂,处理组合、重复、切片特别顺手。
import itertools
# 无限计数器 + islice 截取
print(list(itertools.islice(itertools.count(10), 3))) # 输出: [10, 11, 12]
# chain:把多个可迭代对象首尾拼接
print(list(itertools.chain([1, 2], [3, 4]))) # 输出: [1, 2, 3, 4]
# product:笛卡尔积
print(list(itertools.product([1, 2], "ab")))
# 输出: [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
命令式 vs 推导式改写
同一批逻辑,两种写法对照。注意什么时候不改。
# 1) 求平方
# 命令式
out = []
for x in range(5):
out.append(x * x)
# 推导式
out = [x * x for x in range(5)] # ✅ 改写
# 2) 挑出偶数
# 命令式
out = []
for x in range(10):
if x % 2 == 0:
out.append(x)
# 推导式
out = [x for x in range(10) if x % 2 == 0] # ✅ 改写
# 3) 单词转大写(同样是"映射",适合推导式)
out = [w.upper() for w in ["a", "b"]] # ✅ 改写
# 4) 带副作用:读写文件
# ❌ 别硬改成推导式
for line in lines:
with open("out.txt", "a", encoding="utf-8") as f:
f.write(line)
# 这种有 I/O 副作用的,保持循环
改写的判断很简单:本质是"生成一份新数据"就改写,"执行一串操作"就保留循环。
常见坑
❌ 把大列表推导式当流式处理,白白吃内存。
total = sum([x * x for x in range(10_000_000)]) # ❌ 先造出一个巨大列表
✅ 用生成器表达式。
total = sum(x * x for x in range(10_000_000)) # ✅ 常量级内存
❌ 推导式里塞副作用,结果没人看得懂。
[print(x) for x in range(3)] # ❌ 就为了打印,用普通 for
✅ 用循环。
for x in range(3):
print(x)
❌ lambda 里写多语句,语法都不允许;✅ 需要多行就用 def。
f = lambda x: y = x + 1 # ❌ SyntaxError:lambda 只能是一个表达式
def f(x): # ✅
y = x + 1
return y
小结
- 列表/字典/集合推导式是"生成新容器"的利器,写法
[表达式 for 变量 in 可迭代对象 if 条件]。 - 多重
for顺序与普通循环一致,if只作用于左边那层。 - 生成器表达式用圆括号,惰性求值、省内存,适合大数据只遍历一次的场景。
- 逻辑复杂、带副作用、难塞进一行时,果断用循环,可读性优先。
map/filter多数场景可由推导式替代;reduce常常有更直白的sum/max。sorted(key=...)、any/all、zip、enumerate是高频惯用法,值得熟练。functools.partial冻结参数,itertools提供组合/切片/拼接等迭代器工具。
延伸阅读
- 大师篇《迭代器、生成器与协程》会把"惰性求值"讲到底层,读完你就明白生成器表达式为什么省内存。
- 入门篇《列表元组字典集合》是这些容器的地基,觉得自己对切片、
dict方法还不熟的话,可以回头补一补。
上一篇:异常处理 · 下一篇:变量与数据类型
文章回复
0 条公开回复