Python 函数装饰器和闭包(闭包)

本章内容:

Python 如何计算装饰器句法

Python 如何判断变量是不是局部的

闭包存在的原因和工作原理

nonlocal 能解决什么问题

掌握这些基础知识后,我们可以进一步探讨装饰器:

实现行为良好的装饰器

标准库中有用的装饰器

实现一个参数化装饰器

闭包

在博客圈,人们有时会把闭包和匿名函数弄混。这是有历史原因的:在

函数内部定义函数不常见,直到开始使用匿名函数才会这样做。而且,

只有涉及嵌套函数时才有闭包问题。因此,很多人是同时知道这两个概

念的。

其实,闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是

不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是

它能访问定义体之外定义的非全局变量。

这个概念难以掌握,最好通过示例理解。

假如有个名为 avg 的函数,它的作用是计算不断增加的系列值的均值;

例如,整个历史中某个商品的平均收盘价。每天都会增加新价格,因此

平均值要考虑至目前为止所有的价格。

起初,avg 是这样使用的:

python 复制代码
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

avg 从何而来,它又在哪里保存历史值呢?

初学者可能会像示例 7-8 那样使用类实现。

示例 7-8 average_oo.py:计算移动平均值的类

python 复制代码
class Averager():
  def __init__(self):
    self.series = []
  def __call__(self, new_value):
    self.series.append(new_value)
    total = sum(self.series)
  return total/len(self.series)

Averager 的实例是可调用对象:

python 复制代码
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

示例 7-9 是函数式实现,使用高阶函数 make_averager。

示例 7-9 average.py:计算移动平均值的高阶函数

python 复制代码
def make_averager():
  series = []
  def averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total/len(series)
return averager

调用 make_averager 时,返回一个 averager 函数对象。每次调用

averager 时,它会把参数添加到系列值中,然后计算当前平均值,如

示例 7-10 所示。

示例 7-10 测试示例 7-9

python 复制代码
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

注意,这两个示例有共通之处:调用 Averager() 或

make_averager() 得到一个可调用对象 avg,它会更新历史值,然后

计算当前均值。在示例 7-8 中,avg 是 Averager 的实例;在示例 7-9

中是内部函数 averager。不管怎样,我们都只需调用 avg(n),把 n

放入系列值中,然后重新计算均值。

Averager 类的实例 avg 在哪里存储历史值很明显:self.series 实例

属性。但是第二个示例中的 avg 函数在哪里寻找 series 呢?

注意,series 是 make_averager 函数的局部变量,因为那个函数的定

义体中初始化了 series:series = []。可是,调用 avg(10)

时,make_averager 函数已经返回了,而它的本地作用域也一去不复

返了。

在 averager 函数中,series 是自由变量(free variable)。这是一个

技术术语,指未在本地作用域中绑定的变量,参见图 7-1。

图 7-1:averager 的闭包延伸到那个函数的作用域之外,包含自由

变量 series 的绑定审查返回的 averager 对象,我们发现 Python 在 code 属性(表示

编译后的函数定义体)中保存局部变量和自由变量的名称,如示例 7-11所示。

示例 7-11 审查 make_averager(见示例 7-9)创建的函数

python 复制代码
>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)

series 的绑定在返回的 avg 函数的 closure 属性

中。avg.closure 中的各个元素对应于

avg.code .co_freevars 中的一个名称。这些元素是 cell 对象,

有个 cell_contents 属性,保存着真正的值。这些属性的值如示例 7-

12 所示。示例 7-12 接续示例 7-11

python 复制代码
>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__
(<cell at 0x107a44f78: list object at 0x107a91a48>,)
>>> avg.__closure__[0].cell_contents
[10, 11, 12]

综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,

这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中

的外部变量。

相关推荐
掘金-我是哪吒10 分钟前
分布式微服务系统架构第156集:JavaPlus技术文档平台日更-Java线程池使用指南
java·分布式·微服务·云原生·架构
Me4神秘25 分钟前
电信、移动、联通、广电跨运营商网速慢原因
网络
亲爱的非洲野猪36 分钟前
Kafka消息积压的多维度解决方案:超越简单扩容的完整策略
java·分布式·中间件·kafka
wfsm38 分钟前
spring事件使用
java·后端·spring
微风粼粼1 小时前
程序员在线接单
java·jvm·后端·python·eclipse·tomcat·dubbo
缘来是庄1 小时前
设计模式之中介者模式
java·设计模式·中介者模式
云天徽上1 小时前
【PaddleOCR】OCR表格识别数据集介绍,包含PubTabNet、好未来表格识别、WTW中文场景表格等数据,持续更新中......
python·ocr·文字识别·表格识别·paddleocr·pp-ocrv5
rebel1 小时前
若依框架整合 CXF 实现 WebService 改造流程(后端)
java·后端
你怎么知道我是队长2 小时前
python-input内置函数
开发语言·python
数通Dinner2 小时前
RSTP 拓扑收敛机制
网络·网络协议·tcp/ip·算法·信息与通信