查看原文
其他

Python高级编程——装饰器Decorator超详细讲解(中篇)

草yang年华 机器学习与python集中营 2021-09-10

送你小心心记得关注我哦!!

进入正文

全文摘要

装饰器decorator,是python语言的重要特性,上面一章节详细讲解了python装饰器诞生的背景、装饰器的定义、装饰器的作用、装饰器的简单实现。但是装饰器的内容远远不止于此哦,要想从头到尾、从浅入深的理解,还需要很多的功夫,写着写着发现内容越写越多,所以在此重新声明,python高级编程——装饰器系列文章一共分为上、中、下、补充篇,本文讲解中篇,下篇和补充篇会在后面发出,请记得持续关注哦!文章偏长,阅读全文约20min。

上篇请参考:

Python高级编程——装饰器Decorator超详细讲解(上篇)

全文目录

01 python函数装饰器

    1.1 函数装饰器装饰函数

    1.2 函数装饰器装饰类

02 python类装饰器

    2.1 类装饰器装饰函数

    2.2 类装饰器装饰类

    2.3 装饰器的使用场景

03 类装饰器的一般“模板”

04 装饰器的缺点

    4.1 函数装饰器装饰函数的情况

    4.2 函数装饰器装饰类的情况

    4.3 类装饰器装饰函数的情况

    4.4 类装饰器装饰类的情况

05 python闭包详解(下篇预告)


python装饰器详解(中篇)


前面的一篇文章已经提到过,装饰器分为类装饰器、函数装饰器,它们所装饰的对象又可以是函数、可以是类,所以两两搭配,一共四种情况。本文将详细讨论着四种情况,深入揭示它们的本质,解释他们的优点和缺点。

01

python函数装饰器 

01 python函数装饰器1.1 函数装饰器  装饰函数


函数装饰器是最常见的,故而最先讨论。

注意:因为没有参数,且无函数返回值的函数是最为简单的,就如“上篇”所讨论的那样,这里就不再叙述了,本文主要讲的都是函数带有参数,而且具有函数返回值的函数。

     假定有一个函数实现两个数的相加,a+b,但是为了对这两个数相加的结果进行加密,我们需要给函数添加额外的代码,但是我们通过装饰器去实现,要达到的效果是,不是直接返回a+b的结果,而是进行进一步处理。代码如下:

def MethodDecoration(function):  #外层decorator
    c=150
    d=200
    def wrapper(a,b):            #内层wrapper。和add_function参数要一样
        result=function(a,b)
        result=result*c/d        #加密,相当于添加额外功能
        return result            #此处一定要返回值
    return wrapper

@MethodDecoration
def add_function(a,b):
    return a+b

result=add_function(100,300)    #函数调用
print(result)

运行结果为:300  (即(100+300)*150/200)

因为函数装饰器去装饰函数最为常见,所以这里就不多再解释了,按照前面上篇所讲的模板来即可,但是因为被装饰的函数有参数,而且具有返回值,有两个点需要注意的,

第一:wrapper需要保证与add_function参数一致。因为返回的wrapper就是add_function,所以要统一,我们可以使用*arg,和**args去匹配任何参数;

第二:wrapper一定要返回值。因为add_function函数是需要返回值的。

01 python函数装饰器1.2 函数装饰器  装饰类


在Python中,从某种意义上来说,函数和类是一样的,因为它们都是对象(python一切皆对象),故而decorator的参数理所当然也可以传入一个类了。其中最经典的应用,就是使用装饰器构造“单例模式”(不明白单例模式的小伙伴可以参见下面这篇博文哦)

一文详解“单例模式”及其python语言的实现

代码如下:

def Singleton(cls):   #这是第一层函数,相当于模板中的Decorator.目的是要实现一个“装饰器”,而且是对类型的装饰器
    '''
    cls:表示一个类名,即所要设计的单例类名称,
        因为python一切皆对象,故而类名同样可以作为参数传递
    '''

    instance = {}

    def singleton(*args, **kargs):  #这是第二层,相当于wrapper,要匹配参数
        if cls not in instance:
            instance[cls] = cls(*args, **kargs)   #如果没有cls这个类,则创建,并且将这个cls所创建的实例,保存在一个字典中
        return instance[cls]        #返回创建的对象

    return singleton

@Singleton
class Student(object):
    def __init__(self, name,age):
        self.name=name
        self.age=age

s1 = Student('张三',23)
s2 = Student('李四',24)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep='   ')

上面运行的结果是:

True

True

2150787003840   2150787003840

    懂得单例模式的小伙伴可能一看就明白了,上面的实现和我前面讲过的“装饰器模板”,基本上一样,第一层、第二层、返回值、参数匹配等。但是有的小伙伴可能会问,这里我没有看到“添加额外功能”啊,装饰器不是添加额外功能的么?实际上“添加额外功能”是一种抽象的表述,不是说一定要添加什么东西,对被装饰的对象(函数、类)进行某种约束、处理、添加、删减等额外操作统称为添加额外功能。 

   这里约束了这个类型Student的创建,主要这个类还没有创建实例,就创建一个,只要创建了,就不在创建新的实例了,只需要把之前创建的返回即可,这是单例模式的思想。如果还是不明白,下面再举一个“添加额外功能”的例子。


