函数装饰器和闭包(二)

上一章节:函数装饰器和闭包(一)

闭包

在解释闭包之前,我们先来看个例子:

def make_averager():
    series = []

    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total / len(series)

    return averager

我们先分析一下make_averager这个函数,这个函数中有一个名为series的列表,然后函数之中又定义了函数averager,这个函数接收一个值,将值存入函数体之外的series列表中,同时累计这个列表的总和,再除以这个列表的长度,将结果返回,这是个计算平均值的函数

我们测试make_averager这个函数:

>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0  

avg调用了make_averager函数,得到averager这个函数的引用,所以avg是一个可调用对象,我们不断向avg输入一个数值,而这个函数也返回它接收到所有值的平均值

现在,我们解释一下什么是闭包:在一个外函数中定义了一个内函数,内函数里引用了外函数中定义的变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包

在averager函数中,series是自由变量,指的是未在本地作用域中绑定的变量,如下图:

averager的闭包延伸到那个函数的作用域之外,包含自由变量series的绑定

 审查返回的avgrager对象,我们发现Python在__code__属性中保存局部变量和自由变量的名称

>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)

  

avg.__closure__中的各个元素对应于avg.__code__.co_freevars中的一个名称。 这些元素是cell对象,有个cell_contents属性,保存着真正的值

>>> avg.__closure__
(<cell at 0x0000007253A0F468: list object at 0x00000072557A05C8>,)
>>> avg.__closure__[0].cell_contents
[10, 11, 12]

  

综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。注意:只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量

nonlocal声明

前面实现make_averager函数的方法效率并不高,因为我们把历史值都存储在一个列表中,每次都会列表求和再除以列表元素的个数,现在,让我们来看另外一种更高效的方式

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        total += new_value
        count += 1
        return total / count

    return averager

  

运行结果:

>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
……
UnboundLocalError: local variable 'total' referenced before assignment

  

当count是数字或任何不可变类型时,count += 1可以看成count = count + 1。因此,我们在averager的定义中给count赋值了,这会把count看成一个局部变量,同理total,为了解决这个问题,我们可以引入nonlocal声明,它的作用是把变量标记为自由变量,即时在函数中为变量赋予新值,也会变成自由变量

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        nonlocal total
        nonlocal count
        total += new_value
        count += 1
        return total / count

    return averager

  

运行结果:

>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5

  

 

posted @ 2018-07-14 14:39  北洛  阅读(223)  评论(0编辑  收藏  举报