python tips:最内嵌套作用域规则,闭包与装饰器

时间:2022-03-19 04:48:53

作用域与名字空间提到,python是静态作用域,变量定义的位置决定了变量作用的范围。变量沿着local,global,builtins的路径搜索,直觉上就是从里到外搜索变量,这称为最内嵌套作用域规则。

从里到外的搜索

 a = 1

 def f():
a = 2
def b():
print(a)
b() f()

输出结果

 2

最内嵌套作用域规则有一个神奇的特性,它对local变量的搜索只依赖于静态代码的组成,而与代码如何调用没有关系。

闭包

 a = 1

 def f():
a = 2
def b():
print(a)
return b b = f()
b()

输出结果

 2

函数f返回函数b,在第九行调用f得到函数b,此时函数f调用完成,应该被销毁,它包含的局部变量a也应该随之销毁。所以调用函数b时应该得不到函数f的变量a才对,事实却相反,函数b打印出了本应被销毁的a变量。

局部作用域是静态实现的,跟代码如何调用,何时调用没有关系。对于局部变量a来说,它的作用域是函数f内,当然包含了函数b。而且在函数b中确实引用了a,为了让b在任何时候都能够得到它,a会将自己绑定到函数b上。

这样b得到了一个额外的变量a,在函数f被销毁后,仍然能够使用变量a。这种变量与函数绑定的结果称为闭包(closure),这种绑定的变量是静态变量(类似于为函数添加了属性)。

直观上,闭包的作用就是为函数添加静态变量。

函数计数器

 def fn():
print("call fn") def count(fn):
i = [0]
def s(*arg):
i[0] += 1
print(f"times: {i[0]}")
fn(*arg)
return s fn = count(fn)
for _ in range(5):
fn()

输出结果

 times: 1
call fn
times: 2
call fn
times: 3
call fn
times: 4
call fn
times: 5
call fn

有了闭包,不用传入额外的参数,函数自身就能记住状态的变化(闭包提供静态变量)。

装饰器

 def count(fn):
i = [0]
def s(*arg):
i[0] += 1
print(f"times: {i[0]}")
fn(*arg)
return s @count
def fn():
print("call fn") for _ in range(5):
fn()

装饰器其实就是一个语法糖,fn上方加@count,等价于fn = count(fn)。

计数功能仍靠闭包实现。

@count必须出现在count定义函数之后,否则无法识别。

总结:

1. 最内嵌套作用域对于local变量的搜索只与代码组成有关,与动态运行环境无关

2. 闭包是外层变量与内层函数的绑定结果,主要作用是位函数添加静态变量

3. 装饰器是语法糖,装饰函数一般都返回一个闭包。