赞
踩
一直以来只是单纯地使用装饰器,并没有深究过其执行过程,或者说之前没有死磕,这两天重拾python的基础学习,在这一块儿花了点功夫,把此时的理解记下。
在谈装饰器之前,先理解闭包(closure)的概念:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包。(这一段以后再来补充)
仔细观察装饰器的结构,无非是在一个函数内部定义了另外一个函数,因此,先来说明这种内嵌函数的执行(调用)过程。
例1.内嵌函数例子
def foo():
def bar():
print('bar() called')
print ('foo() called')
bar()
>>>foo()
foo() called
bar() called
上述过程总的来说是很自然的,其中bar()
是在foo()
中通过显式调用的方式执行的。为此,做一点小小的改变,将该过程改为通过return
来调用。
例2.内嵌函数变型1
def foo():
def bar():
print('bar() called')
print ('foo() called')
return bar()
>>>foo()
foo() called
bar() called
结果和例1是一样的,有了上述基础,上面foo()
函数返回的是bar
的调用,我们也可以返回其引用,并在需要的时候调用它,如例3所示。
例3.内嵌函数变型2
def foo():
def bar():
print('bar() called')
print ('foo() called')
return bar
>>>f = foo()
foo() called
>>>f()
bar() called
例3中,先将foo()
的引用赋给f
,即f=bar
;然后执行f()
,即执行bar()
。有了这个基础,现在看一个最简单的装饰器例子。
例4.简单的装饰器
def dec(func):
@functools.wraps(func) # 加这句是为了防止装饰器对被装饰函数的影响
def wrapper(*args,**kwargs):
print('this is a wrapper')
return func(*args,**kwargs)
return wrapper
@dec
def foo():
print('foo() called')
foo()
保存为test.py
,python test.py
运行结果如下:
this is a wrapper
foo() called
首先说明@dec
的含义,这可以看作是foo=dec(foo)
的一种简写(这其实类似于数学中的函数复用),既然如此,那么在foo()
前加上@dec
相当于foo=wrapper
。剩下的就是类似例3的过程了,首先执行装饰器内的打印语句,然后返回foo()
,执行真实的foo()
内容。实际上上述过程省略了一个重要的点,那就是装饰器函数在被装饰函数定义好后立即执行,这个如何理解呢,可以理解为当被装饰函数定义好后,即执行了foo=dec(foo)
操作,因此实际上在例4中,可以将上例稍做修改,再去掉最后一行的foo()
例5.简单的装饰器变形
def dec(func):
print('this is dec')
@functools.wraps(func) # 加这句是为了防止装饰器对被装饰函数的影响
def wrapper(*args,**kwargs):
print('this is a wrapper')
return func(*args,**kwargs)
return wrapper
@dec
def foo():
print('foo() called')
然后python test.py
,其结果如下:
this is dec
在例5中,我们并没有调用foo()
,但是装饰器的外部函数其实已经执行了,也即foo=dec(foo)=wrapper
操作其实已经执行完成,之后进行foo()
的调用就是执行该装饰器内部函数的过程。至此,一个不带参数的简单的装饰器执行过程已经说清楚了。
有时候,需要装饰器有参数,比如设计一个计时程序,用来测试网络训练和测试的时间,此时需要指定当前执行的是训练或测试过程,为此,需要传入一个状态参数。此处说明一下装饰器传参的方式:@dec(args1,args2)
等价于func=dec(args1,args2)(func)
,值得说明的是此处所谓的参数是指装饰器的参数而不是被装饰函数的参数,被装饰函数的参数是通过*args
和**kwargs
自然地传递的。
例6.带参数的装饰器
def timing(status='Train'): def dec(func): @functools.wraps(func) def wrapper(*args,**kwargs): start = time.time() func1 = func(*args,**kwargs) # 此处做了一个变形 print('[%s] time: %.3f s '%(status,time.time()-start)) return func1 return wrapper return dec @timing(status='Train') def Training(): time.sleep(3) @timing(status='Test') def Testing(): time.sleep(2) >>>Training() [Train] time: 3.000 s >>>Testing() [Test] time: 2.000 s
上面这个例子,相对于例4,多了一层函数嵌套,但是理解起来应该并不困难,但是为了深入了解每一步执行过程,加入一些打印信息。
例7.深入探究装饰器执行过程
def timing(status='Train'): print('this is timing') def dec(func): print('this is dec in timing') @functools.wraps(func) def wrapper(*args,**kwargs): start = time.time() func1 = func(*args,**kwargs) print('[%s] time: %.3f s '%(status,time.time()-start)) return func1 return wrapper return dec @timing(status='Train') def Training(): time.sleep(3) >>>Training() this is timing this is dec in timing [Train] time: 3.000 s
对上述过程分步描述:
0.首先说明,@timing(status='Train')
在此处等价于Traing=timing('Train')(Traing)
Traing=timing('Train')(Traing)=dec(Traing)
。dec(Testing)
返回wrapper
,即Traing=wrapper
。(到这一步都只是定义Traing所产生的操作,即即使不调用该函数,上两句话也会被打印)wrapper
函数,记下起始时间start
,执行Traing
函数,延时2秒,打印所用时间,返回func1
。值得说明的是,这里返回的func1
是None
,因为这个返回值实际是Traing
函数的返回值,而这个函数我并没有定义返回值。
现在讨论多个装饰器的情况,多个装饰器类似于数学中的多个函数复用,看下面一个简单的例子,说明多个装饰器的执行顺序。
例8.多个装饰器模型
@dec1(args)
@dec2
@dec3
def foo():
pass
这等价于func = dec1(args)(dec2(dec3(func))
,此处特意将第一个装饰器设置为带参数的。
从上面这些例子中,我们不难看出,装饰器功能是先于被装饰函数执行的,为验证这个猜想,用一个实例说明。
例9.多个装饰器实例
def timing(status='Train'): print('this is timing') def dec(func): print('this is dec in timing') @functools.wraps(func) def wrapper3(*args,**kwargs): start = time.time() func1 = func(*args,**kwargs) print('[%s] time: %.3f s '%(status,time.time()-start)) return func1 return wrapper3 return dec def dec1(func): print('this is dec1') @functools.wraps(func) def wrapper1(*args,**kwargs): print('this is a wrapper in dec1') return func(*args,**kwargs) return wrapper1 def dec2(func): print('this is dec2') @functools.wraps(func) def wrapper2(*args,**kwargs): print('this is a wrapper in dec2') return func(*args,**kwargs) return wrapper2 @dec1 @dec2 @timing(status='Test') def fun(): time.sleep(2)
为方便分析,先不调用函数,其输出为
this is timing
this is dec in timing
this is dec2
this is dec1
从这个例子的输出来看,是从下而上地执行。首先写出此处的“复用”规则,fun = dec1(dec2(timing('Test')(fun)))
,为了方便叙述,此处将三个装饰器的内部函数分别命名为wrapper1
、wrapper2
和wrapper3
,上面这个过程其实是一个逐步“解包”的过程,除输出信息外,其最终达成一种其他的引用关系,在最外层(全局变量空间),fun=wrapper1
,而在wrapper1
的作用域内fun=wrapper2
,在wrapper2
的作用域内fun=wrapper3
。按照这个设定,先假设调用fun
会有什么输出。首先,把上面四个输出信息省略。由于在全局变量空间fun=wrapper1
,故执行fun()
时,会先打印wrapper1
内的信息,然后由于在wrapper1
中,fun=wrapper2
,故继续执行wrapper2
内的内容,以此类推,(这段文字比较绕,但是应该仔细推测)故推测其输出为:
this is a wrapper in dec1
this is a wrapper in dec2
[Test] time: 2.000 s
现在,调用fun
函数测试是否正确。
this is timing
this is dec in timing
this is dec2
this is dec1
this is a wrapper in dec1
this is a wrapper in dec2
[Test] time: 2.000 s
上述结果和猜测是一致的,从这个结果来看,这个过程有点像自底向上地完成装饰器的定义,然后自顶向下地执行装饰器的功能。所以,如果从使用的角度来看,装饰器其实是自顶向下的(毕竟实际使用过程中一般不会在外层打印信息)
情形 | “复用公式” |
---|---|
单个无参 | fun=dec(fun) |
单个含参 | fun=dec(args1,args2)(fun) |
多个无参 | fun=dec1(dec2(fun)) |
多个含参 | fun=dec1(args1,args2)(dec2((fun))) |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。