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 = ?
参数的传递顺序
- 首先,最里层的装饰器 @decorator_01 会先装饰原函数func,即原函数func会先被传进 @decorator_01 中
- 其次,外层装饰器 @decorator_02 会装饰里层的装饰器 @decorator_01 + 原函数func 返回的结果,@decorator_01 会返回的一个新函数 inner_01,这个新函数将被传进 @decorator_02
- 外层装饰器 @decorator_02 也会返回一个新的函数inner_02 ,这个函数将被赋值给原函数的函数名func,形成一个新的func函数
func --> decorator_01 --> decorator_02
从参数的传递顺序来看,是从内往外的一个过程,这是装饰的顺序所导致的。
虽然从代码上看,@decorator_02写在了@decorator_01之前,按照顺序执行的话,不应该是@decorator_02先生效吗?
并不是这样的,你可以想象成是一个已经打包好了的快递盒子,毫无疑问当你看到成品时,肯定会先看到最外层的包装纸,但是快递小哥肯定是从最里层开始打包的,也就是说,是最内层的装饰器最先开始装饰功能的。
程序的执行顺序
- 首先执行的是外层装饰器inner_02,因为这时候的func已经指向了inner_02了
- 在inner_02执行的过程中,被当做func参数传进decorator_02来的inner_01也会被执行
- 在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
结果说明
- 首先,程序开始执行
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')
- 针对于多重装饰器的解析,我们要分析装饰函数中的参数传递问题,这样我们才能得到正确的执行顺序。
- 根据前面的讲解,内层的装饰器是最先被调用来装饰函数的,即
@decorator_01(1)
先生效。
@decorator_01(1)
def my_func(*args):
print(args)
- 请注意这里的
@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
- @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
- 至此,第一层装饰器完成调用。这里有同学可能会有疑问,这里不是新生成一个my_func函数吗,即
my_func = inner_01
吗?别急,因为还有一层装饰器,这里不会出现my_func = inner_01
。
- 接下看第二层装饰器:第二层装饰器 @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
- 函数decorator_02 将返回函数 inner_02,这是两层装饰器再被调用过后,最终的返回结果,这也是我们前面说到的,函数名my_func 将要指向函数对象,即
my_func = inner_02
。至此,新的my_func 函数正式生成!
- 最后就是新的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')
- 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
- 在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
- 至此,
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
关键说明
- 在多重装饰器被调用的时候,需要经过层层解析之后,才会把最后返回的那个函数赋值给原函数的函数名,形成一个新的函数
- 多重层装饰器的装饰应该是从内到外去调用的
- 一般情况下,最后返回那个函数一般是最外层的装饰器中定义的,这样代码的执行顺序看起来就是从外到内的