Python中late-binding-closures

2018-01-03 @望京

示例1

>>> b = []
>>> for i in range(5):
...     b.append(lambda :i)
... 
>>> for j in b:
...     print j()
... 
4
4
4
4
4
>>> for m in b:
...     print type(m),m
... 
<type 'function'> <function <lambda> at 0x7fe4aae708c0>
<type 'function'> <function <lambda> at 0x7fe4aae70938>
<type 'function'> <function <lambda> at 0x7fe4aae709b0>
<type 'function'> <function <lambda> at 0x7fe4aae70b18>
<type 'function'> <function <lambda> at 0x7fe4aae70b90>
>>> 
>>> 

 为什么不是输出 0~4?   

Closures in Python are late-binding, 
meaning that each lambda function in the list will only evaluate the variable i when invoked, 
and not when defined. 

That's why all functions return the same value, i.e. the last value of ì (which is 4).

late-binding-closures in Python  http://docs.python-guide.org/en/latest/writing/gotchas/#late-binding-closures

 

怎么修改使之输出 0~4?   b.append(lambda i=i:i)

>>> b = []
>>> for i in range(5):
...     b.append(lambda i=i:i)
... 
>>> for j in b:
...     print j()
... 
0
1
2
3
4
>>> 

或者使用 functools.partial

>>> b = []
>>> for i in range(5):
...     from functools import partial
...     b.append(partial(lambda x:x, i))
... 
>>> for j in b:
...     print j()
... 
0
1
2
3
4
>>> 

参考:https://stackoverflow.com/questions/38369470/lambdas-from-a-list-comprehension-are-returning-a-lambda-when-called

 

lambda补充

>>> foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]
>>>
>>> print filter(lambda x: x % 3 == 0, foo)   # 过滤
[18, 9, 24, 12, 27]
>>>
>>> print map(lambda x: x * 2 + 10, foo)      # map
[14, 46, 28, 54, 44, 58, 26, 34, 64]
>>>
>>> print reduce(lambda x, y: x + y, foo)     # 求和
139
>>>

 

示例2

>>> def func(arg=[]):
...     arg.append(1)
...     print arg
... 
>>> func()
[1]
>>> func()
[1, 1]
>>> 

修改1:在函数调用的时候传入参数

>>> def func(arg=[]):
...     arg.append(1)
...     print arg
... 
>>> func([])
[1]
>>> func([])
[1]
>>>  

修改2:默认参数改为 None (更安全的做法)

>>> 
>>> def func(arg=None):
...     if not arg:
...         arg = []
...     arg.append(1)
...     print arg
... 
>>> 
>>> func()
[1]
>>> func()
[1]
>>>

示例3

>>> 
>>> def func(x, l=[]):
...     for i in range(x):
...             l.append(i*i)
...     print l
... 
>>> func(2)
[0, 1]
>>> func(3)
[0, 1, 0, 1, 4]
>>> 

 

2018-03-12  https://zhuanlan.zhihu.com/p/33376761  

今天在地铁上看到公众号推荐这个文章,也是late-binding问题,再补充下:

def foo():
    temp = [lambda x : i*x for i in range(4)]
    return temp

for bar in foo():
    print(bar(2))

# 输出
6
6
6
6

其实可以改写成下面这样,这样就比较容易看出是闭包了:lambda本身是一个函数,调用了外面的变量 i 

temp = []
for i in range(4):
    temp.append(lambda x : i*x)

for bar in temp:
    print(bar(2))

 

解决方法1:  [lambda x,i=i : i*x for i in range(4)]

def foo():
    temp = [lambda x,i=i : i*x for i in range(4)]
    return temp

for bar in foo():
    print(bar(2))

解决方法2:使用 functools.partial

from functools import partial
from operator import mul

def foo():
    temp = [partial(mul,i) for i in range(4)]
    return temp

for bar in foo():
    print(bar(2))

解决方法3:把temp改成生成器

def foo():
    temp = (lambda x : i*x for i in range(4))
    return temp

for bar in foo():
    print(bar(2))

# 或者用 next() 方式调用
# bar = foo()
# print(next(bar)(2))
# print(next(bar)(2))
# print(next(bar)(2))
# print(next(bar)(2))

# 或者用 next() 方式调用
# bar = foo()
# print(bar.__next__()(2))
# print(bar.__next__()(2))
# print(bar.__next__()(2))
# print(bar.__next__()(2))

解决方法4:使用yield

def foo():
    for i in range(4):
        yield lambda x : i*x

for bar in foo():
    print(bar(2))

补充

构造生成器的两种方式:
    使用类似列表生成式的方式生成 (2*n + 1 for n in range(3, 11))
    使用包含yield的函数来生成

如果计算过程比较简单,可以直接把列表生成式改成generator;
但是,如果计算过程比较复杂,就只能通过包含yield的函数来构造generator。

  

学无止境,戒骄戒躁。

posted @ 2018-01-10 21:29  lixin[at]hitwh  阅读(591)  评论(0编辑  收藏  举报