currying functions in python in a loop
So here is some code that simpl开发者_运维技巧ifies 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
;-).
A function's body isn't ran until the function is called. When you do lambda: a_function(**kwargs)
, kwargs
isn't looked up until you actually call the function. At that point it's assigned to the last one you made in the loop.
One solution that gets the result you want would be to do commands.append(lambda kwargs=kwargs: a_function(**kwargs))
精彩评论