一、什么是装饰器
装饰器本质上就是一个Python函数,它可以装饰在其他函数上,使得其他函数不需要做任何改动就可以获得装饰器函数中的功能。实际上被装饰器修饰的函数在执行的时候不会直接运行其函数内部的逻辑,而是先将这个函数当作参数传递给装饰器函数,再执行装饰器函数的逻辑。
## 定义装饰器函数
def decorator(func):
print("正在执行装饰器函数,即将执行被修饰的 %s 函数" % func.__name__)
## 执行被修饰的函数funA(),其实这里是先将funA()函数赋值给了func形参变量,等价于:func = funA
func_result = func()
print(func_result) ## 得到funA()函数的返回结果值:funA执行完毕
# return "装饰器执行完毕" ## 如果装饰器返回的是'普通变量',那么被修饰的'函数名'就变成了变量名
return func ## 如果装饰器返回的是一个'函数',那么被修饰的函数名依然表示一个函数
## 定义被装饰的函数
@decorator
def funA():
print("正在执行funA()函数")
return "funA执行完毕"
1.当装饰器返回的是'普通变量',打印被装饰器修饰的函数名
print(funA)
## 打印内容为:装饰器执行完毕
2.当装饰器返回的是'普通变量',打印被装饰器修饰的函数方法
print(funA())
## 报错,TypeError: 'str' object is not callable,是由于装饰器返回的是一个字符串变量,
## 无法当作函数来调用。
3.当装饰器返回的是'函数',打印被装饰器修饰的函数名
print(funA)
## 打印内容为:<function funA at 0x0000020DC3451A80>,
## 这里其实是将装饰器最初接收的被装饰的函数(也就是funA()函数)重新赋值给funA变量,
## 等价于:funA = func
4.当装饰器返回的是'函数',打印被装饰器修饰的函数方法
print(funA())
## 打印内容为:正在执行funA()函数
## funA执行完毕
## 这里重新调用了一次funA()函数的内容
二、带参数的函数装饰器
当被装饰器修饰的函数不带参数时,可以将被修饰的函数直接赋值给装饰器函数的形参,然后传递到装饰器中去执行。但是当被修饰的函数带参数时,由于装饰器函数的形参只能接收被修饰的函数,无法接收被修饰的函数的参数,要如何将这些参数也传入到构造器中使用呢?其实需要在装饰器中添加一个嵌套函数,保证这个嵌套函数的形参个数与被修饰的函数形参个数相同就好。由于装饰器可以修饰任意函数,而每个函数的形参个数可能也都大不相同,所以不可能每次都去修改装饰器的嵌套函数,因此我们使用python的变长参数*args和**kwargs来解决不同函数参数不同的问题。
- 被装饰器修饰的函数只有一个参数时:
## 定义装饰器
def decorator(func):
print("正在执行装饰器函数,即将执行被修饰的 %s 函数" % func.__name__)
## 定义一个嵌套函数,用来接收被装饰函数的参数
def deco_func(deco_param):
print("装饰器函数内嵌套函数接收的参数值为:%s " % deco_param)
## 将接收的被装饰的函数参数重新传递给被装饰函数并执行被装饰的函数后将被装饰的函数返回值返回出去
return func(deco_param)
print("装饰器函数内嵌套函数对象为: %s" % deco_func)
## 装饰器函数最终将嵌套函数对象返回出去
return deco_func
## 定义被装饰的函数
@decorator
def funA(param):
print("正在执行funA()函数, 参数为:%s " % param)
return "funA执行完毕"
print("funA对象为: %s" % funA)
正在执行装饰器函数,即将执行被修饰的 funA 函数
装饰器函数内嵌套函数对象为: <function decorator.<locals>.deco_fucn at 0x0000022F83E22B60>
funA对象为: <function decorator.<locals>.deco_fucn at 0x0000022F83E22B60>
1.通过打印funA函数名变量可以看出,funA = deco_func,实际上就是执行完装饰器函数后,最终直到将这个嵌套函数deco_func对象返回出去并赋值给funA函数名变量。
print("调用funA函数,执行结果为: %s" % funA("sdad"))
正在执行装饰器函数,即将执行被修饰的 funA 函数
装饰器函数内嵌套函数对象为: <function decorator.<locals>.deco_fucn at 0x000001BA245FE340>
装饰器函数内嵌套函数接收的参数值为:sdad
正在执行funA()函数, 参数为:sdad
调用funA函数,执行结果为: funA执行完毕
2.通过funA函数名变量去调用实际赋值给它的函数,也就是会进入到装饰器中去执行deco_func()函数,
再执行嵌套函数的时候会调用func()函数(其实就是执行funA()函数),最后将func()函数执行结果返回出去。
- 被装饰器修饰的函数有多个参数时:
def decorator(func):
print("正在执行装饰器函数,即将执行被修饰的 %s 函数" % func.__name__)
## 定义一个嵌套函数,用来接收被装饰函数的参数
def deco_fucn(*args, **kwargs):
print("装饰器函数内嵌套函数接收的参数值为:%s %s " % (args, kwargs))
return func(*args, **kwargs)
print("装饰器函数内嵌套函数对象为: %s" % deco_fucn)
return deco_fucn
@decorator
def funA(param):
print("正在执行funA()函数, 参数为:%s " % param)
return "funA执行完毕"
@decorator
def funB(param1, param2):
print("正在执行funB()函数, 参数为:%s %s " % (param1, param2))
return "funB执行完毕"
print("调用funA函数,执行结果为: %s" % funA("sdad"))
print("调用funB函数,执行结果为:%s " % funB("A", "B"))
三、装饰器函数修饰类
装饰器函数除了可以装饰函数外,还可以装饰在类上,当装饰在类上时,可以实现对类添加额外的属性、方法等,被装饰的类同样分为带参数与不带参数,实现方式与装饰在函数上一样的。
## 定义一个装饰器函数,cls形参接收的是被装饰的类对象
def decorator(cls):
## 通过装饰器内嵌套函数
def wrapper():
## 给被装饰的类添加一个属性
cls.age = 18
## 嵌套函数返回的是被装饰的类对象
return cls
print("装饰器函数内嵌套函数对象为: %s" % wrapper)
## 装饰器函数最终返回的是嵌套函数对象
return wrapper
@decorator
class Person:
def __init__(self, name):
self.name = name
print("被装饰的类名变量为: %s" % Person)
装饰器函数内嵌套函数对象为: <function decorator.<locals>.wrapper at 0x00000191FC6204A0>
被装饰的类名变量为: <function decorator.<locals>.wrapper at 0x00000191FC6204A0>
1.通过打印类名变量可以看出,Person = wrapper;实际上就是执行完装饰器函数后会将装饰器最终返回的嵌套函数对象赋值给类名变量Person。
print(Person().age)
18
2.通过类名变量Person去调用实际赋值给它的函数对象,也就是进入装饰器中去执行嵌套函数wrapper(),执行完嵌套函数后会返回一个cls对象,也就是被装饰器修饰的Person对象,这里Person()得到就是经过装饰器中的嵌套函数处理过后,具有age属性的Person对象了。
四、装饰器类装饰函数
类也可以用来当作装饰器,在使用类装饰器的时候需要注意在装饰器类内需要实现__init__()和__call__()方法。
class Decorate:
## func接收被装饰的函数
def __init__(self, func):
print("__init__中接收的被装饰的函数名为:%s " % func.__name__)
self.__func = func
## *args, **kwargs 接收被装饰的函数的参数
def __call__(self, *args, **kwargs):
"""实现__call__方法,对被装饰的函数参数进行打印或者写入到日志中"""
print("打印被装饰的函数参数为:%s %s " % args, kwargs)
## 调用被装饰的函数并将执行的结果返回
return self.__func(*args, **kwargs)
@Decorate
def sum(a, b):
result = a + b
return result
print(sum)
<__main__.Decorate object at 0x0000024E98556CD0>
##这里打印被装饰的函数名变量得到的是装饰器类的对象。
print(sum(1, 2))
__init__中接收的被装饰的函数名为:sum
打印被装饰的函数参数为:1 2 {}
3
##其实这里sum(1, 2)已经不能说是调用sum函数了,实际上是调用Decorate类的构造方法,
##将sum()函数传递到装饰器类的__init__方法,被装饰的函数的参数传递到装饰器类的__call__方法中。
五、装饰器类装饰类
与装饰器类装饰函数修饰函数一样,只不过被装饰器装饰的是一个类,基本处理逻辑是一样的。
class Decorator:
## decorated_class接收被装饰的类
def __init__(self, decorated_class):
self.decorated_class = decorated_class
## *args, **kwargs用于接收被装饰的类的参数
def __call__(self, *args, **kwargs):
# 对被装饰的类执行初始化,并返回创建的实例对象
return self.decorated_class(*args, **kwargs)
@Decorator
class Persion:
def __init__(self, name, age):
self.name = name
self.age = age
def get_age(self):
return self.age
print(Persion)
<__main__.Decorator object at 0x0000021887015210>
##这里还是将装饰器类对象赋值给被装饰的类名变量。
obj = Persion("Tom", 18)
##由于Persion已经被赋值成装饰器类对象,所以Persion("Tom", 18)实际上调用的是装饰器中的__call__方法
##在装饰器的__call__方法中对被装饰的类执行初始化,并返回创建的实例对象,所以obj相当于初始化后的Persion对象
print(obj.get_age())
18
六、多装饰器的执行顺序
有时会遇到多个装饰器同时装饰一个函数的场景,其装饰顺序与执行顺序为:离原函数越近的装饰器先进行装饰,离原函数越远的装饰器先执行。
def Decorate1(func):
def wrapper1(*args, **kwargs):
print(func.__name__)
print("Decorate1执行时间为: ", datetime.datetime.now())
time.sleep(5)
return func(*args, **kwargs)
return wrapper1
def Decorate2(func):
def wrapper2(*args, **kwargs):
print(func.__name__)
print("Decorate2执行时间为: ", datetime.datetime.now())
time.sleep(5)
return func(*args, **kwargs)
return wrapper2
@Decorate1 # 等价于 func = decorator1(decorator2(func))
@Decorate2 # 等价于 func = decorator2(func)
def func():
print("原函数执行时间为: ", datetime.datetime.now())
func()
wrapper2
Decorate1执行时间为: 2023-06-28 23:10:41.802692
func
Decorate2执行时间为: 2023-06-28 23:10:46.803709
原函数执行时间为: 2023-06-28 23:10:51.804347