简体   繁体   English

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

[英]Different generators added to the same dictionary return the same values

I have just encountered a weird Python (3.7.0) behavior which I don't really understand and looks like a bug for me. 我刚刚遇到一个奇怪的Python(3.7.0)行为,我真的不明白,看起来像是一个bug。 I want to create a dictionary with generators, but somehow all of them return the same values. 我想用生成器创建一个字典,但不知怎的,它们都返回相同的值。 Here is a code example of what I am talking about: 这是我正在谈论的代码示例:

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)

From my point of view, expected output would be the same for g2 elements and g generators, however I always receive values from the latest generator: 从我的观点来看,g2元素和g生成器的预期输出是相同的,但是我总是从最新的生成器接收值:

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

To conclude, am I doing something wrong? 总而言之,我做错了吗? Or is it just a standard Python behavior? 或者它只是标准的Python行为?

This error is not due to generators. 此错误不是由于生成器造成的。 It's a scoping error. 这是一个范围错误。

In your generator declaration, you use the name values , although the generator is not executed until after the loop is done where values is now the last item in the list. 在生成器声明中,使用名称values ,尽管直到循环完成才会执行生成器,其中values现在是列表中的最后一项。 Here is an example that reproduces your error. 这是一个重现错误的示例。

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

i = 'some new value'

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

In other words values[0] and values[1] were not bound to the generator and if the value behind the name values changes then so will the generators outputs. 换句话说, values[0]values[1]未绑定到生成器,如果名称values后面的values发生更改,则生成器输出也会发生变化。

This means you want a closure around your generator to store the values values[0] and values[1] . 这意味着您希望生成器周围的闭包存储值values[0]values[1] You can do that by defining your generator as a function. 您可以通过将生成器定义为函数来实现。

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)

In fact, inline generators are rarely used for that reason. 实际上,由于这个原因,很少使用内联生成器。 Favor the def-yield way of creating generators. 支持创建生成器的def-yield方式。

Also, do no call __next__ , use the builtin next instead. 另外,不要调用__next__ ,而是使用内置的next

Output 产量

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

Check out a fixed version of your code: 查看代码的固定版本:

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)

It prints out: 打印出来:

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

The point is that in your code all generators work with the same values variable in the local scope, so they all end up using the values from the last key in the dict. 关键是在您的代码中,所有生成器在本地范围内使用相同的values变量,因此它们最终都使用了dict中最后一个键的values In my version, each generator uses correct values because each generator is created in a separate scope. 在我的版本中,每个生成器使用正确的values因为每个生成器都在单独的范围内创建。

This doesn't happen with the list comprehensions in g2 because they are evaluated immediately with correct values in the local scope, and the generators are evaluated later when values has already been overwritten. 对于g2的列表g2不会发生这种情况,因为它们会在本地范围内使用正确的values立即求values ,并且稍后在values已被覆盖时对生成器进行求values

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM