python自学

Python装饰器之多重装饰器

2019-08-18  本文已影响1人  西西里加西

多重装饰器

def decorator(func):
    def inner():
        print('This is inner')
        func()
    return inner    

@decorator
def func():
    print('This is func')
    
func() 
#func = inner   

​众所周知,使用装饰器装饰一个函数时,装饰器会将原函数当做一个参数,传进装饰器函数中,然后返回一个新的函数。那么当原函数被多个装饰器装饰时,它的参数传递顺序以及程序执行的顺序是怎样的呢?

def decorator_01(func):
    def inner_01():
        print('This is inner_01')
        func()
    return inner_01 
    
def decorator_02(func):    
    def inner_02():
        print('This is inner_02')
        func()
   return inner_02    

@decorator_02
@decorator_01
def func():
    print('This is func')
    
func()
#func = ?      

参数的传递顺序

  1. 首先,最里层的装饰器 @decorator_01 会先装饰原函数func,即原函数func会先被传进 @decorator_01 中
  2. 其次,外层装饰器 @decorator_02 会装饰里层的装饰器 @decorator_01 + 原函数func 返回的结果,@decorator_01 会返回的一个新函数 inner_01,这个新函数将被传进 @decorator_02
  3. 外层装饰器 @decorator_02 也会返回一个新的函数inner_02 ,这个函数将被赋值给原函数的函数名func,形成一个新的func函数
func --> decorator_01 --> decorator_02

从参数的传递顺序来看,是从内往外的一个过程,这是装饰的顺序所导致的。

虽然从代码上看,@decorator_02写在了@decorator_01之前,按照顺序执行的话,不应该是@decorator_02先生效吗?

并不是这样的,你可以想象成是一个已经打包好了的快递盒子,毫无疑问当你看到成品时,肯定会先看到最外层的包装纸,但是快递小哥肯定是从最里层开始打包的,也就是说,是最内层的装饰器最先开始装饰功能的。

程序的执行顺序

  1. 首先执行的是外层装饰器inner_02,因为这时候的func已经指向了inner_02了
  2. 在inner_02执行的过程中,被当做func参数传进decorator_02来的inner_01也会被执行
  3. 在inner_01执行的过程中,被当做func参数传进decorator_01来的原函数func也会被执行
new_func() <==> inner_02() --> inner_01() --> old_func()

从程序的执行顺序上看,这是一个从外到内的过程,这就相当于你收到了快递包裹,总是需要从最外层开始拆封。

案例分析

下面我们通过一个例子,深入了解下多重装饰器的运行逻辑。

def decorator_02(func):   
    print(func.__name__)
    def inner_02(*args):   
        print(args)
        func(*args)
        print('inner_02 is running')
    return inner_02

def decorator_01(num):
    def outer(func):   
        print(func.__name__)
        def inner_01(*args):  
            func(*args)
            for i in range(num):
                print('inner_01 is running')
        return inner_01
    print(outer.__name__)
    return outer

@decorator_02
@decorator_01(1)
def my_func(*args):     
    print(args)

my_func('hello,world', 'hello,python')
print(my_func.__name__)

运行结果

>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
>>> ('hello,world', 'hello,python')
>>> inner_01 is running
>>> inner_02 is running
>>> inner_02

结果说明

  1. 首先,程序开始执行 my_func('hello,world', 'hello,python'),按照my_func原本的定义,输出结果应该是 ('hello,world', 'hello,python') 的,但是程序会检测到装饰器的存在,所以程序会先对装饰器进行解析。
@decorator_02
@decorator_01(1)
def my_func(*args):     
    print(args)

my_func('hello,world', 'hello,python')
  1. 针对于多重装饰器的解析,我们要分析装饰函数中的参数传递问题,这样我们才能得到正确的执行顺序。
  1. 根据前面的讲解,内层的装饰器是最先被调用来装饰函数的,即 @decorator_01(1) 先生效。
@decorator_01(1)
def my_func(*args):     
    print(args)   
  1. 请注意这里的 @decorator_01(1) 是一个带参数的装饰器,我们可以看作是先执行decorator_01(1),执行的结果是除了把num参数传递了进去,还打印输出了函数名outer指向的函数对象,最后返回 outer函数。所以 outer 是最先输出的,它是 print(outer.__name__) 的执行结果,这里可以看出,decorator_01其实是一个伪装饰器函数,不然 outer应该指向被传进来的函数my_func才对,即 outer = my_func。这里真正的装饰函数应该是outer,在decorator_01(1)返回了outer 后,语法糖 @decorator_01(1) 就变成了 @outer,这才是我们熟悉的普通装饰器。
