【问题标题】:Different generators added to the same dictionary return the same values添加到同一个字典的不同生成器返回相同的值
【发布时间】:2023-03-26 23:40:01
【问题描述】:

我刚刚遇到了一个奇怪的 Python (3.7.0) 行为,我不太了解,对我来说似乎是一个错误。我想用生成器创建一个字典,但不知何故,它们都返回相同的值。这是我正在谈论的代码示例:

import itertools

d = {
"a": [-1, 2],
"b": [1, 2],
"c": [20, 20]
}
g = dict()
g2 = dict()
for letter, values in d.items():
    g[letter] = (values[0] * values[1] * x for x in itertools.count())
    g2[letter] = [values[0] * values[1] * x for x in range(3)]

for i in range(3):
    for l, v in g.items():
        print(v.__next__())
print(g2)

在我看来,g2 元素和 g 生成器的预期输出是相同的,但是我总是从最新的生成器接收值:

0
0
0
400
400
400
800
800
800
{'a': [0, -2, -4], 'b': [0, 2, 4], 'c': [0, 400, 800]}

总结一下,我做错了什么吗?还是只是标准的 Python 行为?

【问题讨论】:

  • 您期望输出什么?同样使用内置的next,不要显式调用o.__next__

标签: python python-3.x dictionary itertools


【解决方案1】:

此错误不是由生成器引起的。这是一个范围错误。

在您的生成器声明中,您使用名称values,尽管直到之后循环完成后生成器才会执行,其中values 现在是列表中的最后一项。这是一个重现您的错误的示例。

for i in [1]:
    g = (i for _ in range(3))

i = 'some new value'

print(next(g)) # 'some new value'

换句话说,values[0]values[1] 没有绑定到生成器,如果名称 values 后面的值发生变化,那么生成器的输出也会发生变化。

这意味着您希望生成器周围有一个闭包来存储值values[0]values[1]。您可以通过将生成器定义为函数来做到这一点。

import itertools

# Here is a function taht will return a generator
def gen(a, b):
    for x in itertools.count():
        yield a * b * x

d = {"a": [-1, 2], "b": [1, 2], "c": [20, 20]}

g, g2 = dict(), dict()

for letter, values in d.items():
    g[letter] = gen(values[0], values[1])
    g2[letter] = [values[0] * values[1] * x for x in range(3)]

for i in range(3):
    for l, v in g.items():
        print(next(v))

print(g2)

事实上,出于这个原因,很少使用内联生成器。支持def-yield 创建生成器的方式。

另外,不要调用__next__,而是使用内置的next

输出

0
0
0
-2
2
400
-4
4
800
{'a': [0, -2, -4], 'b': [0, 2, 4], 'c': [0, 400, 800]}

【讨论】:

    【解决方案2】:

    查看您的代码的固定版本:

    import itertools
    
    def make_generator(values):
        return (values[0] * values[1] * x for x in itertools.count())
    
    d = {
    "a": [-1, 2],
    "b": [1, 2],
    "c": [20, 20]
    }
    g = dict()
    g2 = dict()
    for letter, values in d.items():
        g[letter] = make_generator(values)
        g2[letter] = [values[0] * values[1] * x for x in range(3)]
    
    for i in range(3):
        for l, v in g.items():
            print(v.__next__())
    print(g2)
    

    打印出来:

    0
    0
    0
    -2
    2
    400
    -4
    4
    800
    {'a': [0, -2, -4], 'b': [0, 2, 4], 'c': [0, 400, 800]}
    

    关键是在您的代码中,所有生成器都在本地范围内使用相同的values 变量,因此它们最终都使用字典中最后一个键的values。在我的版本中,每个生成器都使用正确的values,因为每个生成器都是在单独的范围内创建的。

    g2 中的列表推导不会发生这种情况,因为它们会立即在本地范围内使用正确的 values 进行评估,并且稍后会在 values 已被覆盖时评估生成器。

    【讨论】:

      猜你喜欢
      • 2012-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-21
      • 1970-01-01
      • 2020-09-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多