《Fluent Python》学习笔记系列7
装饰器的基础知识
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。从语法上来讲,函数装饰器就是其后边函数运行时的声明。
在定义函数或方法的def
语句之前的一行,以@
开头,后边跟着是元函数。
如,有名为decorate的装饰器:
class C:
@decorate
def target():
这个语法就等同于:
target = decorate(target)
也就是把函数传递给装饰器,然后再赋值给最初的变量名。
class C:
def target():
...
target = decorate(target)
装饰器执行
装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
执行结果:
running register(<function f1 at 0x000001BC7DF210D0>)
running register(<function f2 at 0x000001BC7DF21158>)
running main()
registry -> [<function f1 at 0x000001BC7DF210D0>, <function f2 at 0x000001BC7DF21158>]
running f1()
running f2()
running f3()
可以看出,在主程序main
执行之前,register
已经执行了两次。调用register
时,传入的参数是被装饰的函数,如f1
和 f2
。 所以函数装饰器在导入模块的时候就已经执行了,而被装饰的函数只在明确调用时运行。这也是导入时与运行时 之间的区别。
在真实代码中,装饰器的用法为:
- 装饰器通常在一个模块中定义,然后应用到其他模块中的函数上。
- 大多数装饰器会在内部定义一个函数,然后将其返回。
变量作用域规则
>>> 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
>>>
注:因为在Python 编译函数的定义体时,它判断 b 是局部变量,因为在函数中给它赋值。生成的字节码证实了这种判断,Python 会尝试从本地环境获取 b 。后面调用 f2(3) 时,f2 的定义体会获取并打印局部变量 a 的值,但是尝试获取局部变量 b 的值时,发现 b 没有绑定值。
这不是缺陷,而是设计选择:Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。
如果在函数中赋值时想让解释器把 b 当成全局变量,要使用 global 声明:
>>> b = 6
>>> def f3(a):
... global b
... print(a)
... print(b)
... b = 9
...
>>> f3(3)
3
6
>>> b
9
>>> f3(3)
3
9
- |
---|
闭包
闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。
实现方法有多种。
首先,可以使用类来实现:
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)
Averager
的实例是可调用对象:
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
来可以使用函数实现:
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
调用 make_averager 时,返回一个 averager 函数对象。每次调用 averager 时,它会把参数添加到系列值中,然后计算当前平均值。
series 是 make_averager 函数的局部变量,因为那个函数的定义体中初始化了series : series = [] 。可是,调用 avg(10) 时, make_averager 函数已经返回了,而它的本地作用域也一去不复返了。在 averager 函数中, series 是自由变量(free variable)。这是一个技术术语,指未在本地作用域中绑定的变量。
nonlocal 声明
nonlocal与global相似,但是它只是作用于嵌套作用域,而且只是作用在函数里面
Python 3 引入了 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
程序中,count,total在函数averager内声明为nonlocal,赋值会修改最近的嵌套函数的本地作用域中的名称。
全局声明将会将变量映射至整个模块。当嵌套函数存在时,嵌套函数中的变量 也许仅仅是引用,但是需要
nonlocal
声明才可可以修改。