我刚遇到一个怪异的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行为?
答案 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
被评估后再进行评估覆盖。