Reputation: 494
I've got the following dictionary:
d = {
'A': {
'param': {
'1': {
'req': True,
},
'2': {
'req': True,
},
},
},
'B': {
'param': {
'3': {
'req': True,
},
'4': {
'req': False,
},
},
},
}
I want to have a generator which will give me for each first level keys, the required parameters.
req = {}
for key in d:
req[key] = (p for p in d[key]['param'] if d[key]['param'][p].get('req', False))
So here, for each key in d
, I get parameter p
only if req
is True
.
However, when I try to use my generator, it raises a KeyError
exception:
>>> req
{'A': <generator object <genexpr> at 0x27b8960>,
'B': <generator object <genexpr> at 0x27b8910>}
>>> for elem in req['A']:
... print elem
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-6-a96226f95cce> in <module>()
----> 1 for elem in req['A']:
2 print elem
3
<ipython-input-4-1732088ccbdb> in <genexpr>((p,))
1 for key in d:
----> 2 req[key] = (p for p in d[key]['param'] if d[key]['param'][p].get('req', False))
3
KeyError: '1'
Upvotes: 4
Views: 2284
Reputation: 76194
The generator expressions you assign to req[key]
binds on the key
variable. But key
changes from 'A' to 'B' in the loop. When you iterate over the first generator expression, it will evaluate key
to 'B' in its if
condition, even though key
was 'A' when you created it.
The conventional way to bind to a variable's value and not its reference, is to wrap the expression in a lambda with a default value, and then call it immediately.
for key in d:
req[key] = (lambda key=key: (p for p in d[key]['param'] if d[key]['param'][p].get('req', False)))()
Result:
1
2
Upvotes: 4
Reputation: 91017
This is because upon execution of the generator, the latest value of key
is used.
Suppose the for key in d:
iterates over the keys in the order 'A', 'B'
, the 1st generator is supposed to work with key = 'A'
, but due to closure issues, it uses the item with 'B'
as key. And this has no '1'
sub-entry.
Even worse, the key
variable in the generator has two different values: the for p in d[key]['param']
part uses the "correct" value, while the if d[key]['param'][p].get('req', False)
uses the "closure value", which is the last one.
Upvotes: 3