装饰器基础知识

  - 装饰器是可调用对象,其参数是另一个函数

  - 装饰器会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象

def deco(func):
    def inner():
        print("inner is called")
    return inner

@deco
def func():
    print("func is called")

# func被装饰器修改成inner
func()

print(func)

输出:

inner is called
<function deco.<locals>.inner at 0x00000155417B9F28>

Python何时执行装饰器

  - 装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。

  - 即在模块导入初始化时运行

registry = []

def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func

@register
def func1():
    print('func1 is running')

@register
def func2():
    print('func2 is running')

def func3():
    print('func3 is running')

def main():
    # 在main()运行之前,register已经运行了两次
    # 这两次就是在被装饰函数定义时
    # 所以registry也已经被初始化
    print('main is running')
    print('registry:',registry)
    func1()
    func2()
    func3()

if __name__ == '__main__':
    main()

输出:

running register(<function func1 at 0x000001BAE35B9EA0>)
running register(<function func2 at 0x000001BAE35B9F28>)
main is running
registry: [<function func1 at 0x000001BAE35B9EA0>, <function func2 at 0x000001BAE35B9F28>]
func1 is running
func2 is running
func3 is running

变量作用域规则

  - Python不要求声明变量,但是假定在函数定义体中有赋值的变量是局部变量

b = 3

def f1(a):
    print(a)
    # 这里b是局部变量,所以错误提示为在赋值之前使用
    # 如果想获取全局变量b,需要加上global b
    print(b)
    b = 9

f1(1)

闭包

  - 只有涉及嵌套函数时才有闭包问题

  - 闭包指延伸了作用域的某个函数,它包含了不在函数内定义的非全局变量

def make_avg():
    vars = []

    def avg(value):
        vars.append(value)
        total = sum(vars)
        return total/len(vars)

    return avg

# avg中包含了在make_avg中定义的vars列表
# 即使make_avg已经返回,但vars仍然被保留下来
avg = make_avg()

print(avg(10))
print(avg(11))
print(avg(12))

# 闭包中引用的非自己定义的的非全局变量称为自由变量
# 自由变量的名称被保存在__code__.co_freevars中
# 自由变量的值被保存在__closure__[i].cell_contents中
print('avg.__code__.co_freevars:',avg.__code__.co_freevars)

print('avg.__closure__[0].cell_contents',avg.__closure__[0].cell_contents)

输出:

10.0
10.5
11.0
avg.__code__.co_freevars: ('vars',)
avg.__closure__[0].cell_contents [10, 11, 12]

nonlocal声明

  - 在闭包中对自由变量进行赋值,会生成一个局部变量覆盖自由变量,并且报错:局部变量使用前未赋值

def make_avg():
    count=0
    total=0
    def avg(value):
        # count+=1就是count = count + 1
        # 这里会自动生成一个未赋初始值的局部变量count覆盖自由变量count
        count+=1
        # total和count相同情况
        total+=value
        return total/count
    return avg

avg = make_avg()

print(avg(10))
print(avg(11))
print(avg(12))

输出:
UnboundLocalError: local variable 'count' referenced before assignment

  - 使用nonlocal声明,不会覆盖自由变量

def make_avg():
    count=0
    total=0
    def avg(value):
        # 使用nonlocal声明,不会覆盖自由变量
        nonlocal count,total
        count+=1
        total+=value
        return total/count
    return avg

avg = make_avg()

print(avg(10))
print(avg(11))
print(avg(12))

输出:
10.0
10.5
11.0

一个函数执行时间的装饰器

   - func的函数__name__和__doc__属性将被clocked相应属性覆盖

import time

def clock(func):

    def clocked(*args):
        t0 = time.perf_counter()
        result = func(*args)
        elapsed = time.perf_counter() - t0
        func_name = func.__name__
        func_args = ','.join(str(args))
        s = '[%0.8fs] %s(%s) --> %r' % (elapsed,func_name,func_args,result)
        print(s)
        return result

    return clocked

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

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

snooze(0.123)

print('-' * 100)

print('factorial(6):',factorial(6))

print('-' * 100)

print('the name of factorial:',factorial.__name__)

输出:
[0.12324016s] snooze((,0,.,1,2,3,,,)) --> None
----------------------------------------------------------------------------------------------------
[0.00000032s] factorial((,1,,,)) --> 1
[0.00001091s] factorial((,2,,,)) --> 2
[0.00002855s] factorial((,3,,,)) --> 6
[0.00003401s] factorial((,4,,,)) --> 24
[0.00003914s] factorial((,5,,,)) --> 120
[0.00004588s] factorial((,6,,,)) --> 720
factorial(6): 720
----------------------------------------------------------------------------------------------------
the name of factorial: clocked

   - 使用wraps装饰器后,func的__name__和__doc__将不会被覆盖

import time
import functools

def clock(func):

    # 使用wraps装饰器后,func的__name__和__doc__将不会被覆盖
    @functools.wraps(func)
    def clocked(*args):
        t0 = time.perf_counter()
        result = func(*args)
        elapsed = time.perf_counter() - t0
        func_name = func.__name__
        func_args = ','.join(str(args))
        s = '[%0.8fs] %s(%s) --> %r' % (elapsed,func_name,func_args,result)
        print(s)
        return result

    return clocked

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


print('the name of factorial:',factorial.__name__)

输出:
the name of factorial: factorial

使用functools.lru_cache做备忘

import time
import functools

def clock(func):

    # 使用wraps装饰器后,func的__name__和__doc__将不会被覆盖
    @functools.wraps(func)
    def clocked(*args):
        t0 = time.perf_counter()
        result = func(*args)
        elapsed = time.perf_counter() - t0
        func_name = func.__name__
        func_args = ','.join([str(a) for a in args if str(a) != ''])
        s = '[%0.8fs] %s(%s) --> %r' % (elapsed,func_name,func_args,result)
        print(s)
        return result

    return clocked

