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
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 的迭代器 + 计算逻辑 | 可能影响 |