16

What's going on here? I'm trying to create a list of functions:

def f(a,b):
    return a*b

funcs = []

for i in range(0,10):
    funcs.append(lambda x:f(i,x))

This isn't doing what I expect. I would expect the list to act like this:

funcs[3](3) = 9
funcs[0](5) = 0

But all the functions in the list seem to be identical, and be setting the fixed value to be 9:

funcs[3](3) = 27
funcs[3](1) = 9

funcs[2](6) = 54

Any ideas?

Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
Dan Lorenc
  • 5,376
  • 1
  • 23
  • 34

5 Answers5

20

lambdas in python are closures.... the arguments you give it aren't going to be evaluated until the lambda is evaluated. At that time, i=9 regardless, because your iteration is finished.

The behavior you're looking for can be achieved with functools.partial

import functools

def f(a,b):
    return a*b

funcs = []

for i in range(0,10):
    funcs.append(functools.partial(f,i))
Ryan
  • 4,179
  • 6
  • 30
  • 31
  • I agree. Partial application is the way to go here. – Evan Fosmark Jul 10 '09 at 01:15
  • here partial(f,i) stands for partial(f,b=i) not partial(f,a=i). so it's not the same thing as the original post. Partial function application 'from the right' (http://www.gossamer-threads.com/lists/python/dev/715103) has been rejected two times. – sunqiang Jul 10 '09 at 01:26
15

Yep, the usual "scoping problem" (actually a binding-later-than-you want problem, but it's often called by that name). You've already gotten the two best (because simplest) answers -- the "fake default" i=i solution, and functools.partial, so I'm only giving the third one of the classic three, the "factory lambda":

for i in range(0,10):
    funcs.append((lambda i: lambda x: f(i, x))(i))

Personally I'd go with i=i if there's no risk of the functions in funcs being accidentally called with 2 parameters instead of just 1, but the factory function approach is worth considering when you need something a little bit richer than just pre-binding one arg.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 2
    I haven't seen this one before. Kind of neat. – ars Jul 10 '09 at 02:12
  • @ars, bit of an overkill for most cases actually (in Python), but it IS what one would naturally do in (say) Scheme, so I guess it _is_ kinda neat;-). – Alex Martelli Jul 10 '09 at 02:16
  • Right, I can't immediately think of a case where I would prefer it, but a fun construction nonetheless. :-) – ars Jul 10 '09 at 05:55
  • There is one case where this naturally pops up: when you use map(range..., lambda i: ...) instead of for loops and list comprehensions. Functional code in general is very good at avoiding capture bugs because you only capture immutable state – saolof Aug 09 '23 at 18:46
10

There's only one i which is bound to each lambda, contrary to what you think. This is a common mistake.

One way to get what you want is:

for i in range(0,10):
    funcs.append(lambda x, i=i: f(i, x))

Now you're creating a default parameter i in each lambda closure and binding to it the current value of the looping variable i.

ars
  • 120,335
  • 23
  • 147
  • 134
2

All the lambdas end up being bound to the last one. See this question for a longer answer:

How do I create a list of Python lambdas (in a list comprehension/for loop)?

Community
  • 1
  • 1
sykora
  • 96,888
  • 11
  • 64
  • 71
2

Considering the final value of i == 9

Like any good python function, it's going to use the value of the variable in the scope it was defined. Perhaps lambda: varname (being that it is a language construct) binds to the name, not the value, and evaluates that name at runtime?

Similar to:

i = 9
def foo():
    print i

i = 10
foo()

I'd be quite interested in finding out of my answer is correct

gahooa
  • 131,293
  • 12
  • 98
  • 101