列表推导式说三件事:产出什么,哪个循环提供元素,以及可选的哪些元素能留下来。短写法很有用,但它的可读性有一条硬上限。下面的例子既讲语法,也讲该在哪儿停手。
🎙️ 发布并录制于: ·
从中间的 for n in nums 开始读,再把左边的表达式当成输出。做筛选的 if 放在最右边,它可以把元素丢掉。if-else 要放在 for 前面,因为它是在挑输出的值,元素一个都不会少。
nums = [1, 2, 3, 4, 5]
[n * n for n in nums] # transform → [1,4,9,16,25]
[n for n in nums if n % 2 == 0] # filter → [2,4]
[n*n for n in nums if n > 2] # both → [9,16,25]
["even" if n%2==0 else "odd" for n in nums] # choose
位置决定含义。右边的 if 是留下还是丢掉。左边的 if-else 是取这个值还是取那个值。
好的推导式只做一次能一眼认出来的转换,最多再加一个简单的筛选。清洗字符串顺手丢掉空串、从活跃用户里取 email 字段,都过得了这个标准。按后缀筛文件名也读得顺,因为条件就挨着它控制的那个循环。
# clean strings and discard blanks
[s.strip().lower() for s in raw if s.strip()]
# pull a field from active API users
[u["email"] for u in users if u["active"]]
# parse numeric fields
[float(x) for x in fields]
# filenames matching a suffix
[f for f in os.listdir(".") if f.endswith(".csv")]
真要处理 CSV 数据,别用一行字符串按逗号切分去造 fields;带引号的逗号会让这个捷径失效。先把这一行交给 Python 的 CSV 模块,再用推导式转换已经解析好的字段。
花括号里写键和值,得到字典。花括号里只写一个表达式,得到集合,重复值自动去掉。要展平一层嵌套,就按等价嵌套循环的顺序,从左到右写这些 for:先是每一行,再是这一行里的每个元素。
{w: len(w) for w in words} # dict: word → length
{u["id"]: u for u in users} # index records by id
{n % 3 for n in nums} # set: unique values
{v: k for k, v in d.items()} # invert a dict
matrix = [[1, 2], [3, 4], [5]]
[x for row in matrix for x in row] # [1,2,3,4,5]
展平一层是个合理的天花板。再加一层嵌套或者一个条件,读代码的人就得在脑子里跑一遍。那种时候把循环老老实实写出来。
改写失败最常见的一种,是把 else 放到了筛选条件后面。Python 会报 SyntaxError: invalid syntax,因为右边的筛选没有 else 这个分支。把整个 if-else 表达式挪到 for 的左边。如果你只想要符合条件的元素,就删掉 else,保留右边那个 if。
[n for n in nums if n % 2 == 0 else 0]
# SyntaxError: invalid syntax
[ n if n % 2 == 0 else 0 for n in nums ] # choose
[ n for n in nums if n % 2 == 0 ] # filter
sum(n*n for n in nums) # generator; no intermediate list
[print(x) for x in items] 会白白分配一个装满 None 的列表;有副作用就用普通循环。另外,一旦出现两个条件、要处理异常,或者得写条注释才能解释那个表达式,也该换回循环。当 sum、max、any、all 能直接消费这些值时,把方括号去掉,用生成器表达式。这样就不用先存一份临时列表。这么做是为了省内存,不是为了把复杂逻辑硬塞进一行。