Python 中生成器解包的奇怪行为
Weird behaviour of generator unpacking in Python
我目前正在尝试更熟悉 Python 中的迭代器,但我遇到了一些奇怪的行为。本质上,我在生成器理解中得到了错误的行为,但在列表理解中得到了正确的行为。
让我先解释一下我尝试做什么,然后解释一下我得到了什么行为。
想象一下有一个可迭代的字典,例如
d = {'a': [1, 2, 3], 'b': [4, 5]}
我想要的是一个字典列表,其中包含可迭代对象的所有可能组合。对于第一个示例,这将是
l = [
{'a': 1, 'b': 4},
{'a': 1, 'b': 5},
{'a': 2, 'b': 4},
{'a': 2, 'b': 5},
{'a': 3, 'b': 4},
{'a': 3, 'b': 5},
]
为此,我创建了这个生成器:
def dict_value_iterator(d):
for k, v in d.items():
yield ((k, vi) for vi in v)
想法是运行下面的代码来获得想要的结果
def get_all_dicts(d):
return map(dict, *itertools.product(dict_value_iterator(d)))
现在,对于 st运行ge 行为。
为了测试 dict_value_iterator
生成器确实做了我希望的事情,我 运行 下面的代码:
for i in dict_value_iterator(d):
print(list(i))
这确实如我所愿,即打印出以下内容:
[('a', 1), ('a', 2), ('a', 3)]
[('b', 4), ('b', 5)]
但是,当我运行下面的代码
def test_unpacking(*args):
for a in args:
print(list(a))
test_unpacking(*dict_value_iterator(d))
我得到输出
[('b', 1), ('b', 2), ('b', 3)]
[('b', 4), ('b', 5)]
这对我来说几乎没有意义,为什么迭代器解包会改变任何东西。
最后的注释。
我发现它的方法是 运行 在 d 上调用 get_all_dicts
函数,结果是下面的输出
[{'b': 4}, {'b': 5}, {'b': 4}, {'b': 5}, {'b': 4}, {'b': 5}]
但是,当我修改dict_value_iterator
如下
def dict_value_iterator(d):
for k, v in d.items():
yield ((k, vi) for vi in v)
我得到这个输出
[{'a': 1, 'b': 4},
{'a': 1, 'b': 5},
{'a': 2, 'b': 4},
{'a': 2, 'b': 5},
{'a': 3, 'b': 4},
{'a': 3, 'b': 5}]
这就是我想要的。
这是一个简化版本:
generators = []
for i in [1, 2]:
generators.append((i for _ in [1]))
print(list(generators[0])) # [2]
只有一个名为 i
的变量存在,for
循环重复设置它。生成器表达式创建的所有生成器都引用相同的 i
并且在循环退出之前不读取它。
修复它的一种方法是创建另一个带有函数的范围(例如,就像在 ES5 中那样):
def dict_value_iterator(d):
def get_generator(k, v):
return ((k, vi) for vi in v)
for k, v in d.items():
yield get_generator(k, v)
我目前正在尝试更熟悉 Python 中的迭代器,但我遇到了一些奇怪的行为。本质上,我在生成器理解中得到了错误的行为,但在列表理解中得到了正确的行为。
让我先解释一下我尝试做什么,然后解释一下我得到了什么行为。
想象一下有一个可迭代的字典,例如
d = {'a': [1, 2, 3], 'b': [4, 5]}
我想要的是一个字典列表,其中包含可迭代对象的所有可能组合。对于第一个示例,这将是
l = [
{'a': 1, 'b': 4},
{'a': 1, 'b': 5},
{'a': 2, 'b': 4},
{'a': 2, 'b': 5},
{'a': 3, 'b': 4},
{'a': 3, 'b': 5},
]
为此,我创建了这个生成器:
def dict_value_iterator(d):
for k, v in d.items():
yield ((k, vi) for vi in v)
想法是运行下面的代码来获得想要的结果
def get_all_dicts(d):
return map(dict, *itertools.product(dict_value_iterator(d)))
现在,对于 st运行ge 行为。
为了测试 dict_value_iterator
生成器确实做了我希望的事情,我 运行 下面的代码:
for i in dict_value_iterator(d):
print(list(i))
这确实如我所愿,即打印出以下内容:
[('a', 1), ('a', 2), ('a', 3)]
[('b', 4), ('b', 5)]
但是,当我运行下面的代码
def test_unpacking(*args):
for a in args:
print(list(a))
test_unpacking(*dict_value_iterator(d))
我得到输出
[('b', 1), ('b', 2), ('b', 3)]
[('b', 4), ('b', 5)]
这对我来说几乎没有意义,为什么迭代器解包会改变任何东西。
最后的注释。
我发现它的方法是 运行 在 d 上调用 get_all_dicts
函数,结果是下面的输出
[{'b': 4}, {'b': 5}, {'b': 4}, {'b': 5}, {'b': 4}, {'b': 5}]
但是,当我修改dict_value_iterator
如下
def dict_value_iterator(d):
for k, v in d.items():
yield ((k, vi) for vi in v)
我得到这个输出
[{'a': 1, 'b': 4},
{'a': 1, 'b': 5},
{'a': 2, 'b': 4},
{'a': 2, 'b': 5},
{'a': 3, 'b': 4},
{'a': 3, 'b': 5}]
这就是我想要的。
这是一个简化版本:
generators = []
for i in [1, 2]:
generators.append((i for _ in [1]))
print(list(generators[0])) # [2]
只有一个名为 i
的变量存在,for
循环重复设置它。生成器表达式创建的所有生成器都引用相同的 i
并且在循环退出之前不读取它。
修复它的一种方法是创建另一个带有函数的范围(例如,就像在 ES5 中那样):
def dict_value_iterator(d):
def get_generator(k, v):
return ((k, vi) for vi in v)
for k, v in d.items():
yield get_generator(k, v)