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

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

从里到外的搜索

1 a = 1
2 
3 def f():
4     a = 2
5     def b():
6         print(a)
7     b()
8 
9 f()

输出结果

1 2

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

闭包

 1 a = 1
 2 
 3 def f():
 4     a = 2
 5     def b():
 6         print(a)
 7     return b
 8 
 9 b = f()
10 b()

输出结果

1 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),这种绑定的变量是静态变量(类似于为函数添加了属性)。

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

函数计数器

 1 def fn():
 2     print("call fn")
 3 
 4 def count(fn):
 5     i = [0]
 6     def s(*arg):
 7         i[0] += 1
 8         print(f"times: {i[0]}")
 9         fn(*arg)
10     return s
11 
12 fn = count(fn)
13 for _ in range(5):
14     fn()

输出结果

 1 times: 1
 2 call fn
 3 times: 2
 4 call fn
 5 times: 3
 6 call fn
 7 times: 4
 8 call fn
 9 times: 5
10 call fn

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

装饰器

 1 def count(fn):
 2     i = [0]
 3     def s(*arg):
 4         i[0] += 1
 5         print(f"times: {i[0]}")
 6         fn(*arg)
 7     return s
 8 
 9 @count
10 def fn():
11     print("call fn")
12 
13 for _ in range(5):
14     fn()

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

计数功能仍靠闭包实现。

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

总结:

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

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

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

posted @ 2019-06-11 16:20  luoheng  阅读(212)  评论(0编辑  收藏  举报