我们通过以示例来看看带参数的装饰器到底怎么回事。
from time import perf_counter
from functools import wraps
def repeated(times):
def outer(fn):
@wraps(fn)
def inner(*args, **kwargs):
for i in range(0, times):
result = fn(*args, **kwargs)
return result
return inner
return outer
def timed(fn):
@wraps(fn)
def inner(*args, **kwargs):
start = perf_counter()
result = fn(*args, **kwargs)
elapsed = perf_counter() - start
print(f'{fn.__name__} took {elapsed:.6f}s to execute')
return result
return inner
def print_split_line():
print('*' * 50)
print_split_line()
@repeated(5)
@timed
def func1():
print('func1 running')
func1()
print_split_line()
@timed
@repeated(5)
def func2():
print('func2 running')
func2()
print_split_line()
def func3():
print('func3 running')
func3 = repeated(5)(timed(func3))
func3()
print_split_line()
def func4():
print('func4 running')
func4 = timed(repeated(5)(func4))
func4()
print_split_line()
输出如下:
**************************************************
func1 running
func1 took 0.000034s to execute
func1 running
func1 took 0.000020s to execute
func1 running
func1 took 0.000020s to execute
func1 running
func1 took 0.000020s to execute
func1 running
func1 took 0.000019s to execute
**************************************************
func2 running
func2 running
func2 running
func2 running
func2 running
func2 took 0.000109s to execute
**************************************************
func3 running
func3 took 0.000031s to execute
func3 running
func3 took 0.000023s to execute
func3 running
func3 took 0.000020s to execute
func3 running
func3 took 0.000035s to execute
func3 running
func3 took 0.000022s to execute
**************************************************
func4 running
func4 running
func4 running
func4 running
func4 running
func4 took 0.000120s to execute
**************************************************
说明:
- repeated decorator就是多次重复执行被装饰的函数,并把被装饰函数的最后一次执行结果返回
- timed decorator就是打印被装饰函数的执行时间
- @语法糖等价关系
@repeated(5)
@timed
def func1():
print('func1 running')
def func1():
print('func1 running')
func1 = repeated(5)(timed(func1))
@repeated(5)中的repeated(5)是不是很眼熟?不错,repeated(5)就是一次函数调用!然后repeated(5)的返回值是一个真正的装饰器,我们就可以用在@语法糖上了。我们也可以说repeated()是个装饰器工厂函数!
- 说明有时候多个装饰器的情况下,加入装饰器的顺序会影响执行结果。
思考1:
为什么以下代码不能实现带参数的装饰器?
from functools import wraps
def repeated(fn, times):
@wraps(fn)
def inner(*args, **kwargs):
for i in range(0, times):
result = fn(*args, **kwargs)
return result
return inner
@repeated(5)
def func():
print('func running')
func()
运行会得到以下异常:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-60-a0fe82c182b7> in <module>
9 return inner
10
---> 11 @repeated(5)
12 def func1():
13 print('func1 running')
TypeError: repeated() missing 1 required positional argument: 'times'
这个比较明显,repeated(5)就是函数调用,但repeated需要2个位置参数,而repeated(5)只提供了一个位置参数。显然,此处的函数调用出错!
思考2:
为什么以下代码不能实现带参数的装饰器?
from functools import wraps
def repeated(fn, times):
@wraps(fn)
def inner(*args, **kwargs):
for i in range(0, times):
result = fn(*args, **kwargs)
return result
return inner
@repeated(func, 5)
def func():
print('func running')
func()
运行会得到以下异常:
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-4-febc2a5c7b04> in <module>
9 return inner
10
---> 11 @repeated(func, 5)
12 def func():
13 print('func running')
NameError: name 'func' is not defined
怎么是这个异常?为什么正确实现的带参数或者不带参数的装饰器能找到下面的函数名而没有这个异常?
我想这个和Python解释器有关系, 当到达@repeated(func, 5)这行代码时,Python应该在执行repeated(func, 5)调用,然而此时func函数还没定义,func标签也不存在,所以报当前错误。
思考3:
在思考2中出现的错误,有没有可能不用@语法糖的时候,是可以实现的?直接上代码。
from functools import wraps
def repeated(fn, times):
@wraps(fn)
def inner(*args, **kwargs):
for i in range(0, times):
result = fn(*args, **kwargs)
return result
return inner
def func():
print('func running')
func = repeated(func, 5)(func) # 此处代码近似于 @repeated(func, 5) 语法糖
func()
运行出现如下异常:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-3398bd4a9573> in <module>
12 print('func running')
13
---> 14 func = repeated(func, 5)(func)
15
16 func()
<ipython-input-3-3398bd4a9573> in inner(*args, **kwargs)
5 def inner(*args, **kwargs):
6 for i in range(0, times):
----> 7 result = fn(*args, **kwargs)
8 return result
9 return inner
TypeError: func() takes 0 positional arguments but 1 was given
说明:
repeated(func, 5)执行没有问题,能正确返回repeated.inner函数。
然后repeated.inner就会继续执行,请注意此时传给repeated.inner函数有一个位置参数func,在repeated.inner内部,func会被调用,并且func本身作为一个位置参数传给func,但根据func定义,func是不解释任何参数的,所以此时报错!
其实可以继续做一些事情,让本思考中的代码运行起来,但那已经没有意义!
总结:
- @语法糖本省就给我们使用格式的限制
- 带参数的装饰器,我们也可以认为是装饰器工厂函数,只有调用一次才返回真正意思上的装饰器
- 在带参的@语法糖出,其实会执行2次函数调用,而在不带参的@语法糖处,只执行一次函数调用
- 不带参的装饰器只需要嵌套一层子函数,而带参的装饰器需要嵌套二层子函数!
- 装饰器从某种意义上讲,就是实现了传参。
最内层:args, kwargs,留给目标函数调用时使用的
从内向外第2次:fn,这层就是留给装饰那个目标函数的
从内向外第3层:extra args, 这层才是给装饰器本身的参数
其实大家只要记住带参和不带参的装饰器的标准实现方式就足够应付大多数应用场景了!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)