修饰器的用途在于提供一种统一的对象修改模式,并用于不同的对象个体。比如对于某个数据加、减、乘等运算结果,根据某种规则执行统一打折折扣,不同的运算可以处理为不同的函数或者对象,再用统一的折扣修饰器对其进行修饰,实现打折运算。其实Python语言本身很多都是采用修饰器完成其功能的。比如类的方法除了常规的由对象拥有和调用的方法外,静态方法和类方法是另外两种不同特性的方法,而Python正是通过内建的方法staticmethed()和classmethod()利用静态修饰器@staticmethod和@classmethod作用于用户定义的普通函数实现对静态函数和类函数的定义的。
当然,个人觉得遗憾的是修饰器的修饰作用只在对象定义时才起作用,而不能在运行过程中起作用,这点在使用时应该注意。
Python修饰器对对象进行修饰,所以,除了可以将函数作为修饰器外,还可以将类作为修饰器,完成对类的修饰。此时,我们应该首先对修饰器有一个基本认识:
- 修饰器本身是用来对被修饰对象进行某种修饰的,可以完成某些修饰动作,应该首先定义,同时,它必须具有接收被修饰对象的参数。在前面的例子中,我们的修饰器都是函数,它接收另一个函数作为被修饰对象;
- 由于修饰器要对被修饰对象进行修饰,因此,修饰器必须知道被修饰对象的相关信息,并且具有对其完成修饰操作的相关权限。或者反过来说,被修饰对象必须符合修饰器对被修饰对象制定的相关标准,并提供给修饰器相关权限,才能使修饰器完成被修饰对象的修饰。所以,使用同一修饰器的被修饰对象在这个层面上讲是同一类的对象。例如,如果被修饰对象是函数并在修饰器中被调用,那被修饰函数的调用参数定义必须符合修饰器中的调用方式。
根据上述要点,针对类修饰器,我们可以知道有以下几点:
- 关于组合问题。理论上,对修饰器和被修饰对象的组合可以有以下几种:修饰器是函数,被修饰对象是函数;修饰器是函数,被修饰对象是类;修饰器是类,被修饰对象是类;修饰器是类,被修饰对象是函数;
- 对于修饰器是函数,修饰对象是类的情况。要解决的第一个问题是,函数必须定义一个被修饰的类作为参数;第二是要在函数中完成对这个类的某种修饰,需要能够访问类的一些特性。为了解决这两个问题,Python通过在修饰器函数中定义一个内嵌的类,而被修饰对象类在经由修饰器修饰后,实际效果是由这个内嵌的类代替了被修饰的对象类而完成的。比如有如下代码:
def Dec(cls):
class innerClass:
def inMthd(self);
print('我是:',self)
return innerClass
@class outClass:
def outMthd(self):
print('我是:',self)
c=outClass()
查看c可以发现,c其实已经被取代为innerClass类,这是由于修饰器的作用为c=Dec(c)产生的,所以c的所有特性、方法都已经与outClass无关,例如只具有inMthd方法而不具有outMthd方法了。不过由于修饰器的一般用法是看做利用修饰器对修饰对象进行修改,在使用中我们从逻辑上一般认为它是修改过后的outClass类对象,因此在程序代码中自然还是会调用outClass的方法,使用outClass的特性,所以为了实现这个目的,我们只有通过将innerClass的方法和特性定义为与outClass要使用的方法和特性相同才能达到目的。这里有点像由innerClass重载覆盖了outClass,但又不是通过继承完成的。
- 对于修饰器是类,被修饰对象是类或者函数的情况。此时首先要解决的问题是如何向一个类传递被修饰对象参数。这个问题通过修饰器类的__init__初始化方法解决,即在修饰器类实例化的时候,向其传递被修饰对象(类或者函数)。而如何完成对被修饰对象的修改,以及修改时如果需要向修饰器传递更多的被修饰对象的信息,则利用修饰器类的object.__call__(self,[args...])方法。该方法允许使用圆括号向其实例传递参数,并实现方法的调用。如下所示:
class myClass:
def __call__(self,*gargs):
print(args,end=' ')
print()
c=myClass() #创建myClass实例
c() #调用__call__,没有参数
c('1',[1,2,3]) #调用__call__,两个参数
有了该函数,就可以让被修饰对象向修饰器传递更多的参数。同时,修饰器对被修饰对象的修改也是在这个方法内实现的。如果被修饰对象是类,则可以在修饰器的__call__定义内嵌的类,完成修改后并将其返回;如果被修饰对象是函数,则可以在其中内嵌函数定义并将其返回。如下所示:
class cDec: #被修饰对象为类的修饰器
def __init__(self,cls): #cls为被修饰的类参数
pass
def __call__(self): #利用该方法完成对被修饰类的修改
class inClass: #修改后的类
def print():
print('inClass')
return inClass #返回被修改后的类
@cDec #定义被修饰的类
class ouClass:
def print():
print('ouClass')
c=ouClass() #生成ouClass实例
c.print() #调用c的方法,结果显示,已经被修改为inClass的方法了
class mDec: #被修饰对象为函数的修饰器
def __init__(self,func): #func为被修饰的函数参数
pass
def __call__(self):
def inFunc(): #修改后的函数
print('inFunc')
return inFunc()
@mDec #定义被修饰的函数
def ouFunc():
print('ouFunc')
ouFunc() #运行可以看出,ouFunc已经被inFunc取代
上面的两个例子中还应该注意的是,当被修饰对象为类时,应该返回类inClass,而不是返回类的实例inClass(),因为使用ouClass类时本身还有一个如c=ouClass()的实例化步骤,这时如果已经是一个实例而不是类的定义是错误的。而当被修饰对象为函数时,应该返回函数的运行inFunc()而不是返回函数定义inFunc,否则运行被替代后的ouFunc()实际仅仅是类mDec的一个内嵌函数定义,是无法运行的。