Python 的装饰器
Posted on Wed, 25 Dec 2024 16:11:22 +0800 by LiangMingJian
什么是装饰器
装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
为什么要实现装饰器
这里已经有 3 个编写好的函数 a, b, c,各个函数的内容如下所示:
def a():
print('1')
def b():
print('2')
def c():
print('3')
这个时候,用户想让函数执行的时候打印它自己的名字,怎么解决?
正常的做法是给每个函数都加上一行代码,让它打印自己的名字,比如:
def a():
print('1')
print('我是函数 a')
但这就有问题了,如果函数很多,或者要加的功能不止一行代码,那给所有函数都这样操作是一件很复杂工作,同时在后续维护的时候也很麻烦(有点地方要修改,要一个一个函数去修改)。
这个时候,我们就可以使用装饰器,将功能抽象出来,然后给每个函数调用。
如何实现一个装饰器
我们来看下面的代码,这里的代码实现了一个简单的装饰器。
def logging(func):
def wrapper(*args, **kwargs):
print(f"我是函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
这个代码里,我们直接将函数 func 作为变量传递。(Python 的函数像普通对象一样,能作为参数传递给其他函数,能被赋值给其他变量,能作为返回值,甚至能被定义在另外一个函数内)
然后用 wrapper 代替 func 的运行,wrapper(*args, **kwargs) 可以接收所有的参数,最后在 return 时传递回 func,正常执行 func 的逻辑。
在 return 返回前,我们就可以添加自己想要多加的操作,比如打印 func 的名字。
形如上面这种结构的函数,我们都可以称之为装饰器。
如何使用装饰器
装饰器的使用极其简单,只需在想要使用的函数上用 @ + 装饰器名称 即可。注意,装饰器要先于原函数定义,这样原函数在调用时才能找到目标。
def logging(func):
def wrapper(*args, **kwargs):
print(f"我是函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logging
def a():
print('a')
@logging
def b():
print('b')
@logging
def c():
print('c')
a()
b()
c()
如何实现一个带参数的装饰器
def logging(date):
def decorator(func):
def wrapper(*args, **kwargs):
print(f'我是参数: {date}')
print(f"我是函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
上面的代码实现了一个支持传递参数的装饰器,它实际上是原有装饰器的再一个装饰器封装。
在这串代码中,decorator(func)可以看作是原本装饰器,logging(date)是用接收参数再一个装饰器,Python 能自动的发现这层结构,然后将参数传递到里层装饰器中。
使用:
@logging(1)
def a():
print('a')
a()
装饰器的缺陷
从上面的描述中,我们知道装饰器是通过替代函数执行实现的,因此不难看出,装饰器的缺陷就是原函数的信息会在替代时丢失,丢失原函数的元数据。
@logging
def a():
print('a')
def b():
print('b')
print(a.__name__)
print(b.__name__)
不难看出,在使用装饰器后,函数的 __name __ 会被装饰器替代,由 a 变成了 wrapper。
那有没有办法解决呢?答案肯定是有的。
使用装饰器 @functools.wraps(func),能将原函数的元数据复制到装饰器中。
import functools
def logging(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"我是函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logging
def a():
print('a')
def b():
print('b')
print(a.__name__)
print(b.__name__)
可以看到,在使用 @functools.wraps(func) 后,函数的 __name__ 正常了。
装饰器的执行顺序
在 Python 中,装饰器是支持多个使用的,多个装饰器的执行顺序是从下往上的,比如:
@a
@b
@c
def func()
上面代码中,先执行 c,再执行 b,最后执行 a,其执行等效于 func = a(b(c(func)))。