Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

currying functions in python in a loop

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?

like image 970
NorthIsUp Avatar asked Apr 11 '26 01:04

NorthIsUp


1 Answers

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;-).

like image 57
Alex Martelli Avatar answered Apr 12 '26 13:04

Alex Martelli



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!