比如我有一个学生类,在创建学生实例的时候有两个实例属性,name,age,现在要通过装饰器对类加以装饰,使得在创建学生类的实例的时候,还会添加height和weight两个属性,代码如下:

def ClassDecorator(cls):  #第一层函数decorator
    height=170
    weight=65
    def wrapper(name,age): #第二层函数wrapper,参数要和类的构造函数匹配
        s=cls(name,age)
        s.height=height    #添加两个额外属性
        s.weight=weight
        return s           #返回创建的对象,因为类的构造函数是要返回实例的,即有返回值
    return wrapper

@ClassDecorator
class Student:
    def __init__(self,name,age):
        self.name=name
        self.age=age

stu=Student('张三',25)
print(stu.name)
print(stu.age)
print(stu.height)    #在 IDE中可能会有提示此处错误,学生没有height和weight属性,但是运行之后没错
print(stu.weight)    #这就是python的魅力,动态添加属性

运行结果为:

张三
25
170
65

上面的例子和我们前面讲的装饰函数实在是太像了,基本上和前面讲的模板一模一样。

总结:函数装饰器不管是装饰函数、还是装饰类,所遵循的思想原理是一样的,实现的方式也是大同小异。

注意:函数装饰器装饰类,实际上是装饰类的构造函数哦!


02

类装饰器 

前面定义的装饰器都是函数,实际上,类也可以是一个装饰器,同样的道理,类装饰器既可以装饰函数,也可以装饰类。

02 类装饰器2.1 类装饰器  装饰函数


先从一个简单的例子说起,代码如下:

class MethodDecorator:
    def __init__(self,function):
        self.function=function
    def __call__(self):
        print('开始')
        self.function()
        print('结束')

@MethodDecorator
def myfunc():
    print('我是函数myfunc')

myfunc()

运行结果如下:

开始

我是函数myfunc

结束

可能有的小伙伴很懵逼,怎么会得到这样的结果?我们一句一句来分析。

@MethodDecorator

def myfunc():

      pass

myfunc()#调用函数


这里相当于  myfunc=MethodDecorator(myfunc),这样一写就明白了,首先myfunc函数作为参数传递给类的构造函数,因为调用类的构造函数自然会返回类的一个实例对象,所以前面的myfunc实际上是类的一个实例对象,然后调用myfunc,这里虽然从形式上看依然是看起来还是调用函数,但是本质上已经发生了变化,它实际上一个对象调用(这是类装饰器的本质,很重要),这就是为什么要定义__call__魔法方法的原因。下面比如函数有返回值,而且有参数,要用类装饰器去装饰这个函数,再用一个实例说明,依然以上面的两个数据值和进行加密为例。


class MethodDecorator:
    def __init__(self,function):  #这里相当于是第一层,作用是将函数function传递进来
        self.function=function
        self.c=150      #这两个是需要加密的额外信息
        self.d=200
    def __call__(self,a,b):  #这相当于是第二层的wrapper
        print('开始')
        result=self.function(a,b)
        result=result*self.c/self.d
        print('结束')
        return result     #返回值

@MethodDecorator
def add_function(a,b):
    return a+b

result=add_function(100,300)  #这里相当于是函数调用
print(result)
运行结果为:
开始
结束
300.0
总结:实际上类装饰器所实现的功能在原理上和函数装饰器也没有太大的区别,但是在代码实现上有所区别,主要体现在两方面:
第一:类装饰器的构造函数__init__就相当于是第一层(外层)的decorator,传入需要装饰的对象作为参数;
第二:类装饰器的魔法方法__call__相当于是第二层(内层)的wrapper,注意参数要统一,有返回值需要返回值。

02 类装饰器2.2 类装饰器  装饰类


依然以上面的给学生添加额外属性为例:

class ClassDecorator:
    def __init__(self,cls):  #这里相当于是第一层,作用是将类名Student传递进来
        self.cls=cls
        self.height=170
        self.weight=65
    def __call__(self,name,age):  #这相当于是第二层的wrapper
        s=self.cls(name,age)
        s.height=self.height      #动态添加属性,增加额外信息
        s.weight=self.weight
        return s                  #返回创建的学生实例s

@ClassDecorator
class Student:
    def __init__(self,name,age):
        self.name=name
        self.age=age

stu=Student('张三',25)   #注意:这里的Student其实并不是一个类了,而是装饰器返回的一个对象,即这里的Student是ClassDecorator的实例
                        #而且,这里的Student('张三',25) 也不是构造函数了,它的本质是装饰类的“对象调用”
print(stu.name)
print(stu.age)
print(stu.height)
print(stu.weight)

运行结果为:

张三
25
170
65

总结:这里的Student其实并不是一个类了,而是装饰器返回的一个对象,即这里的Student是ClassDecorator的实例,而且,这里的Student('张三',25) 也不是构造函数了,它的本质是装饰类的“对象调用”。


