《Fluent Python》读书笔记-Function Decorators and Closures

概览

    本章主要介绍装饰器和闭包(closure)。函数装饰器允许我们在源码上对函数进行标记,以增强函数的功能。要想能充分掌握装饰器,必须先理解闭包。

Decorators 101

    装饰器是一种把另外一个函数作为参数的可调用对象。装饰器可能在被装饰的函数上做一些处理,然后返回这个函数或者其他的函数或可调用对象。
    装饰器的效果可以用下面这个例子来展示:

@decorate
def target():
    print('running target()')

    等价于:

def target():
    print('running target()')
target = decorate(target)

    严格来讲装饰器只是一种语法糖,你可以像常规的调用一个可调用对象一样去使用装饰器,把另外一个函数作为参数。
    总结一下装饰器有两个很重要的特性:

  • 能够把被装饰的函数替换为另一个
  • 装饰器是在模块加载的时候就立即执行的

When Python Executes Decorators

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()

main()
running register()
running register()
running main()
registry -> [, ]
running f1()
running f2()
running f3()

    从上面的例子可以看到装饰器是在模块加载的时候就运行。

Variable Scope Rules

>>> def f1(a):
...     print(a)
...     print(b)
... 
>>> f1(3)
3
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in f1
NameError: name 'b' is not defined
>>> b = 6
>>> f1(3)
3
6
>>> b=6
>>> def f2(a):
...     print(a)
...     print(b)
...     b = 9
... 
>>> f2(3)
3
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment
>>> def f3(a):
...     global b
...     print(a)
...     print(b)
...     b = 9
... 
>>> f3(3)
3
6
>>> b
9

    这是python的一个设计抉择:python不需要声明变量,但是python认为在一个函数里赋值的变量是一个局部变量。

Closures

    闭包是一个包含扩展了可见范围的非全局变量的函数,这些变量在函数中引用,但是不在函数中定义。闭包的关键在于是否能访问定义在函数体外的非全局变量。

>>> 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)
11.0

    可以看到seriesmake_averager中的局部变量。当调用avg(10)的时候,make_averager已经返回,并且他的局部可见范围已经消失。对于averagerseries是一个自由变量(free variable,没有绑定到局部可见范围的变量)。

>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__
(,)
>>> avg.__closure__[0].cell_contents
[10, 11, 12]

    前面的series是一个可变对象,如果是一个不可变对象,进行赋值操作会使得变量变成一个局部变量,就会出错。python3提供了一个nonlocal关键字,可以把一个变量变为一个自由变量。

>>> 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):
  File "", line 1, in 
  File "", line 5, in averager
UnboundLocalError: local variable 'count' referenced before assignment
>>> def make_averager():
...     count = 0
...     total = 0
... 
...     def averager(new_value):
...         nonlocal count, total
...         count += 1
...         total += new_value
...         return total / count
... 
...     return averager
... 
>>> avg = make_averager()
>>> avg(10)
10.0

简单装饰器

import time


def clock(func):
    def clocked(*args): #
        t0 = time.perf_counter()
        result = func(*args) #
        elapsed = time.perf_counter() - t0
        name = func.__name__
        arg_str = ', '.join(repr(arg) for arg in args)
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
        return result

    return clocked


@clock
def snooze(seconds):
    time.sleep(seconds)


@clock
def factorial(n):
    return 1 if n < 2 else n*factorial(n-1)


if __name__=='__main__':
    print('*' * 40, 'Calling snooze(.123)')
    snooze(.123)
    print('*' * 40, 'Calling factorial(6)')
    print('6! =', factorial(6))
    print(factorial.__name__)

**************************************** Calling snooze(.123)
[0.12481301s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000064s] factorial(1) -> 1
[0.00001838s] factorial(2) -> 2
[0.00003116s] factorial(3) -> 6
[0.00004362s] factorial(4) -> 24
[0.00005653s] factorial(5) -> 120
[0.00007111s] factorial(6) -> 720
6! = 720
clocked

    这个是装饰器的一个典型的模式,用一个新的函数替换被装饰的函数,这个函数和被装饰的函数的参数相同,并且返回的内容也相同。从最后的打印可以看到factorial实际上持有的是到clocked函数的引用。

标准库里的装饰器

  • functools.wraps:把被装饰函数的一些属性拷贝到装饰器函数,如name
  • functools.lru_cache:可以对函数调用的结果进行缓存
  • functools.singledispatch:将一个函数变为泛型函数,可以根据第一个参数的不同类型去做不同的操作

带参数的装饰器

import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'


def clock(fmt=DEFAULT_FMT):
    def decorate(func):
        def clocked(*_args):
            t0 = time.time()
            _result = func(*_args)
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args)
            result = repr(_result)
            print(fmt.format(**locals()))
            return _result
        return clocked
    return decorate


if __name__ == '__main__':
    @clock()
    def snooze(seconds):
        time.sleep(seconds)

    for i in range(3):
        snooze(.123)

    print(clock)
    print(clock())
    print(clock()(snooze))

[0.12632895s] snooze(0.123) -> None
[0.12463617s] snooze(0.123) -> None
[0.12320995s] snooze(0.123) -> None

.decorate at 0x1075b6c80>
.decorate..clocked at 0x1075f5268>

你可能感兴趣的:(《Fluent Python》读书笔记-Function Decorators and Closures)