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

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

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

变量作用域规则

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值