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

Posted

技术标签:

【中文标题】添加到同一个字典的不同生成器返回相同的值【英文标题】:Different generators added to the same dictionary return the same values 【发布时间】:2018-09-22 11:21:02 【问题描述】:

我刚刚遇到了一个奇怪的 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__ 【参考方案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 已被覆盖时评估生成器。

【讨论】:

以上是关于添加到同一个字典的不同生成器返回相同的值的主要内容,如果未能解决你的问题,请参考以下文章

将新字典添加到现有字典作为键的值[重复]

CString.Format 以相同的精度生成不同的值

如何根据列表集中的元素执行字典查询?

添加嵌套字典会导致 JSONSerialization 返回 nil

Swift:如何使用 Eureka 表单生成器获取表单值?

将不同的值添加到并发字典内的列表中