嵌套函数,nonlocal,闭包 closure 和自由变量

概要

嵌套函数和自由变量 free variable,在装饰器 decorator 中被广泛使用。
而嵌套函数,nonlocal,闭包 closure 和自由变量,是 4 个紧密相关的概念,因此需要放在一起讨论。


1. 先说结论

嵌套函数和自由变量的 2 种常见使用场景如下:

  1. 使用自由变量来记录状态量,或是用来登记 registration 一些输入的对象。
    这种用法的好处是不需要用 class ,也不需要用全局变量(正如 Google Python Style Guide 的建议:不要用全局变量)。
  2. 当函数内有部分代码,被重复使用次数大于等于 2 次时,就可以把这部分代码写成一个内部函数,形成嵌套函数。然后调用该嵌套函数即可。并且结合使用自由变量,还可以避免过多的参数传递。

下面详细讨论这 4 个概念。


2. 嵌套函数和 nonlocal

nonlocal 的 2 个特点:

  1. nonlocal 是指一个区域/范围 scope,这一点和 global, local 一样。
  2. 只有在嵌套函数中,才会出现 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 个重要特征:

  1. 在嵌套函数的内层函数中(如下图中的 inner),如果使用了 nonlocal 变量,就会形成闭包。被使用的 nonlocal 变量也叫自由变量 free variable 。
  2. 返回内层函数时,实际返回的是闭包。也就意味着自由变量也被“隐藏”地返回了(如下图例子中的自由变量 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, absNone 等。
	并且该区域是各个 Python 文件都共享的。

在使用一个变量 foo 时,会按从小到大的区域顺序搜索,如果搜到了该变量,就停止搜索,不再查看更大的区域。
例如在内部 local 区搜到了 foo,就不会搜索 nonlocal 等其它 3 个区域。


6. 参考资料

  1. Python 官网 https://docs.python.org/3
  2. 《Fluent Python》 https://www.oreilly.com/library/view/fluent-python-2nd/9781492056348/

—————————— 本文结束 ——————————

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值