@decorator_01(1)
def my_func(*args):     
    print(args)

# 执行完 decorator_01(num) 后:
@outer
def my_func(*args):     
    print(args) 
>>> outer
  1. @outer 会继续被调用,outer函数将被执行。执行过程中,outer函数接受了一个func的形参,那这是传进来的实参是什么呢?没错,是 my_func,即 func = my_func,所以在执行print(func.__name__) 的时候,会得到第二行输出 my_func;并且还会返回函数inner_01。
def outer(func):   
    print(func.__name__)
    def inner_01(*args):  
        func(*args)
        for i in range(num):
            print('inner_01 is running')
    return inner_01
>>> outer
>>> my_func
  1. 至此,第一层装饰器完成调用。这里有同学可能会有疑问,这里不是新生成一个my_func函数吗,即 my_func = inner_01吗?别急,因为还有一层装饰器,这里不会出现 my_func = inner_01
  1. 接下看第二层装饰器:第二层装饰器 @decorator_02 被调用时,decorator_02函数将被执行,这里decorator_02同样也接受了一个形参func,那这次传进来的实参又是什么呢?答案是inner_01!这是因为在@decorator_01(1) 被调用过后,返回的结果是函数inner_01!这个可从print(func.__name__) 中验证这个结论,这里我们得到第三行输出 inner_01
def decorator_02(func):   
    print(func.__name__)
    def inner_02(*args):   
        print(args)
        func(*args)
        print('inner_02 is running')
    return inner_02
>>> outer
>>> my_func
>>> inner_01
  1. 函数decorator_02 将返回函数 inner_02,这是两层装饰器再被调用过后,最终的返回结果,这也是我们前面说到的,函数名my_func 将要指向函数对象,即 my_func = inner_02。至此,新的my_func 函数正式生成!
  1. 最后就是新的my_func 函数的执行:这时候执行my_func 函数,就相当于执行函数 inner_02。inner_02中的 print(args) 会将 my_func('hello,world', 'hello,python') 带的实参输出,即第四行输出结果是 ('hello,world', 'hello,python')。过后,到了 func(*args) 被执行,这里的func还记得是谁吗?不记得请看回上面的第七点,这里的func 应该是 inner_01,故函数inner_01会被执行。
def inner_02(*args):
     print(args)
     func(*args)
>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
  1. inner_01在执行的过程中,首先会执行 func(*args) ,那么这里的func又是谁呢?不记得请看回上面的第五点,这里的func 应该是原来的my_func函数,故原函数中定义的 print(args) 会被执行,得到第五行输出 ('hello,world', 'hello,python')。此时 inner_01还剩下一个for循环,由于我们装饰器@decorator_01(1) 传进去的参数是1,即 num = 1,所以这里只会循环一次 print('inner_01 is running') ,这便是第六行输出 inner_01 is running。inner_01执行完成。
def inner_01(*args):  
    func(*args)
    for i in range(num):
        print('inner_01 is running')
>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
>>> ('hello,world', 'hello,python')
>>> inner_01 is running
  1. 在inner_01执行完成后,别忘了,这仅仅只是执行了inner_02 中的 func(*args) 而已,inner_02 中还剩最后一部分 print('inner_02 is running') ,这里得到第七行输出 inner_02 is running
def inner_02(*args):   
    print(args)
    func(*args)
    print('inner_02 is running')
>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
>>> ('hello,world', 'hello,python')
>>> inner_01 is running
>>> inner_02 is running
  1. 至此,my_func('hello,world', 'hello,python') 就算是全部运算完了,最后我们再验证下my_func最后指向了什么,print(my_func.__name__) 后,得到最后一行输出 inner_02,这也验证了我们前面所讲的 my_func 将指向 inner_02!
print(my_func.__name__)
>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
>>> ('hello,world', 'hello,python')
>>> inner_01 is running
>>> inner_02 is running
>>> inner_02

关键说明

  1. 在多重装饰器被调用的时候,需要经过层层解析之后,才会把最后返回的那个函数赋值给原函数的函数名,形成一个新的函数
  2. 多重层装饰器的装饰应该是从内到外去调用的
  3. 一般情况下,最后返回那个函数一般是最外层的装饰器中定义的,这样代码的执行顺序看起来就是从外到内的
上一篇下一篇

猜你喜欢

热点阅读