开发者

lambda push to a list then invoking - output is not as expected [duplicate]

This question already has answers here: Creating functions (or lambdas) in a loop (or comprehension) (6 answers) Closed 5 months ago.

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
>>>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