Python装饰器深入全面理解(7)

在上面代码中,我们同时用func和timmer两个装饰器来装饰do_something,从运行结果中可以看出,两个装饰器都发挥了作用。同时,为了方便大家理解,我们使用不带@符号的来使用两个装饰器装饰,两者运行结果是一样的,结合代码中的输出标记,我们来分析一下装饰器的执行过程:开始运行后,1->4->7这几个过程我相信大家都是可以理解的,到了位置7后,遇到了@符号标识的装饰器,而且是多层的,两个@装饰器相当于func(timmer(do_something_2)),所以是先执行timmer函数获取返回值作为参数传递给func,所以有了7之后是5->6,timmer函数返回值是inner_timmer函数,这时候就相当于func(inner_timmer),所以程序退出timmer函数后进入func函数,就有了2->3,从func函数返回后,继续向下执行遇到位置8,然后就进入了主函数运行,所以是8->9,此时的函数是被装饰过的,本质已经是func函数返回的inner_func函数了,所以最终在主函数中执行do_something时执行的是inner_func方法,所以先输出了func装饰器的函数开始提示,然后才是timmer装饰器的计时开始提示。

嗯,有点复杂!

3.5 类装饰器

通过上面的介绍,我想你已经知道,@符号是装饰器的一个表示,或者说一个装饰器语法糖,当使用@时,例如@A,这种语法糖会自动将被装饰函数f作为参数传递给A函数,然后将A函数的返回值重新f给f这个变量名,这就是@语法糖帮我们做的事情,概括来说就是f=A(f)()。

我们现在发散一下思维,假设A如果是一个类会怎么样呢?我们知道当A是一个类时,A()表示调用A类的构造函数__init__实例化一个A类对象,那么A(f)就表示将函数f作为参数传递给A类的构造方法__init__来构造一个A类实例对象,如果使用了@符号,那么这种语法机制就还会在A(f)后面加一个括号变成A(f)(),这是什么鬼?执行一个类实例对象?不过话要说回来,如果A(f)()这种结构要是没有问题,能够成功执行,是不是就意味着Python中类也可以成为装饰器了呢?确实如此。我们先看看通过A()()执行一个类实例对象会怎么样:

class A(object): def __init__(self): print('实例化一个A类对象') def __call__(self, *args, **kwargs): print('__call__方法被调用……') if __name__ == '__main__': A()()

输出结果:

实例化一个A类对象

__call__方法被调用……

看到没,通过A()()执行一个类实例对象时,执行的是A类内部的__call__方法。那么如果用A用作装饰器时,@A返回的就是A类内部定义的__call__方法,相当于函数装饰器func内的inner_func。来,我们感受一下类装饰器:

class A(object): def __init__(self, f): print('实例化一个A类对象……') self.f = f def __call__(self, *args, **kwargs): print('{}函数开始运行……'.format(self.f.__name__)) self.f(*args, **kwargs) print('{}函数结束运行……'.format(self.f.__name__)) @A def do_something(name): print('你好,{}!'.format(name)) if __name__ == '__main__': do_something('姚明')

输出结果:

实例化一个A类对象……

do_something函数开始运行……

你好,姚明!

do_something函数结束运行……

如果类装饰器带参数呢?这时候,类装饰器的参数也可定是通过@A(t)的形式传递,这时候,因为@语法糖会自动加括号的原因,结构就编程这样A(t)(),A(t)是类实例对象,A(t)()就是__call__方法,所以,@语法糖会把被装饰函数f作为参数传递给__call__方法,被装饰函数的参数需要在__call__内部定义一个函数来接受。也就是话说,定义类装饰器时,装饰器的参数通过__init__构造方法接收,被装饰函数的作为参数被__call__方法接收,。(这段子很绕,有点烧脑,没点儿Python基础还真不好理解,语言表达能力优先感觉要枯竭了)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/b06b6a34177ef29eb92256fd117164fe.html