闭包不好了解,所以先从示例说起。
假设我们需要计算平均值,这些值会从外层传递进来,而后被保存在内部。
(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))
运行结果:
__call__
使得该类实例对象可以像调用普通函数那样,以“对象名()”的形式被使用1。它接收一个参数作为需要计算的新数值,内部被保存在 series 数组中。(2) 闭包方式实现
这个平均数计算方式可以采用函数式编程方式来实现。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total / len(series) return averageravg = make_averager()logging.info('avg(10) -> %s', avg(10))logging.info('avg(20) -> %s', avg(20))logging.info('avg(30) -> %s', avg(30))
运行结果与上例相同。
黄色区域表示产生闭包现象的代码段。其中的 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 翻译过来就是闭包。
通过闭包,我们可以保留住定义的自由变量的值。这样函数调用后,我们依然可以使用这些变量。
__call__()
方法(详解版).