今天看啥  ›  专栏  ›  deniro

说说 Python 中的闭包

deniro  · 掘金  ·  · 2021-02-10 17:35
阅读 29

说说 Python 中的闭包

闭包不好理解,所以先从示例说起。

假设我们需要计算平均值,这些值会从外层传递进来,然后被保存在内部。

(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))
复制代码

运行结果:

  1. 非闭包方式定义了一个类,名为 Averager。然后在初始化方法中为该类定义了一个数组 series,用于保存传入进来的数值。
  2. 接着使用 __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))
复制代码

运行结果与上例相同。

  1. 我们定义了一个 make_averager 函数,其内部又定义了一个名为 averager(new_value) 的函数,里面是计算平均数的算法;
  2. 传入的参数被保存在外层的 series 数组中;
  3. 最后返回这个内部函数。

黄色区域表示产生闭包现象的代码段。其中的 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)
复制代码

运行结果:

其中 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]
复制代码

运行结果:

closure 翻译过来就是闭包。


通过闭包,我们可以保留住定义的自由变量的值。这样函数调用后,我们仍然可以使用这些变量。


  1. Python __call__()方法(详解版).
  2. Luciano Ramalho (作者),安道,吴珂 (译者).流畅的Python[M].人民邮电出版社,2017:312-315.



原文地址:访问原文地址
快照地址: 访问文章快照