6

I have the following python code that generates a list of anonymous functions:

basis = [ (lambda x: n*x) for n in [0, 1, 2] ]     
print basis[0](1)

I would have expected it to be equivalent to

basis = [ (lambda x: 0*x), (lambda x: 1*x), (lambda x: 2*x) ]
print basis[0](1)

However, whereas the second snippet prints out 0 which is what I would expect, the first prints 2. What's wrong with the first snippet of code, and why doesn't it behave as expected?

Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
D R
  • 21,936
  • 38
  • 112
  • 149

4 Answers4

9

You can use a default parameter to create a closure on n

>>> basis = [ (lambda x,n=n: n*x) for n in [0, 1, 2] ]     
>>> print basis[0](1)
0
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • 3
    This technique is frequently used to pass methods as callback functions. It looks something like `register_event_handler(event=evt, callback=lambda cbVar1, cbVar2, s=self: s.handle_evt(cbVar1, cbVar2))`. – Mike DeSimone Dec 13 '10 at 05:22
4

Because it's "pass by name".

That is, when the lambda is run, it executes n*x: x is bound to 1 (it is a parameter), n is looked up in the environment (it is now 2). So, the result is 2.

luispedro
  • 6,934
  • 4
  • 35
  • 45
  • This doesn't explain why the same behaviour is exhibited when we use a generator comprehension instead, which doesn't "leak" `n` into the `locals()`. AFAICT, the value of `n` is looked up from the lambda's `.func_closure`, and it's not at all clear how this magically refers to whatever `n` last referred to when `n` doesn't exist any more. – Karl Knechtel Dec 13 '10 at 07:32
3

The problem is that in the first example, each lambda is bound to the same n -- in other words, it is capturing the variable, not the variable's value. Since n has the value of 2 at the end of the loop, each lambda is using the value 2 for n.

Apparently you can use default parameters to solve this problem:

basis = [ (lambda x,n=n: n*x) for n in [0, 1, 2] ]
print basis[0](1) 

Since default parameter values are constants, the n on the right side of n=n will be evaluated each time through the loop to give you a new captured value.

Gabe
  • 84,912
  • 12
  • 139
  • 238
  • Any suggestions on how to fix that? – D R Dec 13 '10 at 05:11
  • 1
    celil: I edited my answer to offer a suggested fix, but it's not very elegant. – Gabe Dec 13 '10 at 05:16
  • Default parameters might not be elegant, but they're a heck of a lot easier to look at than explicit partial evaluation like I used to do: `basis = [(lambda n:(lambda x: n * x))(n) for n in range(3)]`. :/ The default parameter isn't a "constant" in the sense of being immutable, but it does get evaluated and bound into the lambda's `.func_defaults`. Closure parameters do too, but apparently in a more magical way... – Karl Knechtel Dec 13 '10 at 07:56
  • Karl: By "constant" I mean that the expression is evaluated once and never changes. Of course of that expression evaluates to a reference then you can change the object it refers to, but you can't change the reference itself. – Gabe Dec 13 '10 at 08:01
0

I want to help with the understanding of the comment from Karl Knechtel ( Dec 13 '10 at 7:32). The following code shows how using the generator, the original lambda definition gives the intended result, but it does not using list or tuple:

>>> #GENERATOR
... basis = ( (lambda x: n*x) for n in [0, 1, 2] )  
>>> print(type(basis))
<type 'generator'>
>>> basis = ( (lambda x: n*x) for n in [0, 1, 2] ) 
>>> print([x(3) for x in basis])
[0, 3, 6]
>>> #TUPLE
... basis = tuple( (lambda x: n*x) for n in [0, 1, 2] )
>>> print(type(basis))
<type 'tuple'>
>>> print([x(3) for x in basis])
[6, 6, 6]
>>> #LIST
... basis = list( (lambda x: n*x) for n in [0, 1, 2] )
>>> print(type(basis))
<type 'list'>
>>> print([x(3) for x in basis])
[6, 6, 6]
>>> #CORRECTED LIST
... basis = list( (lambda x, n=n: n*x) for n in [0, 1, 2] )
>>> print(type(basis))
<type 'list'>
>>> print([x(3) for x in basis])
[0, 3, 6]
Mantxu
  • 319
  • 2
  • 11