装饰器应根据装饰器本身和装饰对象进行分类和函数。应该有四种组合。上面提到的有三种,另一种是装饰器和装饰对象的组合。然而,在公共信息中没有发现是否有类型的装饰,即装饰器和装饰对象是类别。
- 函数装饰器:装饰器和装饰对象均为函数;
- 类函数装饰:装饰为函数,装饰对象为类;
- 函数类装饰:装饰为类,装饰对象为函数;
- 类装饰:装饰和装饰对象为类。
二、函数装饰器
装饰包括装饰对象和装饰对象,最简单的装饰是用装饰函数装饰装饰函数,在这种情况下,装饰是函数装饰,装饰对象也是函数。
函数装饰器是一个特殊的函数,该函数的参数是一个函数,在装饰函数中重新定义一个新函数,并在执行某些功能前后或中间使用装饰函数,最后返回新定义函数。装饰器也可以称为函数包装器,实际上是在装饰函数执行前后添加一些单独的逻辑代码,使装饰函数执行后的最终结果受到装饰函数逻辑的影响,以改变或限制装饰函数的执行结果。
@decoratorName def originalFunction(*args,**kwargvs): 函数体
2.3.装饰语法解释
- 装饰的定义如下@符号开始声明
- decoratorName是装饰的名字,decoratorName必须封闭函数必须对应(请参考)《https://blog.csdn.net/LaoYuanPython/article/details/100349055 第13.2节 封闭函数满足以下要求:
- 参数是函数对象;
- 在封闭函数中有一个嵌套函数,它将调用封闭函数参数指定的函数,并添加额外的代码这些代码是装饰);
- 必须包含嵌套函数的参数originalFunction但不能带装饰对象的参数originalFunction;
- 嵌套函数的返回值必须与封闭函数参数指定函数的返回值相似,符合鸭类型的要求(请参考鸭类型)《https://blog.csdn.net/LaoYuanPython/article/details/91350122 第7.3节 Python面向对象对象设计:协议、多态、鸭类);
- 封闭函数的返回值必须是嵌套函数。
- 装饰函数的定义参考以下形式:
def decoratorName(originalFunction,*args,**kwargvs): def closedFunction(*args,**kwargvs): ... #originalFunction执行函数前的一些装饰代码 ret = originalFunction(*args,**kwargvs) ... #originalFunction一些装饰代码执行函数 return ret return closedFunction
其中decoratorName是装饰函数,originalFunction是装饰函数,closedFunction是装饰函数中的嵌套函数。
- 装饰定义的语法本质上相当于以下句子:
originalFunction = decoratorName(originalFunction)
多个装饰器可以在一个函数外顺序定义,类似于:
@decorator1 @decorator2 @decorator3 def originalFunction(*args,**kwargvs): 函数体
这种多个装饰实际上是叠加的,上面的装饰是下面装饰的包装。上述定义句的效果相当于以下句子:
originalFunction = decorator3(originalFunction) originalFunction = decorator2(originalFunction) originalFunction = decorator1(originalFunction)
也就是:
originalFunction = decorator1(decorator2(decorator3(originalFunction)))
三、类函数装饰器
函数装饰器除了添加函数装饰器(使用函数名作为装饰器函数的参数)外,还可以添加函数装饰器添加函数装饰器,类名作为装饰函数的参数,并在装饰函数中定义类别wrapClass,这类称为包装类,包装类的结构函数必须调用装饰类来定义实例变量,装饰函数将返回包装类wrapClass。
def decorateFunction(fun, *a, **k): class wrapClass(): def __init__(self, *a, **k): self.wrappedClass=fun(*a, **k) def fun1(self,*a, **k): print("准备调用被装饰类的方法fun1") self.wrappedClass.fun1(*a, **k) print("调用装饰方法fun1完成") return wrapClass @decorateFunction class wrappedClass: def __init__(self ,*a, **k): print("我是装饰性的结构方法") if a:print("位置参数存在于结构方法中:",a) if k:print("关键词参数存在于构造方法中:",k) print("装饰结构方法完成") def fun1(self,*a, **k): print("我是装饰性的fun1方法") if a:print("fun1存在位置参数:",a) if k:print("fun关键词参数存在:",k) print("被装饰类fun方法执行完毕") def fun2(self,*a, **k): print("我是装饰性的fun2方法")
上述装饰函数装饰类别wrappedClass,我们执行以下句子:
>>> c1 = wrappedClass('testPara',a=1,b=2) 我是装饰性的结构方法 位置参数存在于结构方法中: ('testPara',) 构造方法存在关键字参数: {'a': 1, 'b': 2} 装饰结构方法完成 >>> c1.fun1() 准备调用装饰方法fun1 我是装饰性的fun1方法 被装饰类fun方法执行完毕 调用装饰方法fun1完成 >>> c1.fun2() Traceback (most recent call last): File "<pyshell#37>", line 1, in <module> c1.fun2() AttributeError: 'wrapClass' object has no attribute 'fun2' >>>
可以看出,装饰类的相关方法必须在装饰类中调用才能实施。如果装饰函数定义类不定义装饰类的同名函数,则装饰后返回的对象不能实施装饰类的相关方法。
以上案例1是通过静态重新定义装饰函数内部装饰方法的装饰方法来实现对包装方法的支持,这种情况可以用于装饰类只需要调用指定的已知方法,但有时我们的装饰可以用于装饰多类,只有结构方法和具体方法在装饰类会导致装饰类需要调用功能不能调用,这时我们需要在装饰器中实现一个通用方法来保障被装饰类装饰后能执行被装饰类的所有方法。这需要借助setattr动态定义类实例法。
def decorateFunction(fun, *a, **k): class wrapClass(): def __init__(self, *a, **k): self.wrappedClass=fun(*a, **k) self.decorate() ##赋值没有重写定义的方法wrapClass作为实例变量,本案涉及的是fun2方法 def fun1(self,*a, **k): print("准备调用被装饰类的方法fun1") self.wrappedClass.fun1(*a, **k) print("调用装饰方法fun1完成") def decorate(self):##赋值没有重写定义的方法wrapClass例变量 for m in dir(self.wrappedClass):
if not m.startswith('_')and m!='fun1':
fn = getattr(self.wrappedClass, m)
if callable(fn):
setattr(self, m,fn)
return wrapClass
@decorateFunction
class wrappedClass:
def __init__(self ,*a, **k):
print("我是被装饰类的构造方法")
self.name = a[0]
if a:print("构造方法存在位置参数:",a)
if k:print("构造方法存在关键字参数:",k)
print("被装饰类构造方法执行完毕")
def fun1(self,*a, **k):
print("我是被装饰类的fun1方法")
if a:print("fun1存在位置参数:",a)
if k:print("fun1存在关键字参数:",k)
print("我的实例名字为:",self.name)
print("被装饰类fun1方法执行完毕")
def fun2(self,*a, **k):
print("我是被装饰类的fun2方法")
if a:print("fun2方法存在位置参数:",a)
if k:print("fun2存在关键字参数:",k)
print("我的实例名字为:",self.name)
针对以上被装饰函数装饰的类wrappedClass,我们执行如下语句:
>>> c1 = wrappedClass('c1',a=1,b=2)
我是被装饰类的构造方法
构造方法存在位置参数: ('c1',)
构造方法存在关键字参数: {'a': 1, 'b': 2}
被装饰类构造方法执行完毕
>>> c2 = wrappedClass('c2',a=12,b=22)
我是被装饰类的构造方法
构造方法存在位置参数: ('c2',)
构造方法存在关键字参数: {'a': 12, 'b': 22}
被装饰类构造方法执行完毕
>>> c1.fun1()
准备调用被装饰类的方法fun1
我是被装饰类的fun1方法
我的实例名字为: c1
被装饰类fun1方法执行完毕
调用被装饰类的方法fun1完成
>>> c2.fun2()
我是被装饰类的fun2方法
我的实例名字为: c2
>>> c1.fun2()
我是被装饰类的fun2方法
我的实例名字为: c1
>>>
可以看到,除了在装饰类中重写的fun1方法可以正常执行外,没有重写的方法fun2也可以正常执行。
四、函数的类装饰器
除了用函数作为装饰器装饰函数或者装饰类之外,也可以使用类作为函数的装饰器。将类作为函数的装饰器时,需将要装饰的函数作为装饰器类的实例成员,由于装饰后,调用相关方法时实际上调用的是装饰类的实例对象本身,为了确保类的实例对象可以调用,需要给类增加__call__
方法。
class decorateClass:
def __init__(self,fun):
self.fun=fun
def __call__(self, *a, **k):
print("执行被装饰函数")
return self.fun( *a, **k)
@decorateClass
def fun( *a, **k):
print(f"我是函数fun,带参数:",a,k)
定义后执行相关调用情况如下:
>>> f = fun('funcation1',a=1,b=2)
执行被装饰函数
我是函数fun,带参数: ('funcation1',) {'a': 1, 'b': 2}
>>>
五、类的类装饰器
前面分别介绍了函数的函数装饰器、类的函数装饰器、函数的类装饰器,但公开资料中未查到是否可以有类的类装饰器,即装饰器和被装饰对象都是类。参考类的函数装饰器、函数的类装饰器最终确认类的类装饰器也是可以支持的。
要实现类的类装饰器,按的研究,类的装饰器类的实现需要遵循如下要点:
- 装饰器类必须实现至少两个实例方法,包括
__init__和__call__
; - 在装饰器类的构造方法的参数包括
self,wrapedClass,*a,**k
,其中wrapedClass代表被装饰类,a代表被装饰类构造方法的位置参数,k代表被装饰类构造方法的关键字参数。关于位置参数和关键字参数请参考《https://blog.csdn.net/LaoYuanPython/article/details/90668385:第5章函数进阶 第5.1节 Python函数的位置参数、关键字参数精讲》; - 在装饰器类的构造方法中定义一个包装类如叫wrapClass,包装类从装饰器类的构造方法的参数wrapedClass(即被装饰类)继承,包装类wrapClass的构造方法参数为
self,*a,**k
,相关参数含义同上; - 在包装类的构造方法中调用父类的构造方法,传入参数a、k;
- 在装饰器类的构造方法中用实例变量(例如self.wrapedClass)保存wrapClass类;
- 在装饰器类的
__call__
方法中调用self.wrapedClass(*a,**k)
创建被装饰类的一个对象,并返回该对象。
按照以上步骤创建的类装饰器,就可以用于装饰其他类。当然上述方法只是自己研究测试的结论,是否还有其他方法也不肯定。
class decorateClass: #装饰器类
def __init__(self,wrapedClass,*a,**k): #wrapedClass代表被装饰类
print("准备执行装饰类初始化")
class wrapClass(wrapedClass):
def __init__(self,*a,**k):
print(f"初始化被封装类实例开始,位置参数包括:{a}, 关键字参数为{k}")
super().__init__(*a,**k)
print(f"初始化被封装类实例结束")
self.wrapedClass=wrapClass
print("装饰类初始化完成")
def __call__(self, *a, **k):
print("被装饰类对象初始化开始")
wrapedClassObj = self.wrapedClass(*a,**k)
print("被装饰类对象初始化结束")
return wrapedClassObj
@decorateClass
class car:
def __init__(self,type,weight,cost):
print("class car __init__ start...")
self.type = type
self.weight = weight
self.cost = cost
self.distance = 0
print("class car __init__ end.")
def driver(self,distance):
self.distance += distance
print(f"{self.type}已经累计行驶了{self.distance}公里")
c = car('爱丽舍','1.2吨',8)
c.driver(10)
c.driver(110)
执行以上代码,输出如下:
准备执行装饰类初始化
装饰类初始化完成
被装饰类对象初始化开始
初始化被封装类实例开始,位置参数包括:('爱丽舍', '1.2吨', 8), 关键字参数为{}
class car __init__ start...
class car __init__ end.
初始化被封装类实例结束
被装饰类对象初始化结束
爱丽舍已经累计行驶了10公里
爱丽舍已经累计行驶了120公里
除了上述方法,又找到了一种更简单的方法,具体请参考《https://blog.csdn.net/LaoYuanPython/article/details/111307103:类的类装饰器实现思路及案例》。