装饰器更高级的用法是可以带参数。
带参数的装饰器
先来看一个不带参数的装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import time def timeit(fn): def wrap(*args,**kwargs): start = time.time() ret = fn(*args,**kwargs) print(time.time() - start) return ret return wrap @timeit def sleep(x): time.sleep(x)
|
3.0034420490264893
这里打印出来的是执行sleep
函数所消耗的自然时间
,但在执行此函数时所消耗的cpu时间真的有3.0034420490264893秒吗?当然不是。利用time包中的time.clock
方法可以计算得到代码执行所消耗cpu的时间,那怎样来修改上边的timeit
函数,让其即能计算代码执行的自然时间,也能计算代码执行所消耗cpu的时间?做如下改进:
1 2 3 4 5 6 7 8 9 10 11 12 13
| def timeit_1(process_time=False): cacl = time.clock if process_time else time.time def timeit_2(fn): def wrap(*args,**kwargs): start = cacl() ret = fn(*args,**kwargs) print(cacl() - start) return ret return wrap return timeit_2 def sleep_1(x): time.sleep(x)
|
1
| timeit_1(True)(sleep_1)(3)
|
0.020000000000000018
1
| timeit_1(False)(sleep_1)(3)
|
3.0038363933563232
3.003509283065796
上边的调用过程是怎样的呢?分解一下,如下:
上边调用timeit_1(True)
,函数return回了timeit_2
,并把fn1
这个变量指向了调用结果,即指向了timeit_2
,这里的timeit_2
也是一个函数,此函数接收一个参数
这里调用fn1(sleep_1)
,其实就是调用了timeit_2(sleep_1)
,并把fn2
这个变量指向了调用后的结果,即指向了warp
,这里的warp
也是一个函数,此函数能接收任意的参数
0.009999999999999787
上边调用fn2(3)
,其实是调用了wrap(3)
,即执行了wrap
函数内的语句,此函数内的ret = fn(*args,**kwargs)
语句中的fn
其实是指向了sleep
,所以在执行wrap
函数时,sleep_1
函数才真正被执行。
既然装饰器可以用魔法来装饰一个函数,那上边经过改进过的装饰器是不是也能装饰一个函数呢?如下:
1 2 3
| @timeit_1(False) def sleep_2(x): time.sleep(x)
|
3.0039477348327637
如果想计算代码执行的cpu时间,那如下即可:
1 2 3
| @timeit_1(True) def sleep_3(x): time.sleep(x)
|
0.0
这个魔法又发生了什么呢?
其实质就是在没有用魔法的情况下直接timeit_1(True)(sleep_3)(3)
。而当使用@
这个魔法后,当代码执行到此行时,解析器会执行timeit_1(True)
,timeit_1
实质就是一函数,接收一个参数,并返回一个timeit_2
函数。当代码执行到@
所在语句时,会把所装饰的sleep_3
函数作为一个参数传递给timeit_1(True)
的调用结果,即timeit_2
这个函数,即sleep_3
这个函数已作为一个变量传递给了timeit_2(fn)
中的fn
参数,并返回了一个wrap
函数,在接下的调用sleep_3(3)
这个操作,其实此时的sleep_3
这个函数已不是原先的def sleep_3(x):
中的sleep_3
函数,而是一个指向了wrap
的函数,wrap
函数接收任何参数,所以把当执行sleep_3(3)
时,把参数3
传递给了wrap
函数,并执行内部的代码,内部代码中ret = fn(*args,**kwargs)
中的fn
函数依赖还是指向原先的sleep_3(x)
这个函数。
这里也有一个简单的记忆方式,如果一个函数被装饰器所装饰,在调用这个函数时其实不再是调用表面上看上去的这个函数,以
1 2 3
| @timeit_1(True) def sleep_3(x): time.sleep(x)
|
来做说明。当执行到有@
魔法所在行时,相当于执行了sleep_3 = timeit_1(True)(sleep_3)
,即指向了wrap
函数,既然sleep_3
指向了wrap
函数,那我们执行sleep_3(3)
时,其实就是在进行wrap(3)
这样的函数调用,记住,函数名也是一个变量。
再来举一个带参数的装饰器的例子,比如有一个函数,只有在对有许可权限的用户开放,执行此函数的用户没有在认证列表里的,就不会执行这个函数。这个该如何实现呢?如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def check(allow_users): def inner_check(fn): def wrap(username,*args,**kwargs): '''This is wrap''' if username in allow_users: return fn(username,*args,**kwargs) return "You are illegal users" return wrap return inner_check @check(['zhaochj','zcj']) def private(username): '''The authentication''' return "You are legitimate users"
|
'You are legitimate users'
'You are illegal users'
这样就可以对调用函数进行身份验证。
python中一个函数有一些属性是函数本身具有的,比如__name__
属性是查看函数名称,__doc__
是查看函数文档的等等。如果一个函数被装饰器装饰过后,这个函数的这些属性会发生怎样的变化呢?以上边的check
装饰器和private
函数为例子,如下:
'wrap'
'This is wrap'
private
函数的名称是wrap
了,文档也是wrap
函数的文档,这是怎么回事?上边已经说过,这里的private
函数被装饰器装饰后它已不再是原来的private
函数,private
这个函数名称会被指向到wrap
这个函数对象,那当然用上边的private.__name__
和private.__doc__
查看函数的属性就会是wrap
函数的属性。那怎样来修正呢?可以这样做,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def check_1(allow_users): def inner_check(fn): def wrap(username,*args,**kwargs): '''This is wrap''' if username in allow_users: return fn(username,*args,**kwargs) return "You are illegal users" wrap.__name__ = fn.__name__ wrap.__doc__ = fn.__doc__ return wrap return inner_check
@check_1(['zhaochj','zcj']) def private_1(username): '''The authentication''' return "You are legitimate users"
|
'private_1'
'The authentication'
通过在装饰器把__name__
和__doc__
重新赋值后就能更正这个问题,但对一个函数来说像__name__
这样类似的属性有许多,如果都是这样手工来修正显然是不现实的,所以python提供了一个wraps
装饰器来自动修正这个问题,wraps
在functools
这个包中,所以可以这样来修正这个问题,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import functools def check_1(allow_users): def inner_check(fn): @functools.wraps(fn) def wrap(username,*args,**kwargs): '''This is wrap''' if username in allow_users: return fn(username,*args,**kwargs) return "You are illegal users" return wrap return inner_check
@check_1(['zhaochj','zcj']) def private_1(username): '''The authentication''' return "You are legitimate users"
|
'private_1'
'The authentication'
@functools.wraps(fn)
这个装饰器相当于执行了wrap.__name__ = fn.__name__
这样的操作。