前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 的闭包特性

python 的闭包特性

作者头像
用户3147702
发布2022-06-27 13:26:38
4910
发布2022-06-27 13:26:38
举报
文章被收录于专栏:小脑斧科技博客

1. 引言

此前,我们在介绍 java8 新增的 lambda 表达式时,曾经介绍过“闭包”的概念。

所谓的“闭包”,指的就是可以包含自由变量的代码块,代码块中包含的自由变量并没有在定义时绑定任何对象,他们也不是在这个代码块内或任何全局上下文中定义的,而是在代码块环境中定义的局部变量。 简单的来说,闭包是一个独立的代码块,但是他可以访问其定义体之外的非全局变量。 很多语言通过匿名函数来实现闭包特性,著名的 lambda 表达式就是一个典型的闭包的例子。 python 对闭包有着很好的支持。

2. 闭包实例 — 求解平均数

假设我们有一个方法,每次调用都输出历史所有调用传入参数的总平均数:

代码语言:javascript
复制
>>> avg(10)
10
>>> avg(11)
10.5
>>> avg(39)
20

我们如何来实现呢?下面就是一个闭包的例子:

代码语言:javascript
复制
>>> def make_average():
...     series = []
...     def avg(value):
...         series.append(value)
...         return sum(series)/len(series)
...     return avg
...
>>> avg = make_average()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(39)
20.0

在 avg = make_average() 语句执行完成后,make_average 方法的栈空间就会被销毁,而在其栈空间内定义的 series 变量应该会随着 make_average 方法执行的结束而被销毁。 但令人意外的是,此后 avg 方法的执行并没有出错,其内部对 series 列表的添加并没有报错,那么 series 变量究竟定义在哪里呢? 此前我们介绍过 python 的作用域,其中提到了 Enclosing 作用域(嵌套函数的外层函数内部) — 嵌套作用域(闭包) python 的名称空间与作用域

当 python 解释器看到嵌套函数内部使用了外部该局部变量时,解释器会将其标记为自由变量,从而不会随着局部作用域一起被销毁。

3. python 闭包可能存在的问题 — nonlocal 关键字

上面的例子我们进一步修改:

代码语言:javascript
复制
>>> def make_average():
...     count = total = 0
...     def avg(value):
...         count += 1
...         total += value
...         return total / count
...     return avg
...
>>> avg = make_average()
>>> avg(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in avg
UnboundLocalError: local variable 'count' referenced before assignment

看上去这个例子与上面闭包的例子没什么区别,他将 series 变量改为了保存总和与调用次数的两个变量,但是却在调用时报错,因为外部 count 与 total 随着 make_average 方法的调用结束而被销毁了,这又是为什么呢? 当解释器看到在嵌套内部的 avg 函数中,对 count 与 total 两个变量均有赋值行为,于是他们被当做了 avg 方法局部作用域中的变量,而不是自由变量,于是外部的两个局部变量就被正常销毁了。 python3 引入了 nonlocal 关键字,用于解决这样的问题:

代码语言:javascript
复制
>>> def make_average():
...     count = total = 0
...     def avg(value):
...         nonlocal count, total
...         count += 1
...         total += value
...         return total / count
...     return avg
...
>>> avg = make_average()
>>> avg(10)
10
>>> avg(11)
10.5
>>> avg(39)
20

4. 通过可调用对象实现上述问题

代码语言:javascript
复制
>>> class Average:
...     def __init__(self):
...         self.series = []
...     def __call__(self, value):
...         self.series.append(value)
...         return sum(self.series)/len(self.series)
...
>>> avg = Average()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(39)
20.0

这个例子通过类实现了对历史调用信息的封装,并通过 __call__ 方法实现了计算逻辑。 通常来说,闭包能够实现的功能都可以通过类的方式来实现,类也是通常最容易想到的解决方案,那么,闭包的优势又体现在哪里呢? 在 python 中,闭包最重要的使用方式是在装饰器中,那么,装饰器究竟是什么?闭包与装饰器结合又能碰撞出什么样的火花呢? 我们即将会有一篇文章详尽介绍装饰器的用法与原理,敬请期待。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 闭包实例 — 求解平均数
  • 3. python 闭包可能存在的问题 — nonlocal 关键字
  • 4. 通过可调用对象实现上述问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档