装饰器介绍
装饰器其实就是Python的一个特殊函数
,它接收一个函数
作为参数,然后返回另一个函数
,Python中一般使用@
语法糖来使用,它的作用就是,能在不修改原始函数的基础上为函数增加新的功能。关于装饰器的主要内容,涉及以下几个方面:
- 如何使用装饰器,装饰器的好处
- 带参数的装饰器
- 多个装饰器的使用流程
- 类装饰器
以下内容将围绕这四个点进行展开,其中首先讲解如何使用装饰器和装饰器的好处
如何使用装饰器,装饰器的好处
首先我们先任意定义一个函数,可以自定义也可以参考我的函数
python
def Add(a:float, b:float):
return a + b
我这里定义了一个Add函数,函数接收两个参数,分别是a和b,然后返回这两个数字的相加之和,这时候突然有了一个新的需求,要求为这个函数添加一个日志,获取函数在什么时候执行的,执行时候的参数是什么,函数的结果又是什么。
为了实现这个需求我们可以这么写,在函数中添加一点内容。
python
from datetime import datetime
def Add(a:float, b:float):
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
c = a + b
print(f"[{now}] Add({a}, {b}) => {c}")
return c
d = Add(5, 6)
print(d)
上面这段代码的运行结果如下
[2024-06-01 04:51:04] Add(5, 6) => 11
11
可以看到日志被成功的打印出来了,但是这样涉及到了编程中的一个概念,就是侵入式编程
,也就是通过改变原始函数的执行内容来实现功能添加和修改,但是这里我们要注意到一个问题,就是日志并不是函数的核心内容
,属于函数的附加功能,如果每次新增一个附加功能,就去修改一遍函数,那么函数将越来越混乱,严重影响函数的可读性。那么如何改变这一状况呢,很简单,设计一个日志函数,将找个函数加入到日志函数中运行即可。
python
from datetime import datetime
def log(func):
def wrapper(*args, **kwargs):
now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
result = func(*args, **kwargs)
kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
args_str = ','.join([str(x) for x in args])
if not args_str and not kwargs_str:
print(f"[{now}]{func.__name__}() => {result}")
elif args_str and not kwargs_str:
print(f"[{now}]{func.__name__}({args_str}) => {result}")
elif not args_str and kwargs_str:
print(f"[{now}]{func.__name__}({kwargs_str}) => {result}")
else:
print(f"[{now}]{func.__name__}({args_str}, {kwargs_str}) => {result}")
return result
return wrapper
def Add(a:float, b:float):
return a + b
f = log(Add)
c = f(a=5, b=6)
print(c)
这段代码的执行结果跟上面是一样的,只不过是我们将日志打印和原始函数的执行分开来,而且这样还有一点好处就是,f = log(Add)
这里的函数可以是任意的,对于任意函数的执行我们都可以获取到它的一个执行日志,而不用每个函数每个函数去修改。但是这样似乎还不是特别好,因为还要在f = log(Add)
把函数重新创造一遍,看上去不美观,这里就用到了我们在一些代码中常见的@
语法糖了,可以通过@
语法糖浆代码做如下的更换。
python
# 上面的代码不做任何处理
'''
# 注释掉这一段
def Add(a:float, b:float):
return a + b
f = log(Add)
c = f(a=5, b=6)
print(c)
'''
@log
def Add(a: float, b:float):
return a + b
c = Add(5, 6)
print(c)
这段代码和上面的代码执行流程是一样的,通过这两段代码的一个运行来理解@
的作用的就是
Add = log(Add) # 通过@实现了变量的重新赋值
@log装饰在某个函数上,就相当于 func = log(func)
装饰器的好处
通过上面的代码,可以看到,装饰器的核心优势就在于可以通过软编码
在不修改原函数的基础上为原函数增加其他的功能
,而且一个装饰器可以作用在多个函数
上,避免了重复造轮子。
带参数的装饰器
带参数的装饰器使用的是函数工厂模式,也就是在装饰器函数的外部在增加一个函数,这个函数是一个闭包函数
,它接收参数,返回值是闭包函数。
python
from datetime import datetime
def Level(level='DEBUG'):
def log(func):
def wrapper(*args, **kwargs):
now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
result = func(*args, **kwargs)
kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
args_str = ','.join([str(x) for x in args])
if not args_str and not kwargs_str:
print(f"[{level}][{now}]{func.__name__}() => {result}")
elif args_str and not kwargs_str:
print(f"[{level}][{now}]{func.__name__}({args_str}) => {result}")
elif not args_str and kwargs_str:
print(f"[{level}][{now}]{func.__name__}({kwargs_str}) => {result}")
else:
print(f"[{level}][{now}]{func.__name__}({args_str}, {kwargs_str}) => {result}")
return result
return wrapper
return log
@Level(level='INFO')
def Add(a: float, b:float):
return a + b
c = Add(5, 6)
print(c)
通过上面对@
语法糖的理解,这里我们也可以理解为
Add = Level(level='INFO')(Add)
多个装饰器的使用流程
如果我们的函数要增加的附加功能很多,比如我需要在两个数字相加前判断传入的数据中是不是都是float类型,如果不是,将不会相加,而是直接返回-1
python
from datetime import datetime
def Level(level='DEBUG'):
def log(func):
def wrapper(*args, **kwargs):
now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
result = func(*args, **kwargs)
kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
args_str = ','.join([str(x) for x in args])
if not args_str and not kwargs_str:
print(f"[{level}][{now}]{func.__name__}() => {result}")
elif args_str and not kwargs_str:
print(f"[{level}][{now}]{func.__name__}({args_str}) => {result}")
elif not args_str and kwargs_str:
print(f"[{level}][{now}]{func.__name__}({kwargs_str}) => {result}")
else:
print(f"[{level}][{now}]{func.__name__}({args_str}, {kwargs_str}) => {result}")
return result
return wrapper
return log
def alert(func):
def wrapper(*args, **kwargs):
result = [arg for arg in args if not isinstance(arg, float)]
result1 = [value for value in kwargs.values() if not(isinstance(value, float))]
if result1 or result:
return -1
else:
return func(*args, **kwargs)
return wrapper
@Level(level='INFO')
@alert
def Add(a: float, b:float):
return a + b
c = Add(5, 6)
print(c)
d = Add(8.0, 9.0)
print(d)
e = Add('我', 9.0)
print(e)
当多个装饰器装饰一个函数时,将按从上到下
的顺序来进行装饰,上面这段就相当于
Add = Level(level='INFO')(alert(Add))
类装饰器
类装饰器和普通装饰器的区别在于:普通装饰器通过返回函数来实现,类装饰器通过__call__
特殊方法来实现。现在很多时候使用的装饰器是类装饰器,因为从理解上,类装饰器更便于理解
python
from datetime import datetime
class Level:
def __init__(self, level='DEBUG'):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
result = func(*args, **kwargs)
kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
args_str = ','.join([str(x) for x in args])
if not args_str and not kwargs_str:
print(f"[{self.level}][{now}]{func.__name__}() => {result}")
elif args_str and not kwargs_str:
print(f"[{self.level}][{now}]{func.__name__}({args_str}) => {result}")
elif not args_str and kwargs_str:
print(f"[{self.level}][{now}]{func.__name__}({kwargs_str}) => {result}")
else:
print(f"[{self.level}][{now}]{func.__name__}({args_str}, {kwargs_str}) => {result}")
return result
return wrapper
def alert(func):
def wrapper(*args, **kwargs):
result = [arg for arg in args if not isinstance(arg, float)]
result1 = [value for value in kwargs.values() if not(isinstance(value, float))]
if result1 or result:
return -1
else:
return func(*args, **kwargs)
return wrapper
@Level(level='INFO')
def Add(a: float, b:float):
return a + b
Add(5, 7)
这里所演示的是一个带有参数的装饰器,也就是在使用装饰器是,必须带有(),否则将会产生异常,那么如何创建一个不需要带有()的装饰器呢,很简单,将__init__中的参数设置为func就可以。
python
from datetime import datetime
class log:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
now = datetime.now().strftime("%y-%m-%d %H:%M:%S")
result = self.func(*args, **kwargs)
kwargs_str = ','.join([f"{key} = {value}" for key, value in kwargs.items()])
args_str = ','.join([str(x) for x in args])
if not args_str and not kwargs_str:
print(f"[{now}]{self.func.__name__}() => {result}")
elif args_str and not kwargs_str:
print(f"[{now}]{self.func.__name__}({args_str}) => {result}")
elif not args_str and kwargs_str:
print(f"[{now}]{self.func.__name__}({kwargs_str}) => {result}")
else:
print(f"[{now}]{self.func.__name__}({args_str}, {kwargs_str}) => {result}")
return result
def alert(func):
def wrapper(*args, **kwargs):
result = [arg for arg in args if not isinstance(arg, float)]
result1 = [value for value in kwargs.values() if not(isinstance(value, float))]
if result1 or result:
return -1
else:
return func(*args, **kwargs)
return wrapper
@log
def Add(a: float, b:float):
return a + b
Add(5, 7)