6

I am new in Python. My task was quite simple -- I need a list of functions that I can use to do things in batch. So I toyed it with some examples like

fs = [lambda x: x + i for i in xrange(10)]

Surprisingly, the call of

[f(0) for f in fs]

gave me the result like [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]. It was not what I expected as I'd like the variable i has different values in different functions.

So My question is:

  1. Is the variable i in lambda global or local?

  2. Does python has the same concept like 'closure' in javascript? I mean does each lambda here holds a reference to the i variable or they just hold a copy of the value of i in each?

  3. What should I do if I'd like the output to be [0, 1, .....9] in this case?

4 Answers 4

10

It looks a bit messy, but you can get what you want by doing something like this:

>>> fs = [(lambda y: lambda x: x + y)(i) for i in xrange(10)]
>>> [f(0) for f in fs]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Normally Python supports the "closure" concept similar to what you're used to in Javascript. However, for this particular case of a lambda expression inside a list comprehension, it seems as though i is only bound once and takes on each value in succession, leaving each returned function to act as though i is 9. The above hack explicitly passes each value of i into a lambda that returns another lambda, using the captured value of y.

Sign up to request clarification or add additional context in comments.

4 Comments

Yes, the trick above was smart. It creates local copies by using an extra closure.
Actually, closures work just like they're expected (capture variables, not the value of variables at closure creation time) - and JS does this as well (try creating functions in a loop).
it seems as though i is only bound once and takes on each value in succession, ... If so, shouldn't the value of i to be 0, for it's the first value generated by xrange?
Actually, all closures in python work like this, which is completely insane. People need to stop defending python. Python is insane.
6

The problem you are running into here is the distinction between "early binding" and "late binding".

When Python looks up a variable from an outer scope (i in this case) it uses late binding. That means it sees the value of that variable at the time the function is called, rather than the value at the time the function is defined.

So, in your example code, all 10 lambda functions see the final value assigned to the i variable by the looping process: 9.

Greg's answer shows one way to force early binding behaviour (i.e. create an additional closure and call it immediately while still inside the loop).

Another commonly used approach to forcing early binding semantics is the "default argument hack", which binds the variable as a default argument at function definition time:

>>> fs = [(lambda x, _i=i: x + _i) for i in xrange(10)]
>>> [f(0) for f in fs]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Either method works. Greg's has the advantage of not messing with the returned function's signature, the default argument hack is faster and is significantly more readable than adding an additional closure level when defining named functions rather than using lambda expressions.

1 Comment

yes, it is good to know there is an alternative hack in python. Greg's trick has correspondence in Javascript, but your hack is new to me. Thanks for the great explaination
4
  1. The variable i is local to the list comprehension, but it's available to the lambda because the lambda is in its scope.
  2. Yes, lambdas are closures. Variable binding might not always work the way you want it to, but they're closures. You shouldn't rely on them heavily, though.
  3. You'd just want to loop over xrange(10). You could do this with lambdas (see the other answer), but you wouldn't want to. Lambdas should be used pretty sparingly.

The reason that the lambda behaves this way is because for each loop i is rebound. Since i isn't local to the lambda, it changes too, and the last value it holds is 9. So all you're doing is 0 + 9 10 times.

Comments

1

i is local and there are indeed closures in python.

I believe your confusion is that you assigh fs to a list of identical function.

>>> fs = [lambda x: x + i for i in xrange(10)]
>>> fs
[<function <lambda> at 0x02C6E930>, <function <lambda> at 0x02C6E970>, <function <lambda> at 0x02C6E9B0>, <function <lambda> at 0x02C6E9F0>, <function <lambda> at 0x02C6EA30>, <function <lambda> at 0x02C6EA70>, <function <lambda> at 0x02C6EAB0>, <function <lambda> at 0x02C6EAF0>, <function <lambda> at 0x02C6EB30>, <function <lambda> at 0x02C6EB70>]
>>> fs[0](0)
9
>>> fs[0](100)
109
>>> fs[5](0)
9
>>> fs[5](100)
109

I think a single function returning a list would be more appropriate.

>>> fs3 = lambda x: [x + i for i in xrange(10)]
>>> fs3
<function <lambda> at 0x02C6EC70>
>>> fs3(0)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.