闭包与装饰器
标题
闭包
*变量
闭包指延伸了作用域的函数。其中包含函数定义体中引用、 但是不在定义体中定义的非全局变量。 关键是它能访问定义体之外定义的非全局变量。通过这个特性可以做状态的记录比如对参数做记录。
比如有个用类可调用化实现的函数avg()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
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)
实际上可以使用闭包实现
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
Averager 类的实例 avg 在哪里存储历史值很明显: self.series 实例属性。 但是第二个示例中的 avg 函数在哪里寻找 series 呢?
series 是 make_averager 函数的局部变量, 因为那个函数的定义体中初始化了 series: series = []。 可是, 调用 avg(10)时, make_averager 函数已经返回了, 而它的本地作用域也一去不复返了。
在 averager 函数中, series 是*变量(free variable) 。 这是一个技术术语, 指未在本地作用域中绑定的变量。
averager 的闭包延伸到那个函数的作用域之外, 包含*变量 series 的绑定
在__code__ 属性中保存局部变量和*变量的名称,series 的绑定在返回的 avg 函数的 closure 属性,avg.closure 中的各个元素对应于avg.code.co_freevars 中的一个名称。
>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)
>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__
(<cell at 0x107a44f78: list object at 0x107a91a48>,)
>>> avg.__closure__[0].cell_contents
[10, 11, 12]
闭包是一种函数, 它会保留定义函数时存在的*变量的绑定,这样调用函数时, 虽然定义作用域不可用了, 但是仍能使用那些绑定。
变量作用域规则
>>> b = 6
>>> def f2(a):
... print(a)
... print(b)
... b = 9
...
>>> f2(3)
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment
类似于上面代码,虽然先定义了b为全局变量,但是在f2函数时b给赋值了,这时b变成了局部变量,就算在是在b给赋值代码前的print(b),此时也把b当作局部变量,但是还未赋值所以出错了。
这是因为Python 编译函数的定义体时, 它判断 b 是局部变量, 因为在函数中给它赋值了。 生成的字节码证实了这种判断, Python 会尝试从本地环境获取 b。 后面调用 f2(3) 时, f2 的定义体会获取并打印局部变量 a 的值, 但是尝试获取局部变量 b 的值时, 发现 b 没有绑定值。
可以把 b 当成全局变量, 要使用global 声明:
>>> b = 6
>>> def f3(a):
... global b
... print(a)
... print(b)
... b = 9
...
>>> f3(3)
36>
>> b
其实举这个例子也没有什么实际意义,只要把print放在赋值之后就可以了。主要还是想引出下面的内容。
nonlocal声明
如果把前面make_averager 函数中存储的series列表*变量换成不可变类型,例如字符串,数字元组等,会出现什么问题吗。
def make_averager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'count' referenced before assignment
>>>
此时count是不可变类型时,不可以对其像列表进行改动更新,只能读取,只能重新绑定赋值,这时就会引起与前面谈到的变量作用域规则相关的错误count与total都变成了局部变量,可以使用nonlocal把这两个变量标记为*变量。
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
上一篇: 闭包与装饰器简单概括
下一篇: 硬件设计16之什么是DCDC与LDO