Python装饰器之多重装饰器

多重装饰器

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. 一般情况下,最后返回那个函数一般是最外层的装饰器中定义的,这样代码的执行顺序看起来就是从外到内的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容