何为装饰器和为什么要用装饰器
python装饰器是对原有函数进行扩展的操作,使用装饰器可以在不改动原有函数的情况下独立扩展现有代码,增加新的功能。
假设有两个函数:
def answer():
t1 = time.time()
print('answer the question')
time.sleep(1)
t2 = time.time()
print(t2 - t1)
def quest():
t1 = time.time()
print('ask the question')
time.sleep(1)
t2 = time.time()
print(t2 - t1)
answer()
quest()
通过执行,我们可以看见两者的执行时间
但是如果还要对第三个函数进行一样的运行时间展示,我们还要写一次一样的time,time相减的操作,这相当于重复劳动,有没有办法避免呢?
于是我们想要一个函数,它能以参数的形式,统计其他函数的运行时间:
import time
def func3():
print('func3')
def count_time(f):
def wrapper():
t1 = time.time()
f()
print('time cost:', time.time() - t1)
return wrapper#count_time函数接收f参数,作为参数的函数f在wrapper中执行。
#于是count_time(func)相当于wrapper,
#count_time(func)()相当于wrapper(),wrapper中的f()替换为func()
#于是该函数count_time可以计算所有函数的运行时间,
count_time(func3)()
语法糖
使用上面的函数闭包的方式依然需要调用函数的形式,使用装饰器的语法糖可以更加便捷实现。如果在leecode经常刷题的朋友,应该对
@cache
def dfs(n):
的形式并不陌生,这种做法将已有计算的结果通过cache机制存储起来,加快函数运行到终点的速度。
我们以这样的形式改写我们的装饰器:
import time
def func3():
print('func3')
def count_time(f):
def wrapper():
t1 = time.time()
f()
print('time cost:', time.time() - t1)
return wrapper
@count_time
def func4():
print('func4')
time.sleep(1)
func4()
这里直接调用func4函数,因为有@count_time的装饰,其自动计算了代码的运行时间。
被装饰的函数带参数处理
以上的例子中,函数都是无参函数,有参数时又应该怎么处理呢?改进一下装饰器函数的内部函数就行了:
import time
def func3(n: int):
for i in range(n):
print('func3')
time.sleep(1)
def count_time(f):
def wrapper(*args, **kwargs):
t1 = time.time()
f(*args, **kwargs)
print('time cost:', time.time() - t1)
return wrapper
count_time(func3)(4)
在这里也有常见问题,比如最后一行写成:
count_time(func3(4))()
会报错:
count_time(func)(4),func为指定的函数,后一个()中为func中需要传入的参数,后者的写法中会先执行func(4),后一次()需要参数而没有参数,自然报错了。
装饰器本身带参数处理
装饰器本身带参数,就将其参数当普通函数参数处理就可。
import time
def func3(n: int):
for i in range(n):
print('func3')
time.sleep(1)
def count_time(f,s:str = 'aaa'):
def wrapper(*args, **kwargs):
t1 = time.time()
f(*args, **kwargs)
print('time cost:', time.time() - t1)
print(s)
return wrapper
count_time(func3,'count_time for func3')(4)
当然,在使用语法糖形式时有些变化,在装饰器没有参数时,可以自动接受被装饰的函数作为唯一的参数,但是有参数后这样的情况就不再适用了,需要我们自己指定函数的参数。但在这里依然会引出python中函数闭包的问题,使得被修饰的函数带有参数时,问题复杂化。
先看一段错误的代码:
import time
def count_time(s: str = 'aaa'):
def wrapper(f, *args, **kwargs):
t1 = time.time()
f(*args, **kwargs)
print('time cost:', time.time() - t1)
print(s)
return wrapper
@count_time(s='count_time for func')
def func(n: int):
for i in range(n):
print('func')
time.sleep(2)
func(4)
这里甚至可以正常输出我们给装饰器的参数,但是会提示func()缺失参数。因为第一次传递参数,s确实可以接受到“count_time for func”,但count_time装饰器返回wrapper函数,后者只能接受到func函数作为参数,写在()中的4是无法传递到*args,**kwargs中的,这里即使不使用()调用函数,也是一样的:
import time
def count_time(s: str = 'aaa'):
def wrapper(f, *args, **kwargs):
t1 = time.time()
f(*args, **kwargs)
print('time cost:', time.time() - t1)
print(s)
return wrapper
@count_time(s='count_time for func')
def func(n: int):
for i in range(n):
print('func')
time.sleep(2)
func
需要调用参数的话,还要加上一次闭包。
import time
def count_time(s: str = 'aaa'):
def wrapper(f):
def inner_wrapper(*args, **kwargs):
t1 = time.time()
f(*args, **kwargs)
print('time cost:', time.time() - t1)
return inner_wrapper
print(s)
return wrapper
@count_time(s='count_time for func')
def func(n: int):
for i in range(n):
print('func')
time.sleep(2)
func(4)
这里的func就相当于wrapper(func),其又相当于inner_wrapper,再call传参就相当于inner_wrapper(4)
类的装饰器
python中,类也可以实现装饰器的,其实现是调用了call魔术方法。
import time
class Time_Counter:
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
t1 = time.time()
self.func(*args,**kwargs)
print('time cost:',time.time()-t1)
@Time_Counter
def foo():
print('foo')
time.sleep(1)
foo()
这里的类装饰器可能也有参数,此时的init 函数就不能传入func了,类似于我们在有参数的装饰器函数的操作,将func作为call的参数传递即可:
import time
class Time_Counter:
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self, func):
def wrapper(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
print('time cost:', time.time() - t1)
print(self.x, self.y)
return wrapper
@Time_Counter('hello', 'world')
def foo(n):
for i in range(n):
print('foo')
time.sleep(1)
foo(4)