03

类装饰器的一般“模板” 

03 类装饰器的一般模板3.1 类装饰器的一般“模板”


在上一篇文章中,已经给出了关于“函数装饰器”的一般模板,为了大家记忆和理解,本节会给出“类装饰器”的一般“模板”。通过上面的例子,不管类装饰器是装饰类,还是装饰函数,它的模板都是大同小异的,如下所示:

class ClassDecorator:        #类装饰器的名称
    def __init__(self,function_or_cls):  #这里相当于是第一层,作用是将需要装饰的类名、或者是函数名传递进来
        #这里可以添加额外信息
        self.cls=cls         #或者是self.function=function,本质是要构造 一个属性
        #这里可以添加额外信息
    def __call__(self,name,age):  #这相当于是第二层的wrapper,参数需要与被装饰的类、被装饰的函数,参数相同
        #这里可以增加额外信息
        s=self.cls(name,age)       #本质是调用原来的函数或者类的构造函数
        #result=self.function(a,b) 
        #这里可以增加额外信息
        return s                  #返回创建的学生实例s

注意:类装饰器,对象调用__call__是不可或缺的哦。


04

装饰器的缺点 


前面讲了一大堆装饰器的优点:简化代码,代码复用;增加额外功能等。那么装饰器优缺点吗,当然有了,世界上就没有完美无缺的东西!那到底有一些什么样的缺点呢?其实在上面的表述中已经提到了,不知道小伙伴有没有注意!

本节的所有代码也是分成四个小的部分,对应的代码分别就是上面部分实现的代码,这里就不再重复书写了。



04 装饰器的缺点4.1 函数装饰器  装饰函数的时候


在上面源代码的基础之上添加依据下面的代码:

print(add_function.__name__)

返回的结果是:wrapper

这是为什么,如果add_function没有被装饰器修饰的话,则返回的应该为add_function,这里为什么会返回第二层包装函数wrapper的名称?

这是因为装饰器的本质是add_function=MethodDecoration(add_function),而MethodDecoration返回的本来就是wrapper,这就是上面结果的解释了。

04 装饰器的缺点4.2 函数装饰器  装饰类的时候

同样添加一句代码:

print(Student.__name__)

返回的结果是:wrapper

出现这个现象的原因同上面1中所述的,完全一样。


04 装饰器的缺点4.3 类装饰器  装饰函数的时候

同样的添加下面一句话

print(add_function.__name__)  #这里IDE不会提示错误哦,IDE依然觉得这是个函数,应该有__name__才对的

显示:AttributeError: 'MethodDecorator' object has no attribute '__name__'。

这里为什么突然不一样了呢?正如前面所说的,这里的add_function本质上是add_function=MethodDecorator(add_function),所以add_function本质上是装饰类的一个实例,而MethodDecorator没有定义__name__属性,那自然调用add_function.__name__就会显示没有__name__这个属性了。


04 装饰器的缺点4.1 类装饰器  装饰类的时候
print(Student.__name__)  #这里IDE不会提示错误哦,IDE依然觉得这是个类名,应该有__name__才对的

显示:AttributeError: 'ClassDecorator' object has no attribute '__name__'

原因同上面一样,因为Student本质上是ClassDecorator的一个对象实例哦!

装饰器缺点

总结

(1)被函数装饰器所装饰的对象(函数、类)已经不再是它本身了,虽然从形式上看没有变化,本质上是函数装饰器的内部wrapper;

(2)被类装饰器所装饰的对象(函数、类)也不再是它本身了,虽然从形式上看没有变化,本质上是类装饰器的一个对象。

补充:关于装饰器的嵌套,装饰器与python闭包的关系,我会在系列文章:Python高级编程——装饰器Decorator详解(下篇)中继续讲解,有兴趣的继续关注!


下一篇预告:

下面篇文章的重点是闭包:

闭包的诞生背景、闭包的定义、特征、用途、代码实现、装饰器与闭包的联系和区别等内容。

有兴趣的小伙伴持续关注哦!

····

电子书籍分享

下载链接:

链接:https://pan.baidu.com/s/1yAAQ1kgqJfCOys0lmLDp2Q 

提取码:drii 

····

推 荐 阅 读

Python高级编程——装饰器Decorator超详细讲解(上篇)

福利来啦,python数据分析、数据挖掘资料免费分享,无套路哦!

python开发者必备,史上最详细的 ipython 教程——第二篇

一道人人都会的python题,考验你的编程思维,教你打开正确的学习方法



2018/11/30

Friday

小伙伴们,看了这篇文章之后是不是觉得又不一样的体会和收获呢?后面还有关于python闭包详细讲解和嵌套python装饰器的详细讲解哦,另外,文章末尾的这些资料一定要好好学啊。如果你有需要,就添加我的公众号哦,里面分享有海量资源,包含各类数据、教程等,后面会有更多面经、资料、数据集等各类干货等着大家哦,重要的是全都是免费、无套路分享,有兴趣的小伙伴请持续关注!







: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存