概要
嵌套函数和自由变量 free variable,在装饰器 decorator 中被广泛使用。
而嵌套函数,nonlocal,闭包 closure 和自由变量,是 4 个紧密相关的概念,因此需要放在一起讨论。
1. 先说结论
嵌套函数和自由变量的 2 种常见使用场景如下:
- 使用自由变量来记录状态量,或是用来登记 registration 一些输入的对象。
这种用法的好处是不需要用 class ,也不需要用全局变量(正如 Google Python Style Guide 的建议:不要用全局变量)。 - 当函数内有部分代码,被重复使用次数大于等于 2 次时,就可以把这部分代码写成一个内部函数,形成嵌套函数。然后调用该嵌套函数即可。并且结合使用自由变量,还可以避免过多的参数传递。
下面详细讨论这 4 个概念。
2. 嵌套函数和 nonlocal
nonlocal 的 2 个特点:
- nonlocal 是指一个区域/范围 scope,这一点和 global, local 一样。
- 只有在嵌套函数中,才会出现 nonlocal 区域。它是处在 global 和 local 的“夹心层”。
在一个 Python 文件内,nonlocal,global 和 local 这 3 个区域的示意图如下。这里使用了嵌套函数。为了方便说明,把被嵌套函数也叫内部/内层函数。
nonlocal 的使用示例如下,是在 Jupyter Lab 中的运行结果。
在用 nonlocal 声明一个变量来自 nonlocal 区域之后,就可以对其进行修改,也就是“将变量和数据进行绑定”的操作。关于什么是“将变量和数据进行绑定”,可以参看我的前一篇博客 《Python 中赋值操作符 = 的作用》https://blog.csdn.net/drin201312/article/details/135759580 。
3. 闭包 closure 和自由变量 free variable
闭包的 2 个重要特征:
- 在嵌套函数的内层函数中(如下图中的 inner),如果使用了 nonlocal 变量,就会形成闭包。被使用的 nonlocal 变量也叫自由变量 free variable 。
返回内层函数时,实际返回的是闭包。也就意味着自由变量也被“隐藏”地返回了
(如下图例子中的自由变量 b)。
用下图例子来说明闭包的特性。
上图中,用 __code__.co_varnames 属性可以查看函数的局部变量。
用 __code__.co_freevars 属性可以查看函数的自由变量。
函数的其它属性,可以参看:https://docs.python.org/3/reference/datamodel.html?highlight=closure#index-58
此外,还有另外 2 种办法可以查看闭包和自由变量。
3.1 可以用 function.__closure__ 查看。如下图。
function.__closure__ 属性返回一个元祖,其中每个元素都是一个 cell,
每个 cell 对应一个自由变量。
用 cell 的 cell_contents 属性,既可以查看闭包中自由变量的值,也可以修改
自由变量。
并且因为 co_freevars 和 __closure__ 是一一对应关系,所以可以用 zip 把
自由变量的名字和值对应起来显示。
3.2 使用 inspect 模块的 getclosurevars 函数进行查看。如下图。
inspect.getclosurevars(func) 返回一个 named tuple ,然后
用 nonlocals 属性,直接查看自由变量的名字和值。
inspect.getclosurevars(func) 返回的 named tuple ,叫做 ClosureVars(nonlocals, globals, builtins, unbound),其中包含了 nonlocals 等 4 类数据。nonlocals 属性实际上是 func 用到的自由变量,没有用到的 nonlocal 并不包含在内。关于 getclosurevars 的更多介绍可参看官网: https://docs.python.org/3/library/inspect.html?highlight=closure#inspect.getclosurevars
4. 使用举例
使用嵌套函数和自由变量的一个示例如下,是一个用于计算累积平均值的函数。
下面主要展示了如何用自由变量,进行登记 registration 和记录状态量。
4.1 用 __closure__ 和 co_freevars 查看上图例子中的 3 个自由变量。
4.2 用 inspect.getclosurevars(func).nonlocals 直接查看自由变量。
5. 变量搜索
最后提一下在 Python 中,如何搜索一个变量的问题。
Python 有 4 个区域 scope。按照从小到大的顺序,这 4 个区域分别是:
1. 函数内部 local 区域。
2. 嵌套函数的 nonlocal 区域。
3. global 全局区。
4. 最外区域 outermost scope。这部分主要是 Python 的 builtins 内置函数,
即 __builtins__.__dict__ 中的内容,包括了 max, abs,None 等。
并且该区域是各个 Python 文件都共享的。
在使用一个变量 foo 时,会按从小到大的区域顺序搜索,如果搜到了该变量,就停止搜索,不再查看更大的区域。
例如在内部 local 区搜到了 foo,就不会搜索 nonlocal 等其它 3 个区域。
6. 参考资料
- Python 官网 https://docs.python.org/3
- 《Fluent Python》 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/
—————————— 本文结束 ——————————