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

如题,Python 中集合推导式和生成器表达式的求值时机不同,容易踩坑。

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

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

但二者在求值时机上有所不同。

集合推导式:立即求值

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

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

生成器表达式:惰性求值

和集合推导式不同,生成器表达式采用惰性求值。例如:

array = [1, 2, 3]# 在执行下一行代码时,Python 只是创建了一个 `generator` 对象,并保存了生成器表达式的执行逻辑。x = (i for i in array if array.count(i) > 1)array.append(1)# 在执行下一行代码时,Python 会开始遍历 `array`,并根据生成器表达式的逻辑进行过滤。print(list(x))  # 输出 [1, 1]

可能踩坑的点

在创建生成器表达式时,Python 会保存对 array 的引用,所以后面如果修改了array变量的指向,那么生成器里的两个 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是指向[1, 2, 3],后一个array是指向[3, 3, 6],所以在求值时,实际执行的是:

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

总结

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