函数闭包及原理
| 7 min read
不妨先看一下普通函数对局部变量的状态管理是怎么样的:
def func(): sum = 0 sum += 1 print(sum) func() # 1# 调用结束 sum被回收
func() # 1func() # 1进入函数入口 def func() 首先会在栈上创建func的栈帧,并将初始化sum变量使其指向整数0,此时sum的引用次数为1。当执行完 print(sum) 后,函数执行完毕,调用结束,对sum的引用减一并自动收回该变量,sum只在func执行的过程中存在,当我们调用新的 func 时重新初始化sum为0。
上述是普通函数对变量的管理方式:函数销毁,其栈帧上存活的局部变量跟着一起消失。那么如果我们想在外层函数结束时,仍保持函数内部变量的持久化该怎么实现呢?这就引出了函数闭包的概念。
函数闭包(Closure)通过在一个函数内部定义一个嵌套函数,并在内部函数中使用外部函数的局部变量。即使外部函数执行完毕,这时它的内部局部变量按理会消失,但因为内部函数还在引用该外部变量,在外部函数中定义的局部变量并不会被GC回收,Python,JS等语言为这个内部函数持续保留这个外部变量引用的结构就是闭包。
代码示例如下:
def outer_func(): count = 0
def inner_func(x): # 内部函数引用外部变量 nonlocal count count += x
return count
return inner_func
outer_func_01 = outer_func() """此时outer_func返回,栈帧消失,内层的inner_func赋给outer_func01inner_func继续存活且持续引用外部的局部变量count"""print(outer_func_01(5)) # 5 print(outer_func_01(4)) # 9可以看到,当执行完 outer_func_01 = outer_func()语句时,其内部的count变量并没有被释放,在接下来的两条打印语句中,实现了count变量的持久化,并根据x的值累加到了9。
而普通函数中无法实现这种变量持久化的操作,函数销毁时,栈帧中的变量也销毁了,也就无从谈起变量持久化操作。
发生了什么
在闭包 函数中,当外部变量被内部函数引用时,会被包装成Cell对象。Cell对象的 cell_contents 指向堆上的整数变量0。outer_func栈帧上的x指向在堆上分配的Cell对象,内部函数inner_func的count变量也指向该Cell对象。
即使外部函数的栈帧被销毁,对Cell对象的引用减1,而因为内部函数仍引用着count的值,Cell仍存活在堆上。
通过 outer_fun_01.__closure__ 我们可以查看在 inner_func对象中创建的Cell对象,即被闭包函数所引用的变量:
def outer_func(): count = 0 sum = 1.0
def inner_add_func(): nonlocal count # 对count的引用 nonlocal sum count += 1 sum += count
return sum
return inner_add_func
outer_fun_01 = outer_func() # 此时外层函数已经结束,并将内部inner_add_func作为返回值赋给变量outer_fun_01print(outer_func.__closure__) # Noneprint(outer_fun_01.__closure__) # 内部inner_add_func
"""(<cell at 0x00000133BE48FCD0: int object at 0x00007FF9F6B85308>, <cell at 0x00000133BE48FC40: float object at 0x00000133BE3BA950>)"""
print(outer_fun_01.__closure__[0].cell_contents) # 0可以看到,结果返回了一个元组:outer_func.__closure__为None,不是闭包函数,该对象不含Cell对象的引用。outer_fun_01 即为返回的内部函数 inner_func,打印 outer_fun_01.__closure__可以看到该函数引用了两个Cell对象,并给出了他们的地址。这两个Cell又分别引用了整数count和浮点数sum。
装饰器
python的装饰器底层就是闭包函数,而我们常见的 @wrapper 的形式是封装的语法糖。
def wrapper(func): def inner(*args, **kwargs): print("this will display before func") print(func(*args, **kwargs)) print("this will display after func executed")
return inner
def func(a, b): print("func is executing") return a + b
func = wrapper(func)func(1, 2)
"""this will display before funcfunc is executing3this will display after func executed"""使用python的装饰器语法糖我们无需通过 func = wrapper(func) 手动创建wrapper对象,而是可以简洁地调用 func 的函数实现同样的效果:
def wrapper(func): def inner(*args, **kwargs): print("this will display before func") print(func(*args, **kwargs)) print("this will display after func executed")
return inner
@wrapperdef func(a, b): print("func is executing") return a + b
func(1, 2)
"""this will display before funcfunc is executing3this will display after func executed"""