python 闭包
对闭包的具体定义有很多种说法,其中掺杂了很多误解。我收集了一些较准确的资料,做以下整理。
什么是闭包
维基百科对闭包给出两种解释:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
归纳一下,闭包有两个关键词:函数和自由变量。关于闭包定义争议的焦点无非是“引用了自用变量的函数”或者“函数和引用环境(包括自由变量)组成的实体”。这个问题我认为IBM文档库解释的比较恰当。
闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
为什么使用闭包
我们编程时经常会把通用的东西抽象成类,用类封装方便重用。闭包可以理解为函数粒度上的封装。所有,这很像修饰器的作用。修饰器的实现就是闭包。
闭包举例
如果现在还有疑惑,看下面的例子或许会有帮助。
例1,
def addx(x):
def addy(y):
return x + y
return addy
add8 = addx(8)
add4 = addx(4)
print add8(3) #11
print add4(3) #7
在这个例子中,x是自由变量,当函数addx执行完后,自由变量已经脱离了创造环境,但是依然存在。
再看运行结果,add8和add4都是addx返回,由于配置参数不同(8, 4)导致最后在相同传参(3)时,返回结果不同。
例2,
def hellocounter(name):
count = [0]
def counter():
count[0] += 1
print 'Hello,', name, ', ', str(count[0]) + ' access!'
return counter
hello = hellocounter('Mr.Y')
hello()#Hello, Mr.Y , 1 access!
hello()#Hello, Mr.Y , 2 access!
hello()#Hello, Mr.Y , 3 access!
这个例子里,name是自由变量。和例1很相似,需要注意的地方是count = [0],而不是直接count = 0,如果不用列表的话,会报这样一个错误:
UnboundLocalError: local variable 'count' referenced before assignment.
count这个变量你没有定义就直接引用了,抛出异常。这是python2的一个bug,在python3中得到解决,引入了一个关键字:nonlocal,跟字面意思一样,告诉python解释器count变量是在外部定义的,解释器就去外层函数找,就能找到count=0这个声明和赋值。
例3
def fun():
return [lambda x: x * i for i in range(4)
for f in fun():
print f(2)
#期望输出
0
2
3
6
#实际输出
6
6
6
6
这个例子做为经常被当做python陷阱,其实是python闭包的特性:晚闭包。闭包中用到的变量的值,在内部函数被调用时再查询。也就是查询时,循环完成,i 的值是3。
需要注意,这里的结果跟lambda无关,即使是普通函数同样存在晚闭包。
总结
闭包和修饰器被认为是python程序员编程能力进阶的标识。