Reputation: 1167
I am currently trying to be more familiar with iterators in Python, and I encountered some weird behaviour. Essentially, I get the wrong behaviour with a generator comprehension, but the correct behaviour with a list comprehension.
Let me start by explaining what I try to do, and then what behaviour I get.
Imagine having a dictionary of iterables, e.g.
d = {'a': [1, 2, 3], 'b': [4, 5]}
What I want is to have a list of dictionaries with all possible combinations of the iterable. For the first example, this would be
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},
]
To do this, I created this generator:
def dict_value_iterator(d):
for k, v in d.items():
yield ((k, vi) for vi in v)
The idea was to run the following code to get the wanted result
def get_all_dicts(d):
return map(dict, *itertools.product(dict_value_iterator(d)))
Now, for the strange behaviour.
To test that the dict_value_iterator
generator indeed did what I hoped it would, I ran the following code:
for i in dict_value_iterator(d):
print(list(i))
which indeed does what I hoped it would, namely print out the following:
[('a', 1), ('a', 2), ('a', 3)]
[('b', 4), ('b', 5)]
However, when I run the following code
def test_unpacking(*args):
for a in args:
print(list(a))
test_unpacking(*dict_value_iterator(d))
I get the output
[('b', 1), ('b', 2), ('b', 3)]
[('b', 4), ('b', 5)]
This makes little to no sense for me, why does iterator unpacking change anything.
Final note.
The way I found it was by running the get_all_dicts
function on d, which resulted in the following output
[{'b': 4}, {'b': 5}, {'b': 4}, {'b': 5}, {'b': 4}, {'b': 5}]
However, when I modify the dict_value_iterator
as follows
def dict_value_iterator(d):
for k, v in d.items():
yield ((k, vi) for vi in v)
I get this output
[{'a': 1, 'b': 4},
{'a': 1, 'b': 5},
{'a': 2, 'b': 4},
{'a': 2, 'b': 5},
{'a': 3, 'b': 4},
{'a': 3, 'b': 5}]
which is what I want.
Upvotes: 3
Views: 173
Reputation: 224942
Here’s a simplified version:
generators = []
for i in [1, 2]:
generators.append((i for _ in [1]))
print(list(generators[0])) # [2]
Only a single variable called i
exists, and the for
loop sets it repeatedly. All generators created by the generator expression refer to the same i
and don’t read it until the loop has exited.
One way to fix it is by creating another scope with a function (like you would in ES5, for example):
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)
Upvotes: 2