推导式与生成器表达式
- 要造一个列表 → 用列表推导(listcomp)。它的目的写在脸上:造列表。
- 要把数据喂给别的构造器(
tuple()、array()、sum())→ 用生成器表达式(genexp)。它不造中间列表。 - 不是为了用那个列表 → 两个都别用。写
for循环。
为什么推导式更易读
Section titled “为什么推导式更易读”对比两段做同一件事的代码:
# for 循环版codes = []for symbol in symbols: codes.append(ord(symbol))
# 列表推导版codes = [ord(symbol) for symbol in symbols]for 循环能干的事太多了:累加、筛选、调用副作用、拼字符串。看到 for 你得读完循环体
才知道它在干嘛。看到 [] 加 for,你立刻知道:它在造一个新列表。
这就是「可读性」的来源,不是短,是意图显式。
过滤:推导式 vs filter+map
Section titled “过滤:推导式 vs filter+map”beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))两段等价,但第二段你得在脑子里过一遍:map 把 ord 作用到每个元素,filter 保留
大于 127 的,最后 list() 一下。第一段就是「对每个 s,如果 ord(s) > 127,就收下 ord(s)」。
顺带一个我以前搞错的印象:map/filter 并不比推导式快。书里也澄清了这一点。
笛卡尔积:for 子句的顺序就是嵌套顺序
Section titled “笛卡尔积:for 子句的顺序就是嵌套顺序”>>> colors = ['black', 'white']>>> sizes = ['S', 'M', 'L']>>> [(c, s) for c in colors for s in sizes][('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]结果顺序等于把两个 for 按书写顺序嵌套。想让结果先按尺码分组,就把两个 for 对调:
>>> [(c, s) for s in sizes for c in colors][('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), ('black', 'L'), ('white', 'L')]第 1 章的 FrenchDeck 初始化就是这么写的:
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]先按花色分组,组内按点数 —— 因为 for suit 写在外面。
生成器表达式:省掉中间列表
Section titled “生成器表达式:省掉中间列表”语法一样,把方括号换成圆括号:
>>> tuple(ord(s) for s in symbols)(36, 162, 163, 165, 8364, 164)>>> array.array('I', (ord(s) for s in symbols))array('I', [36, 162, 163, 165, 8364, 164])两个细节:
- 生成器表达式是函数调用的唯一参数时,圆括号可以省(
tuple(...)那行)。 - 有多个参数时不能省(
array(...)那行,'I'是第一个参数,生成器是第二个)。
省的不是时间,是峰值内存。用笛卡尔积举例更容易看出差别:
for tshirt in (f'{c} {s}' for c in colors for s in sizes): print(tshirt)如果两个列表各有 1000 项,用推导式要先造一个 100 万项的列表才能开始循环; 用生成器表达式一项一项给你,那个列表从不出现。
两个作用域细节
Section titled “两个作用域细节”一、推导式有自己的局部作用域。
>>> x = 'ABC'>>> codes = [ord(x) for x in x]>>> x'ABC'for 子句里的 x 是推导式内部的变量,跑完就没了,没污染外面的 x。这是 Python 3 的
行为,Python 2 里会泄漏出来。
二、但海象运算符 := 绑定的变量会留下来。
>>> codes = [last := ord(c) for c in x]>>> last67>>> cNameError: name 'c' is not definedlast 还在,c 没了。区别在于 := 的目标变量的作用域属于外层函数,不是推导式。
tests/test_ch02.py 里对应 test_comprehension_scoping,断言了三件事:
x 还是 'ABC'、last == 67、c 不在作用域里。