Python 列表推导式的语法、过滤、嵌套,字典推导式和集合推导式的写法,以及什么时候该改用生成器表达式。
推导式用一个表达式就能从循环里构建出列表。它不只是更短,也比在循环里调用 append 更快,因为追加元素这一步是在 C 里完成的。
各部分的顺序永远不变:先是表达式,然后是 for,最后是可选的 if。
循环和推导式
下面两种写法得到的列表完全一样。
# the loop and the comprehension do the same thing
squares = []
for n in range(6):
squares.append(n * n)
print(squares)
print([n * n for n in range(6)])
输出:
[0, 1, 4, 9, 16, 25]
[0, 1, 4, 9, 16, 25]
过滤
末尾加一个 if,只留下你想要的元素。
# filtering with if
print([n for n in range(20) if n % 3 == 0])
输出:
[0, 3, 6, 9, 12, 15, 18]
选择值
if/else 是表达式的一部分,所以要放在前面。很多人在这里栽跟头:两种写法长得像,位置却不同。
# if/else goes before the for
print(['even' if n % 2 == 0 else 'odd' for n in range(5)])
输出:
['even', 'odd', 'even', 'odd', 'even']
记住一条:末尾的 if 负责过滤,前面的 if/else 负责选值。
字典推导式和集合推导式
用花括号时,写键值对得到字典,写单个值得到集合。
# dict and set comprehensions
words = ['apple', 'bread', 'apple', 'milk']
print({w: len(w) for w in words})
print({len(w) for w in words})
输出:
{'apple': 5, 'bread': 5, 'milk': 4}
{4, 5}
集合版本去掉了重复的长度,集合本来就是这样。
两个 for 子句
多个 for 子句从左往右读,顺序和写嵌套循环时一样。展平嵌套列表就是这么写的。
# two for clauses read top to bottom
pairs = [(x, y) for x in 'ab' for y in (1, 2)]
print(pairs)
grid = [[1, 2], [3, 4], [5, 6]]
print([cell for row in grid for cell in row])
输出:
[('a', 1), ('a', 2), ('b', 1), ('b', 2)]
[1, 2, 3, 4, 5, 6]
生成器表达式
换成圆括号,得到的就是生成器。它按需产生值,不会把整个列表放在内存里。
# a generator expression does not build the list
import sys
listcomp = [n * n for n in range(100000)]
genexp = (n * n for n in range(100000))
print('list bytes:', sys.getsizeof(listcomp))
print('generator bytes:', sys.getsizeof(genexp))
print('sum is the same:', sum(listcomp) == sum(genexp))
输出:
list bytes: 800984
generator bytes: 200
sum is the same: True
结果一样,内存却少了四千倍。如果结果直接交给只消费一次的东西,比如 sum、any 或者循环,就用生成器。
循环变量不会跑出来
在 Python 3 里,推导式有自己的作用域,不会覆盖你之前已有的变量。
# the loop variable does not leak
n = 'untouched'
result = [n for n in range(3)]
print(result, n)
输出:
[0, 1, 2] untouched
要点
- 顺序是:表达式、
for、然后if。 - 末尾的
if负责过滤。if/else写在for前面。 - 花括号构建字典和集合,圆括号构建生成器。
- 循环变量不会泄漏到外层作用域。
如果推导式需要两个以上的子句,或者一行写不下,就老老实实写循环。一旦得靠解读才能看懂,推导式就不划算了。