在python中,一个函数内部定义一个函数,并将内部函数返回,而一般内部函数会调用外部函数的参数和变量,并都将保存起来,即不会随着外部函数返回而释放,这就称为闭包。
我们来一步一步理解闭包
首先举个简单的例子
def createCounter(i):
def counter():
print(i)
return counter
可以看到,外部createCounter函数中定义了内部counter函数,并将函数名return了回去
要理解闭包就必须理解对象,python中都是以对象的形式出现的,或者说是一个引用。执行函数的时候,return counter可以看成返回了内部函数一个引用。而python中在函数名后面加个()即表示调用该函数
上面这个函数,我们如果想打印i这个参数就要这么做
createCounter(123)()
大家可以自己动手试试,你可能会疑惑,有两个括号是什么情况。其实,第一个括号调用了外部函数,返回值为内部对象引用,再加一个括号调用该函数,于是就输出123了
s=createCounter(123)
s()
一般情况下大家可以这么调用,显得更加直观.。可以尝试打印s,即外部函数返回值,显示的是这个一个函数
<function main.createCounter..counter()>
在廖雪峰的python教程中有一个这样的题目
要求用闭包实现一个计数器函数。
由于内部函数不能更改外部函数参数与变量的值。我们有下面两种方法进行操作。
def createCounter():
i=0
def counter():
nonlocal i
i+=1
return i
return counter
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')
第一种我们可以使用nonlocal关键字,nonlocal专门用在函数或者其它作用域总使用外层变量但不是全局变量
第二种我们可以把要修改的数值放到可变的list中去,如下
def createCounter():
i=0
L=[i]
def counter():
L[0]+=1
return L[0]
return counter
最后说明一种容易出错的情况
请看下面的例子
def createCounter():
i=0
def counter():
nonlocal i
i+=1
return i
return counter
s=createCounter()
print(s(),s()) #1 2
这个大家应该都知道输出应该是1,2
但是 如果换成了
def createCounter():
i=0
def counter():
nonlocal i
i+=1
return i
return counter
print(createCounter()(),createCounter()()) #1 1
结果就是1 1 了
为什么呢,前面说过,这个双括号的方式并没有问题,问题在于,两次引用的不是同一个对象,所以结果是独立的,所以最后输出的1 1 了