Python 元编程 - 装饰器 (2)

在使用装饰器时,看起来原函数并没有被改变,但它的元信息却改变了 - 此时的原函数实际是包裹后的 wrapper 函数。

help(normal_func) print(normal_func.__name__) # wrapper(*args, **kwargs) # wrapper

如果想要保留原函数的元信息,可通过内置的 @functools.wraps(func) 实现:

@functools.wraps(func) 的作用是通过 update_wrapper 和 partial 将目标函数的元信息拷贝至 wrapper 函数。

# def decorator def decorator_with_args(*args, **kwargs): print('Step1: enter wrapper with args func.') print(args) print(kwargs) def decorator_func(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('Step2: enter wrapper func.') return func(*args, **kwargs) return wrapper return decorator_func 装饰器嵌套

Python 支持对一个函数同时增加多个装饰器,那么添加的顺序是怎样的呢?

# def decorator def decorator_func_1(func): print('Step1: enter decorator_func_1..') def wrapper(): print('Step2: enter wrapper1 func.') return func() return wrapper def decorator_func_2(func): print('Step1: enter decorator_func_2..') def wrapper(): print('Step2: enter wrapper2 func.') return func() return wrapper @decorator_func_2 @decorator_func_1 def noraml_func(): pass

看一下 console 的结果:

Step1: enter decorator_func_1.. Step1: enter decorator_func_2..

fun_1 在前说明, 在对原函数包装时,采用就近原则,从下到上。

接着,调用 noraml_func 函数:

Step1: enter decorator_func_1.. Step1: enter decorator_func_2.. Step2: enter wrapper2 func. Step2: enter wrapper1 func.

可以发现,wrapper2 内容在前,说明在调用过程中由上到下。

上面嵌套的写法,等价于 normal_func = decorator_func_2(decorator_func_1(normal_func)),就是正常函数的调用过程。

对应执行顺序:

在定义时,先 decorator_func_1 后 decorator_func_2.

在调用时,先 decorator_func_2 后 decorator_func_1.

应用场景 日志记录

在一些情况下,需要对函数执行的效率进行统计或者记录一些内容,但又不想改变函数本身的内容,这时装饰器是一个很好的手段。

import timeit def timer(func): def wrapper(n): start = timeit.default_timer() result = func(n) stop = timeit.default_timer() print('Time: ', stop - start) return result return wrappe 作为缓存

装饰器另外很好的应用场景是充当缓存,如 lru 会将函数入参和返回值作为当缓存,以计算斐波那契数列为例, 当 n 值大小为 30,执行效率已经有很大差别。

def fib(n): if n < 2: return 1 else: return fib(n - 1) + fib(n - 2) @functools.lru_cache(128) def fib_cache(n): if n < 2: return 1 else: return fib_cache(n - 1) + fib_cache(n - 2) Time: 0.2855725 Time: 3.899999999995574e-05 总结

在这一篇中,我们知道:

装饰器的本质,就是利用 Python 中的嵌套函数的特点,将目标函数包裹在内嵌函数中,然后将嵌套函数 wrapper作为返回值返回,从而达到修饰原函数的目的。

而且由于返回的是 wrapper 函数,自然函数的元信息肯定不再是原函数的内容。

对于一个函数被多个装饰器修饰的情况:

在包装时,采用就近原则,从近点开始包装。

在被调用时,采用就远原则,从远点开始执行。

这自然也符合栈的调用过程。

参考

https://www.programiz.com/python-programming/decorator

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywyfx.html