5

Why does this attempt at creating a list of curried functions not work?

def p(x, num):
    print x, num

def test():
    a = []
    for i in range(10):
        a.append(lambda x: p (i, x))
    return a

>>> myList = test()
>>> test[0]('test')
9 test
>>> test[5]('test')
9 test
>>> test[9]('test')
9 test

What's going on here?

A function that actually does what I expect the above function to do is:

import functools
def test2():
    a = []
    for i in range (10):
        a.append(functools.partial(p, i))
    return a


>>> a[0]('test')
0 test
>>> a[5]('test')
5 test
>>> a[9]('test')
9 test
2
  • Since you have a solution that uses functools.partial, what is the question? Commented May 8, 2009 at 20:09
  • 2
    The question is, why doesn't the first method work? Commented May 8, 2009 at 20:21

4 Answers 4

12

In Python, variables created in loops and branches aren't scoped. All of the functions you're creating with lambda have a reference to the same i variable, which is set to 9 on the last iteration of the loop.

The solution is to create a function which returns a function, thus scoping the iterator variable. This is why the functools.partial() approach works. For example:

def test():
    def makefunc(i):
        return lambda x: p(i, x)
    a = []
    for i in range(10):
        a.append(makefunc(i))
    return a
Sign up to request clarification or add additional context in comments.

Comments

1

Well you can also bind the i to an outer lambda for the lazy.

def p(x, num):
    print x, num

def test():
    a = []
    for i in range(10):
        a.append((lambda i :lambda x: p (i, x))(i))
    return a

Comments

1

I was always confused as to why this doesn't work. Thanks for the explanation, 'a paid nerd'. I personally prefer this solution:

for i in range(10):
    a.append(lambda num, val_i=i: p (val_i, num))

Note the val_i=i default argument of the lambda that enables to capture the instantaneous value of i during the loop whilst still effectively making lambda a function of 1 variable. (BTW: changed your x into num to match p's definition.) I like it better because:

  1. it is very close to the original idea and avoids having to define a new named function, precisely the purpose of a lambda...
  2. avoids importing functools
  3. and avoids imbricating lambdas...

Just did a search and found more detailed explanations for the same problem there: Scope of python lambda functions and their parameters

1 Comment

The named argument is why this works and why your original didn't. Here's my exploration of closures in Python: gist.github.com/maxcountryman/5035489
0

I asked a similar question, and got two answers. One basically the same as the accepted answer here, and the other which is less clear but slightly more succint.

Dynamically creating a menu in Tkinter. (lambda expressions?)

1 Comment

Which includes (and I finally understand now) the lambda x=x : ... hack.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.