欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

闭包与装饰器

程序员文章站 2022-07-12 10:01:08
...

闭包

*变量

闭包指延伸了作用域的函数。其中包含函数定义体中引用、 但是不在定义体中定义的非全局变量。 关键是它能访问定义体之外定义的非全局变量。通过这个特性可以做状态的记录比如对参数做记录。

比如有个用类可调用化实现的函数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
相关标签: python