最近啃源码,发现了自己对Python中的闭包的理解、应用还是有些欠成熟,就整理一下
就我们日常需要而言,基本很难能从函数的外部获取函数内部局部变量的值,这是由变量的作用域等因素决定,但我们还是时不时有这个需求的,如下代码示例:
def func1():
x = 100
我们如果在函数外部执行print(x)
一定会报错。但如果写成下面这样:
def func1():
x = 100
def func2():
print(x)
return func2
这样,函数func2
既可以访问到函数func1
的局部变量,同时我们注意到,func1
函数返回的是fund2
,即返回的是一个函数。
这个小例子,我们基本可以得出以下这几个小结论:
- 函数
func2
包括在函数func1
内部,func1
内部的所有局部变量,对func2
都是可见的。 - 父对象的所有变量,对子对象都是可见的,反之则不成立。即子对象的变量,父亲对象是看不到也访问不了的。
将上面代码完善,即为:
def func1():
x = 100
def func2():
print(x)
return func2
ans = func1()
ans()
那么对应闭包的概念,有一个外层函数的局部变量 x,有一个内层函数func2
,里面可以访问到 n 变量,那这func2
函数就是一个闭包。
对应wiki的定义为:
在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
概括一下出闭包的作用:
- 嵌套定义函数,子函数能读取外部函数内的变量。闭包实际是个函数
- 闭包能将外层函数的变量持久地保存在内存中。
作用1:读取外部函数的内部变量
# 求和,但不想立刻得出求和结果
def f1():
sum = 0
def f2():
x = [1,2,3,4,5]
nonlocal sum # 先忽略这行
for i in x:
sum+=i
return sum
return f2
A = f1() #结果作为函数存到了A中
A() # 只有调用A时,才得出结果
函数内部的局部变量始终保持在内存中
一般来说,函数内部的局部变量在这个函数运行完以后,就会被Python的垃圾回收机制从内存中清除掉。如果我们希望这个局部变量能够长久的保存在内存中,那么就可以用闭包来实现这个功能。
def inc():
x = 0
def fn():
nonlocal x
x = x + 1
return x
return fn
f = inc()
# 会实现变量值累计的效果
print(f()) # 1
print(f()) # 2
再引用一个不错的例子
以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。 这里需要说明的是,每次运动的起点都是上次运动结束的终点。
def create(pos=None):
if pos is None:
pos = [0,0]
def go(direction, step):
new_x = pos[0]+direction[0]*step
new_y = pos[1]+direction[1]*step
pos[0] = new_x
pos[1] = new_y
return pos
return go
player = create()
print(player([1,0],10))
print(player([0,1],20))
print(player([-1,0],10))
在这段代码中,player实际上就是闭包go函数的一个实例对象。
它一共运行了三次,第一次是沿X轴前进了10来到[10,0],第二次是沿Y轴前进了20来到 [10, 20],,第三次是反方向沿X轴退了10来到[0, 20]。
这证明了,函数create中的局部变量pos一直保存在内存中,并没有在create调用后被自动清除。
为什么会这样呢?原因就在于create是go的父函数,而go被赋给了一个全局变量(player),这导致go始终在内存中,而go的存在依赖于create,因此create也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这个时候,闭包使得函数的实例对象的内部变量,变得很像一个类的实例对象的属性,可以一直保存在内存中,并不断的对其进行运算。
总结特点
闭包具备的特点,其实完全可以用“类”,“对象”实现。
但理解闭包,对使用装饰器decorator
很有帮助
后面会再去写一下对解释器的理解。
参考鸣谢
https://zhuanlan.zhihu.com/p/453787908
https://zhuanlan.zhihu.com/p/59968665
https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584