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

时间:2018-09-22 11:21:02

标签: python python-3.x dictionary itertools

我刚遇到一个怪异的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行为?

2 个答案:

答案 0 :(得分:5)

此错误不是由于生成器引起的。这是范围界定错误。

在生成器声明中,您使用名称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]}

答案 1 :(得分: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被评估后再进行评估覆盖。