# 不使用lru_cache,fib(6)要调用fib(1)8次,fic(2)5次等等
@clock
def fib(n):
    if n < 2:
        return n
    else:
        return fib(n-2) + fib(n-1)

# 使用lru_cache,可以缓存中间结果,避免重复调用。
@functools.lru_cache()
@clock
def fib2(n):
    if n < 2:
        return n
    else:
        return fib2(n-2) + fib2(n-1)


print('fib(6):',fib(6))
print('-' * 100)
print('fib2(6):',fib2(6))

输出:
[0.00000032s] fib(0) --> 0
[0.00000032s] fib(1) --> 1
[0.00004235s] fib(2) --> 1
[0.00000000s] fib(1) --> 1
[0.00000000s] fib(0) --> 0
[0.00000032s] fib(1) --> 1
[0.00001059s] fib(2) --> 1
[0.00002117s] fib(3) --> 2
[0.00007443s] fib(4) --> 3
[0.00000032s] fib(1) --> 1
[0.00000032s] fib(0) --> 0
[0.00000000s] fib(1) --> 1
[0.00001091s] fib(2) --> 1
[0.00002181s] fib(3) --> 2
[0.00000032s] fib(0) --> 0
[0.00000000s] fib(1) --> 1
[0.00001123s] fib(2) --> 1
[0.00000032s] fib(1) --> 1
[0.00000032s] fib(0) --> 0
[0.00000032s] fib(1) --> 1
[0.00001123s] fib(2) --> 1
[0.00002246s] fib(3) --> 2
[0.00004459s] fib(4) --> 3
[0.00007731s] fib(5) --> 5
[0.00016297s] fib(6) --> 8
fib(6): 8
----------------------------------------------------------------------------------------------------
[0.00000000s] fib2(0) --> 0
[0.00000032s] fib2(1) --> 1
[0.00001476s] fib2(2) --> 1
[0.00000064s] fib2(3) --> 2
[0.00002695s] fib2(4) --> 3
[0.00000064s] fib2(5) --> 5
[0.00003882s] fib2(6) --> 8
fib2(6): 8

使用单分派函数

  - 类似其他面向对象语言的方法重载,以不同方式执行相同操作的一组函数

from functools import singledispatch
import html
from collections import abc
import numbers

# 单分派函数的基函数
@singledispatch
def htmlize(obj):
    return '<pre>{}<pre>'.format(html.escape(repr(obj)))


# 各个专门函数使用@<base_function>.register(<type>)修饰
@htmlize.register(str)
# 函数名无关紧要,_是个不错的选择,简单明了
def _(txt):
    s = html.escape(txt).replace('\n','<br>\n')
    return '<p>{}<p>'.format(s)


# numbers.Integral是int的虚拟超类
@htmlize.register(numbers.Integral)
def _(n):
    return '<pre>{0}(0x{0:X})</pre>'.format(n)


# 可以叠放多个register装饰器,让同一个函数支持不同类型
@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
    inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
    outer = '<ul>\n<li>' + inner + '</li>\n</ul>'
    return outer


print(htmlize({1, 2, 3}))

print(htmlize(abs))

print(htmlize(42))

print(htmlize(['alpha',66,{3,2,1}]))

输出:

<pre>{1, 2, 3}<pre>
<pre>&lt;built-in function abs&gt;<pre>
<pre>42(0x2A)</pre>
<ul>
<li><p>alpha<p></li>
<li><pre>66(0x42)</pre></li>
<li><pre>{1, 2, 3}<pre></li>
</ul>

一个参数化的装饰器

registry = set()

# register是装饰器工厂,返回一个装饰器
# 它接受一个可选关键字参数
def register(active=True):

    # decorate是真正的装饰器,它的参数是一个函数
    def decorate(func):
        print('running register(active=%s) -> decorate(%s)' % (active,func))
        # active为真,注册func
        if active:
            registry.add(func)
        # active为假,删除func
        else:
            registry.discard(func)
        return func

    return decorate

# register必须作为函数调用,并且可以传入参数
@register(active=True)
def func1():
    print('running func1')

# 即使不传入参数,也要作为函数调用
@register()
def func2():
    print('running func2')

# active为假,不注册func3
@register(active=False)
def func3():
    print('running func3')

if __name__ == '__main__':
    func1()
    func2()
    func3()
    print(registry)

输出:
running register(active=True) -> decorate(<function func1 at 0x00000201B1579F28>)
running register(active=True) -> decorate(<function func2 at 0x00000201B1586048>)
running register(active=False) -> decorate(<function func3 at 0x00000201B15860D0>)
running func1
running func2
running func3
{<function func1 at 0x00000201B1579F28>, <function func2 at 0x00000201B1586048>}

三层嵌套的装饰器

import time

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

# 一个三层嵌套的装饰器
def clock(fmt=default_fmt):
    # @clock()返回decorate
    def decorate(func):
        # 再把被装饰函数传递给decorate,运行它
        # 返回clocked,替代被装饰函数
        def clocked(*args):
            t0 = time.time()
            _result = func(*args)
            elapsed = time.time()-t0
            name = func.__name__
            args = ','.join(repr(item) for item in args)
            result = repr(_result)
            print(fmt.format(**locals()))
            return _result
        return clocked
    return decorate

# func被clocked替代
@clock()
def func():
    time.sleep(0.123)

if __name__ == '__main__':
    for i in range(3):
        func()

输出:
[0.12314367s] func() -> None
[0.12322927s] func() -> None
[0.12370729s] func() -> None
03-06 04:10