So here is some code that simplifies what I've been working on:
vars = {
'a':'alice',
'b':'bob',
}
cnames = ['charlie', 'cindy']
commands = []
for c in cnames:
kwargs = dict(vars)
kwargs['c'] = c
print kwargs
commands.append(lambda:a_function(**kwargs))
print commands
def a_function(a=None, b=None, c=None):
print a
print b
print c
for c in commands:
print "run for "+ repr(c)
c()
And here is its output:
{'a': 'alice', 'c': 'charlie', 'b': 'bob'}
{'a': 'alice', 'c': 'cindy', 'b': 'bob'}
[<function <lambda> at 0x1001e9a28>, <function <lambda> at 0x1001e9e60>]
run for <function <lambda> at 0x1001e9a28>
alice
bob
cindy
run for <function <lambda> at 0x1001e9e60>
alice
bob
cindy
I would expect to get charlie, then cindy, why is cindy being displayed twice?
You're encountering a classic binding-time problem, and @Mike's solution is the classic one. A good alternative is to write a higher order function:
def makecall(kwargs):
def callit():
return a_function(**kwargs)
return callit
and use commands.append(makecall(kwargs)) in your loop. Both solutions work on the same principle (by forcing early binding through passage of an argument -- a plain argument in my case, a default value for a named argument in @Mike's); the choice is just a matter of style or elegance (me, while I tolerate lambda in super-simple cases, as long as the subtlest complication intervenes I vastly prefer good old def;-).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With