Reputation: 429
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è (because
abrowses
range(3)and
2` 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
eval
to obtain the expected behavior?Upvotes: 4
Views: 384
Reputation: 123463
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
Upvotes: 3
Reputation: 28596
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.
Upvotes: 0
Reputation: 19328
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
Upvotes: 0
Reputation: 113978
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)
Upvotes: 2