4

Consider the following Python 3 instructions

res = []
for a in range(3) :
 res.append(lambda n : n + a)

with the aim of building a list res of three functions res[0], res[1], and res[2] such that res[i](n) returns n + i, for all i in [0, 1, 2].

Nevertheless, one obtains

>>> res[0](0)
2

instead of

>>> res[0](0)
0

One also have

>>> res[1](2)
4

The explanation of this behavior is that in the expression n + a in the body of any dynamically generated anonymous function of the example, the symbol a is not evaluated at the creation of the function. The evaluation is performed at the exit of the for statement, explaining why all functions res[0], res[1], and res[2] return the value of their argument plus 2è (becauseabrowsesrange(3)and2` is its last value).

Notice that the problem does not lie in the fact that we use anonymous functions. Indeed, the instructions

res = []
for a in range(3) :
 def f(n) :
 return n + a
 res.append(f)

lead to the same behavior.

Notice also that we can meet the objective set out above by using the function eval of Python:

res = []
for a in range(3) :
 s = "lambda n : n + %s" %(a)
 res.append(eval(s))

The trick lies on the fact that the dynamic value of a is considered for the creation of each function of res.

Now my questions are

  1. is this a bug or a feature?
  2. is there an other way not passing by eval to obtain the expected behavior?
asked May 6, 2015 at 19:09
1

4 Answers 4

3

Because of closure the dynamic functions referencing variable a and using the final value it had at the end of the for loop when they execute.

You can prevent that by making it an function argument with a default value so it doesn't need to be provided on calls. The value of a at the time each dynamic function is defined will become the value used. This is what I mean:

res = []
for a in range(3) :
 res.append(lambda n, a=a: n + a)
print(res[0](0)) # -> 0
print(res[1](2)) # -> 3
answered May 6, 2015 at 19:20
Sign up to request clarification or add additional context in comments.

9 Comments

That makes it a two-argument function, though. Not so clean.
@StefanPochmann: Not really, as illustrated by the sample calls to the functions at the end. This is because values for arguments with default values are optional.
Yes, it's optional, but it's still there.
@StefanPochmann: You're entitled to your own minority opinion, of course, but this is a common Python idiom to deal with issues like this and is very lightweight.
Ok, thanks, I tested it and it's indeed smaller and faster than mine. I'll keep it in mind.
|
2

I cant speak to #1 ... except to say I am sure that is intentional and desirable behaviour for some definition of intentional and desirable

but WRT #2

res = []
for a in range(3) :
 res.append(lambda n,b=a : n + b)
res[0](0)

this will evaluate a as the default 2nd argument while inside the for loop(instead of after exiting) ...

of coarse this leaves it open to something like

res[0](0,8)
answered May 6, 2015 at 19:13

Comments

0

This would also work, if you don't want to use lambda:

>>> from functools import partial
>>> for a in range(3) :
 def f(a, n):
 return n + a
 f = partial(f, a)
 res.append(f)
>>> res[0](0)
0
>>> res[1](2)
3
answered May 6, 2015 at 19:56

Comments

0

This gives you clean functions which just have one argument:

res = []
for a in range(3) :
 res.append((lambda a: lambda n: n+a)(a))

Might be better for readability and efficiency to do it this way, though:

def adder(amount):
 return lambda n: n + amount
res = []
for a in range(3) :
 res.append(adder(a))

Also, your "The evaluation is performed at the exit of the for statement" is wrong. Try printing after the loop, then increase a further, then print again. You'll see that the functions now use the further increased value of a (only with your version, of course, not with mine). That's because your functions don't have their own a but use the same global a, and evaluate it whenever they're called.

answered May 6, 2015 at 20:06

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.