你不一定全知道的四种Python装饰器实现详解

更新时间:2020-12-22 14:29:36点击次数:1009次
函数的函数装饰器
装饰器包含装饰对象和被装饰对象,最简单的装饰器是用装饰器函数装饰被装饰函数,在这种场景下,装饰器为函数装饰器,被装饰对象也是函数。
2.1、概述
函数装饰器就是一个特殊的函数,该函数的参数就是一个函数,在装饰器函数内重新定义一个新的函数,并且在其中执行某些功能前后或中间来使用被装饰的函数,最后返回这个新定义的函数。装饰器也可以称为函数的包装器,实际上就是在被装饰的函数执行前或后增加一些单独的逻辑代码,以使得被装饰函数执行后最终的结果受到装饰函数逻辑的影响以改变或限定被装饰函数的执行结果。
2.2、装饰器定义语法
@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)
2.4、多层装饰器的使用
在一个函数外,可以顺序定义多个装饰器,类似如:
@decorator1
@decorator2
@decorator3
def originalFunction(*args,**kwargvs):
    函数体
这种多个装饰器实际上就是叠加作用,且在上面的装饰器是对其下装饰器的包装,以上定义语句效果等同于如下语句:
originalFunction = decorator3(originalFunction)
originalFunction = decorator2(originalFunction)
originalFunction = decorator1(originalFunction)
也即等价于:
originalFunction = decorator1(decorator2(decorator3(originalFunction)))
三、类的函数装饰器
3.1、定义
函数装饰器除了给函数加装饰器(使用函数名作为装饰器函数的参数)外,还可以给类加函数装饰器,给类加函数装饰器时,将类名作为装饰器函数的参数,并在装饰器函数内定义一个类如wrapClass,该类称为包装类,包装类的构造函数中必须调用被装饰类来定义一个实例变量,装饰器函数将返回包装类如wrapClass。
3.2、类的函数装饰器案例1
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("fun1存在关键字参数:",k)
        print("被装饰类fun1方法执行完毕")
    def fun2(self,*a, **k):
        print("我是被装饰类的fun2方法")
针对以上被装饰函数装饰的类wrappedClass,我们执行如下语句:
>>> c1 = wrappedClass('testPara',a=1,b=2)
我是被装饰类的构造方法
构造方法存在位置参数: ('testPara',)
构造方法存在关键字参数: {'a': 1, 'b': 2}
被装饰类构造方法执行完毕
>>> c1.fun1()
准备调用被装饰类的方法fun1
我是被装饰类的fun1方法
被装饰类fun1方法执行完毕
调用被装饰类的方法fun1完成
>>> c1.fun2()
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    c1.fun2()
AttributeError: 'wrapClass' object has no attribute 'fun2'
>>> 
可以看到被装饰类的相关方法必须在装饰类中调用才能执行,装饰后的类如果装饰函数定义类时未定义被装饰类的同名函数,在装饰后返回的类对象无法执行被装饰类的相关方法。
3.3、类的函数装饰器案例2
上面的案例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)
    print("老猿Python博客文章目录:https://blog.csdn.net/LaoYuanPython/article/details/109160152,敬请关注同名微信公众号")
定义后执行相关调用情况如下:
>>> f = fun('funcation1',a=1,b=2)
执行被装饰函数
我是函数fun,带参数: ('funcation1',) {'a': 1, 'b': 2}
老猿Python博客文章目录:https://blog.csdn.net/LaoYuanPython/article/details/109160152,敬请关注同名微信公众号
>>> 
五、类的类装饰器
前面分别介绍了函数的函数装饰器、类的函数装饰器、函数的类装饰器,但公开资料中未查到是否可以有类的类装饰器,即装饰器和被装饰对象都是类。老猿参考类的函数装饰器、函数的类装饰器最终确认类的类装饰器也是可以支持的。
5.1、实现要点
要实现类的类装饰器,按老猿的研究,类的装饰器类的实现需要遵循如下要点:
装饰器类必须实现至少两个实例方法,包括__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)创建被装饰类的一个对象,并返回该对象。
按照以上步骤创建的类装饰器,就可以用于装饰其他类。当然上述方法只是老猿自己研究测试的结论,是否还有其他方法老猿也不肯定。
5.2、类的类装饰器案例
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}公里")
        print("老猿Python博客文章目录:https://blog.csdn.net/LaoYuanPython/article/details/109160152,敬请关注同名微信公众号")
c = car('爱丽舍','1.2吨',8)
c.driver(10)
c.driver(110)

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息