Python中late-binding-closures

时间:2021-08-16 17:28:49

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://*.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。

  

学无止境,戒骄戒躁。