闭包不好理解,所以先从示例说起。
假设我们需要计算平均值,这些值会从外层传递进来,然后被保存在内部。
(1) 非闭包方式实现
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)
avg = Averager()
logging.info('avg(10) -> %s', avg(10))
logging.info('avg(20) -> %s', avg(20))
logging.info('avg(30) -> %s', avg(30))
运行结果:
![](https://img.haomeiwen.com/i3386108/21e585806a7b1c8f.png)
- 非闭包方式定义了一个类,名为 Averager。然后在初始化方法中为该类定义了一个数组 series,用于保存传入进来的数值。
- 接着使用
__call__
使得该类实例对象可以像调用普通函数那样,以“对象名()”的形式被使用1。它接收一个参数作为需要计算的新数值,内部被保存在 series 数组中。
(2) 闭包方式实现
这个平均数计算方式可以采用函数式编程方式来实现。
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager
avg = make_averager()
logging.info('avg(10) -> %s', avg(10))
logging.info('avg(20) -> %s', avg(20))
logging.info('avg(30) -> %s', avg(30))
运行结果与上例相同。
- 我们定义了一个 make_averager 函数,其内部又定义了一个名为 averager(new_value) 的函数,里面是计算平均数的算法;
- 传入的参数被保存在外层的 series 数组中;
- 最后返回这个内部函数。
![](https://img.haomeiwen.com/i3386108/48b5bd2f0a1643b4.png)
黄色区域表示产生闭包现象的代码段。其中的 series 数组是自由变量(free variable),不受内部函数 averager 的影响,所以可以保存所有传入的变量值。
内部函数 averager 的局部作用域内的变量,都会在函数被调用后失效。
通过 __code__
属性可以看到 avg 函数中的变量名称。__code__
属性是编译后的函数定义体。
logging.info('avg.__code__.co_varnames -> %s', avg.__code__.co_varnames)
logging.info('avg.__code__.co_freevars -> %s', avg.__code__.co_freevars)
运行结果:
![](https://img.haomeiwen.com/i3386108/27b516bd5ebe67ec.png)
其中 co_varnames 表示局部变量;co_freevars 表示自由变量。这与我们之前所描述的闭包场景一致。
闭包中的自由变量值保存在 avg 函数的 __closure__
属性中。它是一组 cell 对象列表,每个 cell 对象与 co_freevars 列表中的名称一一对应:
INFO - avg.__closure__ -> (<cell at 0x000002A8AF736D38: list object at 0x000002A8AF9C7DC8>,)
INFO - avg.__closure__[0].cell_contents -> [10, 20, 30]
运行结果:
![](https://img.haomeiwen.com/i3386108/c2df7b3012ce7757.png)
closure 翻译过来就是闭包。
通过闭包,我们可以保留住定义的自由变量的值。这样函数调用后,我们仍然可以使用这些变量。
-
Python
__call__()
方法(详解版). - Luciano Ramalho (作者),安道,吴珂 (译者).流畅的Python[M].人民邮电出版社,2017:312-315.
网友评论