lambda push to a list then invoking - output is not as expected [duplicate]
All:
def a(p):
return p+1
def gen(func, k=100):
l= []
for x in range(k):
temp = ("%s_with_parameter_%s" %(func.__name__, x), lambda: f开发者_开发知识库unc(x))
# maybe this will be more clear to explain my quetion:
# i want to get list/dict which can bind self-defined string and function together
l.append(temp)
return l
l = gen(a, 100)
for x in range(len(l)):
l[x][1]()
100
100
100
100
100
100
100
100
...I suppose output will be a 1 to 101
print out, but it shows a 100
list.
May I get help for this snippet here?
Thanks!
As other answers have noted, the lambdas are using the last value of x because they're closed over it and so see any changes made to it. The trick is to bind them to the value.
You can do this by writing them as
lambda x=x: func(x)
This binds the value of x
that is current when the lambda is created to the default parameter p which means that the lambda is no longer a closure over x
and is not affected by any future changes in its value. You would change the way you call it to not pass in an argument that you don't do anything with:
for x in range(len(l)):
l[x][1]()
Now, the lambda uses the default value which is bound to what you want.
If you do actually need to pass a value in, then you can just stick the default parameter that is used for binding purposes on after the 'real' parameter:
lambda p, x=x: func(p, x)
The standard trick is to write your function gen()
as:
def gen(func, k=100):
l= []
for x in range(k):
l.append(lambda x=x: func(x))
return l
Note the parameter to the lambda expression. This enforces that a new x
is created for each lambda
. Otherwise, all use the same x
from the enclosing scope.
Maybe with this slight variant of your code you'll understand what happens.
def a(p):
return p+1
def gen(func, k=100):
l= []
for x in range(k):
l.append(lambda p: func(x))
x = 77
return l
l = gen(a, 100)
for x in range(len(l)):
print l[x](10)
Now you always get 78 when calling lx. The problem is you always have the same variable x in the closure, not it's value at the moment of the definition of the lambda.
I believe you probably want something like Haskell curryfication. In python you can use functools.partial for that purpose. I would write your code as below. Notice I have removed the dummy parameter.
import functools
def a(p):
return p+1
def gen(func, k=100):
l= []
for x in range(k):
l.append(functools.partial(func, x))
return l
l = gen(a, 100)
for x in range(len(l)):
print l[x]()
Now let's introduce back the dummy parameter:
import functools
def a(p):
return p+1
def gen(func, k=100):
fn = lambda x, p: func(x)
l= []
for x in range(k):
l.append(functools.partial(fn, x))
return l
l = gen(a, 100)
for x in range(len(l)):
print l[x](10)
Edit: I much prefer the solution of Sven Marnach over mine :-)
In your code, it looks like the value used in a()
is the last value known for x
(which is 100) while you're p
variable is actually dummy (like you mention).
This is because the lambda function is "resolved" when you call it.
It means that calling l[x](10)
would be "equivalent" to: (this is not correct, it's to explain)
lambda 10: func(100)
You can use the yield statement to create a generator, I think is the most elegant solution:
>>> def a(p):
... return p+1
...
>>> def gen(func, k=100):
... for x in range(k):
... yield lambda :func(x)
...
>>> for item in gen(a, 100):
... item()
...
1
2
3
4
(...)
100
>>>
But as you can see it goes only until 100 because of the range function:
>>> range(100)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 2
2, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 4
2, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 6
2, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 8
2, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>>
You can use gen(a, 101) to solve this:
>>> for item in gen(a, 101):
... item()
...
1
2
3
4
5
(...)
101
>>>
精彩评论