zoukankan      html  css  js  c++  java
  • 装饰器

    首先晒下在群里问询一个装饰器执行顺序的问题,如下:

    你清楚这是怎么个执行顺序么?在刘大拿的Python群里,有老师帮我解答了这个顺序,我总结了下:

    执行test( ),因为被装饰了,等价于dec1(dec2(test)) ( )
    装饰器就是将函数作为参数来传递
    此时先执行装饰器dec2( )   test( )做为它的参数放进去
    输出aaaa
    读取函数dec2( )中的two( ) 因为没调用,所以没有任何输出值
    继续执行dec2( )返回值two   此时装饰器dec2已执行完毕得到结果就是two
    现在就相当于dec1(two)    two作为参数放进了函数dec1( )里去执行
    再执行装饰器 dec1( )
    输出1111
    读取函数dec1( )中的one( ) 因为没调用,所以没有任何输出值
    继续执行dec1( )返回值one   此时装饰器dec1已执行完毕 
    现在执行情况来看 就变成了 one( )     
    那么one( )是谁?正好在 dec1( )里有定义one( )函数
    就执行one( )函数去了
    输出2222
    继续执行下一条命令 即  a( )
    a( )函数是谁?看到dec1(a)没,它是dec1( )函数的参数啊,那之前得出这个参数的,就是two啊
    也就是执行a( )时候相当于 是two( )
    two( ) 正好在dec2( )里有定义two( )函数,好吧我们去执行two ( )函数去
    输出bbbb
    继续执行下一条命令 b( )  又来了,b( )函数是谁?看到dec2(b)没?
    它是dec2( )函数的参数啊,那之前得出这个参数的,就是test( )啊
    test ( )输出*******test*******
    继续执行two( ) 最后一条命令输出cccc   此时two( )函数执行完毕,也就是a ( )函数执行完毕
    现在执行one ( )函数最后一条命令输出3333 

    好了,现在我们开始讲解下装饰器,我分两种讲解,按照你自己理解看

    讲解一

    简言之,python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。 一般而言,我们要想拓展原来函数代码,最直接的办法就是侵入代码里面修改,例如:

    这是我们最原始的的一个函数,然后我们试图记录下这个函数执行的总时间,那最简单的做法就是:

    但是如果你的Boss在公司里面和你说:“小周,这段代码是我们公司的核心代码,你不能直接去改我们的核心代码。”那该怎么办呢,我们仿照装饰器先自己试着写一下:

    这里我们定义了一个函数deco,它的参数是一个函数,然后给这个函数嵌入了计时功能。然后你可以拍着胸脯对老板说,看吧,不用动你原来的代码,我照样拓展了它的函数功能。 
    然后你的老板有对你说:“小周,我们公司核心代码区域有一千万个func()函数,按你的方案,想要拓展这一千万个函数功能,就是要执行一千万次deco()函数,这可不行呀,我心疼我的机器。” 
    好了,你终于受够你老板了,准备辞职了,然后你无意间听到了装饰器这个神器,突然想起刘大拿说过:“我们不是为了赚钱,就为了干掉那些不良教育机构” 
    于是小白菜鸟般的我,燃气了斗志!先实现一个最简陋的装饰器,不使用任何语法糖和高级语法,看看装饰器最原始的面貌:

    这里的deco函数就是最原始的装饰器,它的参数是一个函数,然后返回值也是一个函数。其中作为参数的这个函数func()就在返回函数wrapper()的内部执行。然后在函数func()前面加上@deco,func()函数就相当于被注入了计时功能,现在只要调用func(),它就已经变身为“新的功能更多”的函数了。 
    所以这里装饰器就像一个注入符号:有了它,拓展了原来函数的功能既不需要侵入函数内更改代码,也不需要重复执行原函数。

    然后你满足了Boss的要求后,Boss又说:“小周,我让你拓展的函数好多可是有参数的呀,有的参数还是个数不定的那种,你的装饰器搞的定不?”然后你嘿嘿一笑,深藏功与名!

    最后,你的老板说:“大拿徒弟就是牛逼啊!小周我这里一个函数需要加入很多功能,一个装饰器怕是搞不定,装饰器能支持多个吗?” 最后你就把这段代码丢给了他

    多个装饰器执行的顺序就是从最后一个装饰器开始,执行到第一个装饰器,再执行函数本身

    讲解二

    1. 什么是装饰器?

    顾名思义,装饰器就是在方法上方标一个带有@符号的方法名,以此来对被装饰的方法进行点缀改造。

    当你明白什么是装饰器之后,自然会觉得这个名字取得恰如其分,但作为初学者来说多少还是会有些迷茫。下面用代码来说明怎么理解装饰器。

    #脚本1
    def target():
        print('this is target')
    
    def decorator(func):
        func()
        print('this is decorator')
    
    decorator(target)
    -------------------------------------------
    
    运行结果为:
    
    this is target
    this is decorator

    Python允许将方法当作参数传递,因此以上脚本就是将target方法作为参数传入decorator方法中,这其实也是装饰器的工作原理,以上代码等同于:

    #脚本2
    def decorator(func):
        func()
        print('this is decorator')
    
    @decorator
    def target():
        print('this is target')
    
    target
    -------------------------------------------
    运行结果:
    
    this is target
    this is decorator

    因此可以看出,所谓的装饰器就是利用了Python的方法可作参数传递的特性,将方法target作为参数传递到方法decorator中。

    @decorator
    def target():
        ...

    这种在一个方法的上方加一个@符号的写法,就是表示位于下方的方法将被作为参数传递到位于@后面的decorator方法中。使用@符号只是让脚本1中的代码换了一个写法,更加好看,当然也会更加灵活与实用,后面会讲到这点。 但它们的本质其实是一样的,这也就是装饰器的工作原理。

    2. 装饰器的原理

    如果你仔细看的话,会在脚本2中发现一个问题,那就是脚本2中最后一行的target只是一个方法名字,它不是正确的方法调用,正确写法应该加上左右括号的target(),如下:

    #脚本3
    
    def decorator(func):
        func()
        print('this is decorator')
    
    @decorator
    def target():
        print('this is target')
    
    target()
    --------------------------------------------
    运行结果:
    
    this is target
    this is decorator
    Traceback (most recent call last):
      File "C:/Users/Me/Desktop/ff.py", line 34, in <module>
        target()
    TypeError: 'NoneType' object is not callable
    

     正如你所看到的,如果按照正确的写法,运行结果你会看到应该出现的两行打印文字"this is target"和"this is decorator",还会出现错误提示,ff.py是我为写这篇心得临时编写的一个py脚本名字,提示说'NoneType'对象不可调用。这是怎么回事?好吧,我现在必须告诉你,其实脚本2和脚本3中并不是一个使用装饰器的正确写法,不是使用错误,而是作为装饰器的decorator方法写的并不友好,是的,我不认为它是错误的写法,只是不友好。但只要你明白其中的道理,使用恰当的手段也是可以运行正常的,这就是为什么脚本2看似写错了调用方法却得出了正确的结果。当然学习还是得规规矩矩,后面我会具体说正确的装饰器怎么书写,在这里我先解释了一下脚本2和脚本3的运行原理,了解它们的运行原理和错误原因,其实就是了解装饰器的原理。

    脚本2和脚本3的区别在于target和target(),也就是说真正的差别在于()这个括号。当()被附加在方法或者类后面时,表示调用,或者称为运行及实例化,无论称呼怎样,本质意义没有不同,都是调用给出的对象,当对象不具备调用性的时候,就会报错:'某个类型' object is not callable。当一个方法被调用后,即target(),是否能被再次执行,取决于它是否会return一个对象,并且该对象可以被调用。也许你会有点迷糊,对比一下代码会比较容易理解我想表达的意思:

     1 >>>def returnme():
     2 >>>    print('this is returnme')
     3  
     4 >>>def target():
     5 >>>    print('this is target')
     6   
     7 >>>target
     8 <function target at 0x00000000030A40D0>
     9   
    10 >>>target()
    11 target
    12 <function returnme at 0x00000000030A4268>
    13  
    14 >>>target()()
    15 target
    16 returnme
    17 
    18 >>>returnme()()
    19 returnme
    20 Traceback (most recent call last):
    21   File "<pyshell#15>", line 1, in <module>
    22     returnme()()
    23 TypeError: 'NoneType' object is not callable

    如上所示,当直接在脚本中输入target,它只是告诉编译器(我想是编译器吧,因为我也不是很懂所谓编译器的部分),总之就是告诉那个不知道在哪个角落控制着所有python代码运行的“大脑”,在

    0x00000000030A40D0位置(这个位置应该是指内存位置)存有一个function(方法)叫target;在target后面加上(),表示调用该方法,即输入target(),“大脑”便按照target方法所写的代码逐条执行,于是打印出了target字符串,并且“大脑”明白在0x00000000030A4268位置有一个叫returnme的方法;因为target对象调用后是会返回一个returnme方法,并且方法是可以被调用的,因此你可以直接这样书写target()(),“大脑”会逐条执行target中的代码,然后return一个returnme,因为多加了一个(),表示要对返回的returnme进行调用,于是再次逐条执行returnme中的代码,最后便能看到15、16的打印结果;而returnme方法是没有返回任何可调用的对象,因此当输入returnme()()时,“大脑”会报错。

    下面我们可以来解释一下脚本2和脚本3的运行详情,之前说过,装饰器的工作原理就是脚本1代码所演示的那样。

    @decorator
    def target():
        ...
    
    等同于
    
    def decorator(target)():
        ...
    
    注:python语法中以上写法是非法的,以上只是为了便于理解。

    当你调用被装饰方法target时,其实首先被执行的是作为装饰器的decorator函数,然后“大脑”会把target方法作为参数传进去,于是:

    #脚本2
    def decorator(func):
        func()
        print('this is decorator')
      
    @decorator
    def target():
        print('this is target')
      
    target
    -------------------------------------------
    
    实际运行情况:
    首先调用decorator方法:decorator()
    因为decorator方法含1个参数,因此将target传入:decorator(target)
    运行代码“func()”,根据传入的参数,实际执行target(),结果打印出:this is target
    运行代码"print('this is decorator')",结果打印出:this is decorator

    对比脚本3的运行情况

    #脚本3
    def decorator(func):
        func()
        print('this is decorator')
      
    @decorator
    def target():
        print('this is target')
      
    target()
    -------------------------------------------
    
    实际运行情况:
    首先调用decorator方法:decorator()
    因为decorator方法含1个参数,因此将target传入:decorator(target)
    运行代码“func()”,根据传入的参数,实际执行target(),结果打印出:this is target
    运行代码"print('this is decorator')",结果打印出:this is decorator
    
    以上与脚本2中运行情况完全相同,接下来便是执行脚本2中target没有的(),也就是执行调用命令。
    由于decorator(target)没有返回一个可以被调用的对象,因此“大脑”提示错误:'NoneType' object is not callable

    如果你还不是很清楚,请看下面的等价关系:

    @decorator
    def target():
        ...
    
    等同于
    def decorator(target)():
        ...
    
    因此:
    target == decorator(target)
    target() == decorator(target)()
    
    所以:
    假设有一个变量var=target,在将target赋值给var时,其实是将decorator(target)的调用结果赋值给var,因为var不具备调用性(not callable),因此执行var()时,编译器会报错它是个NoneType对象,不能调用。

    综上所述,你大概已经能够明白所谓的装饰器是怎么一回事,它是怎么工作的。但脚本2和脚本3中的写法会带来一些困惑,这个困惑就是通过我们编写的decorator装饰器对target进行装饰后,将target变成了一个永远不能被调用的方法,或者说变成了一个调用就报错的方法。这跟我们的使用习惯以及对方法的认识是很不协调的,毕竟我们还是习惯一个方法天生注定可以被调用这种看法。所以为了满足我们对方法的定义,我们最好将作为装饰器的方法写成一个可以返回具有被调用能力的对象的方法。

    #脚本4
    def whatever():
        print('this is whatever')
    
    def decorator(func):
        func()
        print('this is decorator')
        return whatever  #1
    
    @decorator
    def target():
        print('this is target')
    
    ------------------------------
    输入:target
    结果:
    this is target
    this is decorator
    
    输入:target()
    结果:
    this is target
    this is decorator
    this is whatever

    在#1的位置,你可以return任何可以被调用的方法或类,甚至你可以直接写成:

    def whatever():
        print('this is whatever')
    
    def decorator(func):
        return whatever
    
    @decorator
    def target():
        print('this is target')
    
    ------------------------------
    输入:target
    结果:告诉编译器在内存某个位置有一个叫whatever的方法
    
    输入:target()
    结果:this is whatever

    以上装饰器的作用就是将target方法,完完全全变成了whatever方法。但这只能完美解释装饰器的功效,在实际当中则毫无意义,为什么要辛辛苦苦写了一大堆代码之后,最后的结果却是完完整整地去调用另一个早已存在的方法?如果要调用whatever方法,我为什么不在我的代码中直接写whatever呢?

    装饰器,顾名思义,它就是要对被装饰的对象进行一些修改,通过再包装来达到不一样的效果,尽管它可以将被装饰对象变得面目全非或者直接变成另一个对象,但这不是它被发明出来的主要意义,相反它被发明出来是帮助我们更高效更简洁的编写代码,对其他方法或类进行装饰,而非摧毁它们。例如对接收数据的检校,我们只要写好一个校验方法,便可以在其他许多方法前作为装饰器使用。

    3. 常规的装饰器

    从广义上来说,装饰器就是脚本1中,利用python可以将方法传参的特性,用一个方法去改变另一个方法,只要改变成功,都可以认为是合格的装饰器。但这是理论上的合格,毕竟装饰器是一种语法糖,应该是为我们带来便利而不是无用功,所以:

    1. 将方法装饰成不能被调用,不好:
    def decorator(func):
        func()
        print('this is decorator')
    
    2. 将原方法彻底消灭,直接变成另一个方法,不好:
    def decorator(func):
        return whatever
    
    3. 保留原方法的同时再加上别的功能,不好:
    def decorator(func):
        func()
        print('this is decorator')
        return whatever

    在以上3种写法中,前两种明显不好,简直就是将装饰器变成了恶魔。而第3种写法,虽然看起来重新包装了被修饰方法,但却在方法调用前擅自执行了一些命令,即当你输入target,而非target()时:

    #脚本4
    def whatever():
        print('this is whatever')
    
    def decorator(func):
        func()
        print('this is decorator')
        return whatever  #1
    
    @decorator
    def target():
        print('this is target')
    
    ------------------------------
    输入:target
    结果:
    this is target
    this is decorator

    你尚未执行target(),编译器却已经打印了两行字符串。这并不是我们想要的,当我们在代码中写下target时,我们是不希望编译器立即执行什么命令,我们是希望编译器在碰到target()时才执行运算。而且如果我们并不希望返回whatever,我们只想要通过装饰器,使得target方法除了打印自己的"this is target",再多打印一行"this is decorator”,所有代码只含有target和decorator两个方法,无其他方法介入,应该怎么办?

    当你这样问的时候,其实就是已经开始了解装饰器存在的意义了。以下便是为解决这些问题的装饰器的常规写法:

    #脚本5
    
    def decorator(func):
        def restructure():    
            func()
            print('this is decorator')
        return restructure
    
    @decorator
    def target():
        print('this is target')
    

    是的,从最外层讲,以上代码其实只有两个方法,decorator和target——装饰和被装饰方法。但在decorator内部内嵌了一个方法restructure,这个内嵌的方法才是真正改造target的东西,而decorator其实只是负责将target传入。这里的restructure,相当于在脚本4中,被写在decorator外面的whatever角色,用装饰器的常规写法也可以写出脚本4的效果,如下:

    def decorator(func):   
        func()
        print('this is decorator')
    
        def whatever():
            print('this is whatever')     
        return whatever
    
    @decorator
    def target():
        print('this is target')
    

    对比以上的写法你会发现,python的方法传参和类的继承性质很相似,在decorator之内,whatever之外,你可以写入任何代码,当执行target时,就开始初始化decorator,也就是执行decorator内部可以执行的代码;当执行target()时,会对初始化之后的decorator开始调用,因此这就要求decorator完成初始化之后必须返回一个具备调用性的对象。所以,除非你想要target方法初始化之前(实际上是对decorator进行初始化)就执行一些代码,否则不要在decorator和whatever中间插入代码。

    正常情况下,当decorator完成初始化,应该return一个可调用对象,也就是脚本5中的restructure方法,这个方法就是替代target的克隆人,在restructure中你可以对target进行重写,或其他代码来包装target。因此你只是想初始化target的话(实际就是对restructure初始化),就应将你要初始化的代码写入到restructure内部去。另外你也可以在decorator中内嵌多个方法,或多层方法,例如:

    #脚本6
    
    def decorator(func):
        def restructure():    
            func()
            print('this is decorator')
    
        def whatever():
            func()
            print('this is whatever')
    
        return restructure
    
    @decorator
    def target():
        print('this is target')

    被decorator装饰的target最后会多打印一行'this is decorator'还是'this is whatever’,取决于decorator方法return的是它内部的哪一个方法(restructure或whatever),因此以上代码等价于以下写法:

    执行target()
    
    等同于
    
    首先初始化decorator(target),结果返回一个restructure,即:target == decorator(target) == restructure。
    
    然后调用,即:target() == decorator(target)() == restructure()
    
    与类一样,当target被传入decorator之后,作为decorator内嵌方法是可以调用(继承)target方法的,这就是为什么restructure不用接受传参就可以改造target的原因。
    
    注:在python中,以上多个()()的写法是非法的,这样写只是为了便于理解。

    装饰器这个概念本来也可以设计成多个()这样的形式,但这样就破坏了python的基本写法,而且不好看,尤其当有多重装饰的时候,因此使用@置于方法前的设计是更加优雅和清晰的。

    我之所以使用多个()这样并不被python支持的写法来阐述我对python的装饰器的理解,是因为我相信通过这样被python放弃的语法也能更好地帮助你理解python的继承、传参以及装饰器,尤其是带有参数的装饰器。

    4. 装饰带有参数的方法

    首先请看以下代码:

    def target(x):
        print('this is target %s'%x)
    
    def decorator(func,x):
        func(x)
        print('this is decorator %s'%x)
    
    decorator(target,'!')
    
    等同于:
    
    def decorator(func):
        def restructure(x):
            func(x)
            print('this is decorator %s'%x)
        return restructure
    
    @decorator
    def target(x):
        print('this is target %s'%x)
    
    target('!')

    target(x)中的参数x是如何传入,什么时候传入装饰器的?首先尝试以下代码:

    def decorator(func):
        print(x)  #增加一行代码
        def restructure(x):
            func(x)
            print('this is decorator %s'%x)
        return restructure
    
    @decorator
    def target(x):
        print('this is target %s'%x)
    
    target('!')
    

    此时编译器会报错参数x没有被定义,也就是说在初始化decorator的时候,只有target方法被传入,其参数x='!'并没有传入。

    现在让我们回顾一下之前说的装饰器工作原理,如下:

    target == decorator(target) == restructure。
    
    target() == decorator(target)() == restructure()
    
    同理:
    
    target(x) == decorator(target)(x) == restructure(x)
    

     所以,你可以很清楚地了解到,作为target的参数,其实不是传给decorator,而是传给初始化完decorator之后return的restructure。因此,如果装饰器写成如下代码:

    def decorator(func):
        def restructure():    #不带参数的方法
            func(x)
            print('this is decorator %s'%x)
        return restructure

    此时你输入target('!'),编译器会告诉你restructure没有参数,但被传入了1个参数,这个被传入的参数就是x='!'。

    所以你现在明白了被装饰的方法target(x),方法target和参数x是如何被传入的,所以你必须保证初始化decorator之后返回的对象restructure方法形参与被装饰的target方法形参相匹配,即:

    如果定义为:def target(x)
    则装饰器中:def restructure(x)
    
    如果定义为:def target(x,y)
    则装饰器中:def restructure(x,y)

    你也许会发现如果想装饰器同时装饰target(x)和newtarget(x,y),以上写法就无法满足要求了。因此为了让装饰器可以适用于更多的对象,我们最好让装饰器写成如下形式:

    def decorator(func):
        def restructure(*x):
            func(*x)
            print('this is decorator')
        return restructure
    
    @decorator
    def target(x):
        print('this is target %s'%x)
    
    @decorator
    def newtarget(x,y):
        print('this is target %s%s'%(x,y))
    
    target('!')
    newtarget('!','?')

    利用python的带星号参数语法(*arg),你便可以传入任意数量的参数,你也可以设置带双星号的形参(**arg),便可以传入字典形式的参数,单星形参和双星形参可以同时使用,如:def restructure(*arg, **arg)。

    5. 带有参数的装饰器

    只要记住以上的装饰器工作原理,你便可以知道如何写出带有参数的装饰器,如:

    def newdecorator(i):
        def decorator(func):
            def restructure(x):
                func(x)
                print('this is decorator %s%s'%(i,x))
            return restructure
        return decorator
    
    @newdecorator('?')
    def target(x):
        print('this is target %s'%x)
    
    target('!')
    -------------------------------------------------------
    结果:
    this is target !
    this is decorator ?!

    以上代码实际上是:

    target(x) == newdecorator(i)(target)(x) == decorator(target)(x) == reconstructure(x)
    

    同理,为了满足不同数量的参数传入,你也可以将newdecorator(i)写成newdecorator(*i, **ii)。

  • 相关阅读:
    AppleScript
    iOS 架构之文件结构
    Swift
    ERROR ITMS-90032: "Invalid Image Path
    ios中微信原生登陆的坑,ShareSDK的坑
    ios中OC给js传值的方法
    mac电脑中xcode怎么恢复还原快捷键设置
    ios 中 数组、字典转成json格式上传到后台,遇到的问题
    ios 中长按图片或者二维码,保存图片到手机的方法
    ios 中 Plus屏幕适配的问题,xib创建的cell在 Plus出现被拉大的情况
  • 原文地址:https://www.cnblogs.com/outmanxiaozhou/p/9565312.html
Copyright © 2011-2022 走看看