Python 元编程 - 装饰器
Python 中提供了一个叫装饰器的特性,用于在不改变原始对象的情况下,增加新功能或行为。
这也属于 Python "元编程" 的一部分,在编译时一个对象去试图修改另一个对象的信息,实现 "控制一切" 目的。
本篇文章作为装饰器的基础篇,在阅读后应该了解如下内容:
- 装饰器的原理?
- 装饰器如何包裹有参数的函数?
- 装饰器本身需要参数怎么办?
- 被装饰器修饰的函数还是原函数吗,怎么解决?
- 装饰器嵌套时的顺序?
- 装饰器常见的应用场景?
装饰器原理
在具体装饰器的内容前,先来回顾下 Python 中的基本概念:
1. Python 中,一切都是对象,函数自然也不例外
python 中的对象都会在内存中用于属于自己的一块区域。在操作具体的对象时,需要通过 “变量” ,变量本身仅是一个指针,指向对象的内存地址。
函数作为对象的一种,自然也可以被变量引用。
def hello(name: str): print('hello', name) hello('Ethan') alias_func_name = hello alias_func_name('Michael') # hello Ethan # hello Michael
alias_func_name作为函数的引用,当然也可以作为函数被使用。
2. 函数接受的参数和返回值都可以是函数
def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result operate(inc,3) # 4 operate(dec,3) # 2
这里
operate中接受函数作为参数,并在其内部进行调用。
3. 嵌套函数
def increment(): def inner_increment(number): return 1 + number return inner_increment() print(increment(100)) # 101
在 increment 内部,实现对 number add 1 的操作。
回头再来看下装饰器的实现:
# def decorator def decorator_func(func): print('enter decorator..') def wrapper(): print('Step1: enter wrapper func.') return func() return wrapper # def target func def normal_func(): print("Step2: I'm a normal function.") # use decorator normal_func = decorator_func(normal_func) normal_func()
decorator_func(func)中,参数
func表示想要调用的函数,wrapper 为嵌套函数,作为装饰器的返回值。
wrapper内部会调用目标函数
func并附加自己的行为,最后将
func执行结果作为返回值。
究其根本,是在目标函数外部套上了一层 wrapper 函数,达到在不改变原始函数本身的情况下,增加一些功能或者行为。
通常使用时,使用
@decorator_func来简化调用过程的两行代码。
将自定义调用装饰器的两行代码删掉,使用常规装饰器的写法加在
normal_func的定义处,但却不调用
normal_func,可以发现一个有趣的现象:
# def decorator def decorator_func(func): print('enter decorator..') def wrapper(): print('Step1: enter wrapper func.') return func() return wrapper # def target func @decorator_func def normal_func(): print("Step2: I'm a normal function.")
发现
enter decorator..在没有调用的情况下被打印到控制台。
这就说明,此时
normal_func已经变成了
wrapper函数。
@decorator_func其实隐含了
normal_func = decorator_func(normal_func)这一行代码。
对带有参数的函数使用装饰器
假设这里 normal_func 需要接受参数怎么办?
很简单,由于是通过嵌套函数来调用目标函数,直接在
wrapper中增加参数就可以了。
# def decorator def decorator_func(func): def wrapper(*args, **kwargs): print('Step1: enter wrapper func.') return func(*args, **kwargs) return wrapper # def target func def normal_func(*args, **kwargs): print("Step2: I'm a normal function.") print(args) print(kwargs) # use decorator normal_func = decorator_func(normal_func) normal_func(1, 2, 3, name='zhang', sex='boy')
使用
*args, **kwargs是考虑到该 decorator 可以被多个不同的函数使用,而每个函数的参数可能不同。
装饰器本身需要参数
在装饰器本身也需要参数时,可以将其嵌套在另一个函数中,实现参数的传递。
# def decorator def decorator_with_args(*args, **kwargs): print('Step1: enter wrapper with args func.') print(args) print(kwargs) def decorator_func(func): def wrapper(*args, **kwargs): print('Step2: enter wrapper func.') return func(*args, **kwargs) return wrapper return decorator_func # def target func def normal_func(*args, **kwargs): print("Step3: I'm a normal function.") print(args) print(kwargs) normal_func = decorator_with_args('first args')(normal_func) normal_func('hello') # use @ to replace the above three lines of code @decorator_with_args('first args') def normal_func(*args, **kwargs): print("Step3: I'm a normal function.") print(args) print(kwargs)
来分析下
decorator_with_args函数:
- 由于
decorator_with_args
接受了任意数量的参数,同时由于decorator_func
和wrapper
作为其内部嵌套函数,自然可以访问其内部的作用域的变量。这样就实现了装饰器参数的自定义。 decorator_func
是正常的装饰器,对目标函数的行为进行包装。进而需要传递目标函数作为参数。
在使用时:
@decorator_with_args('first args')实际上做的内容,就是
normal_func = decorator_with_args('first args')(normal_func)的内容:
decorator_with_args('first args')
返回decorator_func
装饰器。decorator_func
接受的正常函数对象作为参数,返回包装的wrapper
对象。- 最后将 wrapper 函数重命名至原来的函数,使其在调用时保持一致。
保留原函数信息
在使用装饰器时,看起来原函数并没有被改变,但它的元信息却改变了 - 此时的原函数实际是包裹后的 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函数,自然函数的元信息肯定不再是原函数的内容。
对于一个函数被多个装饰器修饰的情况:
- 在包装时,采用就近原则,从近点开始包装。
- 在被调用时,采用就远原则,从远点开始执行。
这自然也符合栈的调用过程。
参考
- 【Python学习笔记】函数式编程:装饰器
- Python模块化编程与装饰器
- Python的函数式编程-传入函数、排序算法、函数作为返回值、匿名函数、偏函数、装饰器
- python 的高级特性:函数式编程,lambda表达式,装饰器
- python 核心编程(第二版)p301 装饰器实例纠正
- Python编程(四):两个实用的Python的装饰器
- Learning Python 012 函数式编程 2 返回函数 匿名函数 装饰器 偏函数
- python核心编程-函数-装饰器
- Python面向切面编程的装饰器模式
- Learning Python 012 函数式编程 2 返回函数 匿名函数 装饰器 偏函数
- python 元类型编程,实现匿名验证器的装饰器AuthenticationDecoratorMeta
- Python 函数式编程、装饰器以及一些相关概念简介
- python函数式编程之匿名函数、装饰器、偏函数
- Python笔记第四章,反射,装饰器,面向对像编程,异常处理
- python 装饰器的函数式编程
- 【Python】[函数式编程]高阶函数,返回函数,装饰器,偏函数
- 【语言工具】Python闭包,装饰器,生成器,偏函数,函数式编程,lamda,map,reduce,filter
- python 装饰器编程
- 【Python3 笔记】Python3 函数式编程 高阶函数 返回函数 匿名函数 装饰器 偏函数
- Python闭包、函数式编程、装饰器深入理解