小能豆

嵌套生成器表达式-意外结果

py

以下是测试代码:

units = [1, 2]
tens = [10, 20]
nums = (a + b for a in units for b in tens)
units = [3, 4]
tens = [30, 40]
[x for x in nums]

假设第 3 行 ( nums = ...) 上的生成器表达式形成一个迭代器,我期望最终结果能够反映units和的最终分配值tens。另一方面,如果要在第 3 行评估该生成器表达式,并生成结果元组,那么我希望使用units和的第一个定义。tens

我看到的是 MIX;也就是说,结果是[31, 41, 32, 42]!?

有人能解释这种行为吗?


阅读 22

收藏
2024-12-08

共1个答案

小能豆

这种行为与 Python 生成器表达式的惰性求值和作用域绑定方式有关。以下是对代码行为的逐步解释:

代码分析

units = [1, 2]
tens = [10, 20]
nums = (a + b for a in units for b in tens)  # 创建生成器表达式
units = [3, 4]  # 修改 units
tens = [30, 40]  # 修改 tens
[x for x in nums]  # 消耗生成器

关键点解释

  1. 生成器表达式的作用域绑定:
  2. 生成器表达式中的变量 (ab) 是动态绑定的,它们会在生成器表达式实际被迭代时取当前作用域中的值。
  3. 也就是说,当你写 nums = (a + b for a in units for b in tens) 时,并不会立即计算 a + b。相反,unitstens 的引用被捕获,而不是它们的当前值。

  4. 惰性求值:

  5. 生成器表达式在定义时不会计算,而是等到实际被迭代时才计算每个元素。
  6. 当你调用 [x for x in nums] 时,才会开始逐一迭代 nums 中的每个值。

  7. 修改后的 unitstens 生效:

  8. 当你修改了 unitstens 后,它们的新值会被生成器表达式捕获,因为 nums 在被消耗时会访问当前作用域中的这些变量。
  9. 因此,生成器表达式中的 for a in unitsfor b in tens 实际上会使用 units = [3, 4]tens = [30, 40]

  10. 混合结果:

  11. 迭代 nums 时,它的计算过程是:
    • 外层循环:for a in units 现在使用 [3, 4]
    • 内层循环:for b in tens 现在使用 [30, 40]
    • 最终计算 a + b 的结果。

因此,结果是:
- 3 + 30 = 31
- 3 + 40 = 41
- 4 + 30 = 32
- 4 + 40 = 42

最终输出 [31, 41, 32, 42]

解决方法

如果你希望生成器表达式绑定 unitstens 的初始值,而不受后续修改影响,可以在定义生成器表达式时立即复制 unitstens

units = [1, 2]
tens = [10, 20]
nums = (a + b for a in list(units) for b in list(tens))  # 创建生成器时复制值
units = [3, 4]
tens = [30, 40]
print([x for x in nums])  # 输出 [11, 21, 12, 22]

或者,将生成器表达式立即转换为列表,以强制计算所有值:

units = [1, 2]
tens = [10, 20]
nums = [(a + b) for a in units for b in tens]  # 列表推导强制计算
units = [3, 4]
tens = [30, 40]
print(nums)  # 输出 [11, 21, 12, 22]

总结

生成器表达式的惰性求值和作用域绑定机制会导致它在被迭代时使用变量的当前值。这种行为有时可能导致混淆,但理解其原理后可以通过复制变量值或强制计算来避免问题。

2024-12-08