添加到同一字典的不同生成器返回相同的值

Zas*_*dos 2 python dictionary python-itertools python-3.x

我刚刚遇到一个奇怪的Python(3.7.0)行为,我真的不明白,看起来像是一个bug.我想用生成器创建一个字典,但不知怎的,它们都返回相同的值.这是我正在谈论的代码示例:

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)
Run Code Online (Sandbox Code Playgroud)

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

0
0
0
400
400
400
800
800
800
{'a': [0, -2, -4], 'b': [0, 2, 4], 'c': [0, 400, 800]}
Run Code Online (Sandbox Code Playgroud)

总而言之,我做错了吗?或者它只是标准的Python行为?

Oli*_*çon 5

此错误不是由于生成器造成的.这是一个范围错误.

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

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

i = 'some new value'

print(next(g)) # 'some new value'
Run Code Online (Sandbox Code Playgroud)

换句话说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)
Run Code Online (Sandbox Code Playgroud)

实际上,由于这个原因,很少使用内联生成器.喜欢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]}
Run Code Online (Sandbox Code Playgroud)