因为以前接触过一点面向切面编程(AOP)的东西,第一次看到python的修饰器,就让我想到了AOP,我不清楚AOP是怎么样实现的,但是它们的初衷相同,就是把公共的与主逻辑无关的、但是主业务模块需要调用的辅助代码拿出来,比如性能统计、日志记录、异常处理等,把它们独立封装成函数,在不改变主业务代码的情况下把这些公共的辅助功能应用于主业务中。这样的好处就是解耦,提高可扩展性和复用性,让代码优雅,开发人员只要专注于自己的业务实现即可。
其实网上已经很有很多不错的文章来介绍修饰器了,我只是做个自我总结。
实现python的修饰器的前提是:第一,Python中一切都是对象,包括函数。第二,python支持闭包。
比如在我们的程序中,希望每个函数被执行时,都可以打印出函数的执行时间。一开始想到的做法肯定是这样
import time
def timer(fun):
start = time.perf_counter()
fun()
end = time.perf_counter()
print(fun.__name__ + " run time: " + str(end - start))
def fun1():
print("fun1")
def fun2():
print("fun2")
def fun3():
print("fun3")
timer(fun1)
timer(fun2)
timer(fun3)
这样做的问题就是每次调用主业务函数时,都要将函数对象传入timer中,这样不利于维护而且打乱了业务逻辑,让代码看起来我们不是在实现业务逻辑而是在测试代码的执行时间。
我们修改一下代码:
import time
def timer(fun):
def wrapper():
start = time.perf_counter()
fun()
end = time.perf_counter()
print(fun.__name__ + " run time: " + str(end - start))
return wrapper
def fun1():
print("fun1")
def fun2():
print("fun2")
def fun3():
print("fun3")
fun1 = timer(fun1)
fun2 = timer(fun2)
fun3 = timer(fun3)
fun1()
fun2()
fun3()
我们在timer加了一个wrapper函数,在wrapper函数中去执行被传入的函数对象,然后在最外层函数中返回这个wrapper函数对象。在主执行代码里,我们把业务函数对象传入timer中,赋值给函数同名变量,因为wrapper返回的是函数对象,所以最后我们就可以执行wrapper函数。 这样下来,虽然加了fun = timer(fun),但是在最后实现业务的时候,留下了其最原始的面貌,没有破坏字面上的逻辑。Python为了简化代码,使用了@decorator的语法糖,就像这样。
import time
def timer(fun):
def wrapper():
start = time.perf_counter()
fun()
end = time.perf_counter()
print(fun.__name__ + " run time: " + str(end - start))
return wrapper
@timer
def fun1():
print("fun1")
@timer
def fun2():
print("fun2")
@timer
def fun3():
print("fun3")
fun1()
fun2()
fun3()
用@timer修饰了每个函数,看上去非常优雅简洁。我们第一个修饰器就完成了。后面扩展起来就比较容易了,比如被修饰的函数有参数怎么办?就可以这样
import time
def timer(fun):
def wrapper(string):
start = time.perf_counter()
fun(string)
end = time.perf_counter()
print(fun.__name__ + " run time: " + str(end - start))
return wrapper
@timer
def fun1(string):
print(string)
@timer
def fun2(string):
print(string)
@timer
def fun3(string):
print(string)
fun1("hi")
fun2("hello")
fun3("wei")
只要给wrapper加个参数就好了,因为本质上修饰器调用的是wrapper函数,只是在wrapper中调用了需要被修饰的函数,所以当然要从wrapper作为切入点传参啦。但这样还有个问题,被修饰的函数参数是不固定的,那怎么办,其实很简单,就是给wrapper函数加个可变参数就行了。
import time
def timer(fun):
def wrapper(*args, **kwargs):
start = time.perf_counter()
fun(*args, **kwargs)
end = time.perf_counter()
print(fun.__name__ + " run time: " + str(end - start))
return wrapper
@timer
def fun1(string):
print(string)
@timer
def fun2(string):
print(string)
@timer
def fun3(str1, str2):
print(str1)
print(str2)
fun1("hi")
fun2("hello")
fun3("wei","weiwei")
其实修饰器也可以有自己的参数,那怎么搞头? 那就是在timer的基础上外面在套一个函数,让这个函数去处理传入的参数,然后返回timer对象,你会发现实际到最后还是用timer去修饰。
import time
def outer_timer(string):
def timer(fun):
def wrapper(*args, **kwargs):
print("================%s===================" % string)
start = time.perf_counter()
fun(*args, **kwargs)
end = time.perf_counter()
print(fun.__name__ + " run time: " + str(end - start))
return wrapper
return timer
@outer_timer("fun1")
def fun1(string):
print(string)
@outer_timer("fun2")
def fun2(string):
print(string)
@outer_timer("fun3")
def fun3(str1, str2):
print(str1)
print(str2)
fun1("hi")
fun2("hello")
fun3("wei","weiwei")
这里的@语法糖等同于fun = outer_timer("fun")(fun)。
其实修饰器并不难,如果你从最原始的底部推理上来的话,还有一点就是python中一切皆为对象。