闭包 (Closure)
<<流畅的Python>>中对闭包的定义:
闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量,它能访问定义体之外定义的非全局变量。
闭包有效的减少了函数所需定义的参数数目
闭包 与 lambda
闭包 与 lambda 同时使用时,注意作用域的问题
1. 如下代码:
def createlist():
# [x... for x in xx if x...]
# 列表推导,从现有列表获取一个新的列表
return [lambda x: i*x for i in range(5)]
# createlist() 返回一个各元素是lambda表达式的列表
for i in createlist():
print(i(2))
# 输出:
8
8
8
8
8
createlist函数返回值的列表,每一个元素都是一个函数,指定倍数获取新列表,预期是:[0,2,4,6,8]
但是其中有一个容易忽略的陷阱,Python中的属性查找规则: LEGB(local,enclousing,global,bulitin),在上面的例子中,i
就是在闭包作用域(enclousing)。
而 Python 的闭包是 迟绑定 , 这意味着闭包中用到的变量的值,是在内部函数被调用时查询得到的。
createlist()
被调用后,列表个元素都是匿名函数,匿名函数使用了闭包中的变量i
,而此时i
的值是4,所以对列表个元素求值的结果都是8
所以正确写法是:
def createlist():
# 在匿名函数中定义一个局部变量保存自己需要的值
return [lambda x,i=i: x*i for i in range(5)]
for i in createlist():
print(i(2))
# 或 另一种写法
def createlist():
a=[]
for i in range(5):
def innner(x, i=i):
# 为什么 i=i 定义在函数内会报错???
# 代码块中变量第一次赋值和调用同时出现无法赋值
# i = i
return x*i
a.append(innner)
return a
for i in createlist():
print(i(2))
2. 如下代码:
nums = range(2,20)
for i in nums:
nums = filter(lambda x: x==i or x%i, nums)
print(list(nums))
# 输出:
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
同样按照正常的逻辑结果应该为:[2, 3, 5, 7, 11, 13, 17, 19]
问题产生的原因:
- 在 python3 当中
filter()
函数返回的是一个迭代器,因此并没有做真正的执行,而是在每次调用的时候执行
- python2 中
filter()
返回的值列表,无此现象
- 在遍历后执行打印时,i这个变量使用的是最后调用时的值
正确代码:
nums = range(2,20)
for i in nums:
nums = filter(lambda x,i=i: x==i or x%i, nums)
print(list(nums))
# 输出:
[2, 3, 5, 7, 11, 13, 17, 19]