《Fluent Python》学习笔记 chpter7函数装饰器

来源:互联网 发布:网络平面设计课程 编辑:程序博客网 时间:2024/05/16 12:16

《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()')@registerdef 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时,传入的参数是被装饰的函数,如f1f2 。 所以函数装饰器在导入模块的时候就已经执行了,而被装饰的函数只在明确调用时运行。这也是导入时与运行时 之间的区别。
在真实代码中,装饰器的用法为:

  • 装饰器通常在一个模块中定义,然后应用到其他模块中的函数上。
  • 大多数装饰器会在内部定义一个函数,然后将其返回。

变量作用域规则

>>> b = 6>>> def f2(a):...     print(a)...     print(b)...     b = 9...>>> f2(3)3Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 3, in f2UnboundLocalError: 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)36>>> b9>>> f3(3)39
-

闭包

闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。
实现方法有多种。
首先,可以使用类来实现:

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)。这是一个技术术语,指未在本地作用域中绑定的变量。

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

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声明才可可以修改。