Python 中集合推导式与生成器表达式的求值时机

Python 集合推导式创建时立即求值,生成器表达式惰性求值,只绑定最外层迭代器。示例演示列表被修改或重新赋值后,二者结果为何不同。
/

Python 中,集合推导式(set comprehension)和生成器表达式(generator expression)语法十分相似:

{i for i in iterable}(i for i in iterable)

但二者的求值时机不同。

集合推导式:立即求值

集合推导式会在创建时立即遍历可迭代对象,并生成最终的 set 对象。例如:

array: list = [1, 2, 3]# 在执行下一行代码时,Python 会遍历 `array`,并按推导式里的条件过滤。x: set = {i for i in array if array.count(i) > 1}# append 操作不会影响集合推导式的结果array.append(1)print(x)  # 输出 set()

生成器表达式:惰性求值

生成器表达式则是惰性求值,创建时不遍历元素。PEP 289 直接给出了它的等价代码:

array: list = [1, 2, 3]x: tuple = (i for i in array if array.count(i) > 1)# 上面的 x 等价于:def __gen(bound_exp):    for i in bound_exp:        if array.count(i) > 1:            yield ix = __gen(iter(array))del __gen

形参绑定遵循 Python 函数调用的一般规则:实参 iter(array) 在调用那一刻求值,bound_exp 从此绑定到求出的对象;之后名字 array 改指向哪里,都影响不到 bound_exp

我们可以通过修改列表来观察生成器表达式的行为:

array = [1, 2, 3]x = (i for i in array if array.count(i) > 1)# 修改列表内容,生成器表达式迭代时会看到修改后的列表array.append(1)print(list(x))  # 输出 [1, 1]

如果不是就地修改,而是给 array 重新赋值:

array = [1, 2, 3]x = (i for i in array if array.count(i) > 1)array = [3, 3, 6]print(list(x))  # 输出 [3]

那么效果相当于如下代码,两个array其实是不同的对象:

(i for i in [1, 2, 3] if [3, 3, 6].count(i) > 1)

总结

类型求值时机创建时保存后续修改影响
集合推导式 {}创建时立即执行最终 set 结果不影响
生成器表达式 ()迭代时执行最外层 iterable 的迭代器 + 计算逻辑可能影响