﻿---
title: "Python 中集合推导式与生成器表达式的求值时机"
excerpt: "Python 集合推导式创建时立即求值，生成器表达式惰性求值，只绑定最外层迭代器。示例演示列表被修改或重新赋值后，二者结果为何不同。"
tags:
  - Python
date: 2026-08-07 00:08:57
lang: zh-CN
i18n:
  en: /en/python_pitfalls1
  translation: 2
updated: 2026-08-14 00:14:09
---

<script type="module" src="/js/components/sidenote.js"></script>

Python 中，集合推导式（set comprehension）和生成器表达式（generator expression）语法十分相似：

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

但二者的求值时机不同。

## 集合推导式：立即求值

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

```python
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](https://peps.python.org/pep-0289/#the-details) 直接给出了它的等价代码：

```python
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 i

x = __gen(iter(array))
del __gen
```

<side-note>形参绑定遵循 Python 函数调用的一般规则：实参 `iter(array)` 在调用那一刻求值，`bound_exp` 从此绑定到求出的对象；之后名字 `array` 改指向哪里，都影响不到 `bound_exp`。</side-note>

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

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

如果不是就地修改，而是给 `array` 重新赋值：

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

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

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

## 总结

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