在谈及Python的时候,装饰器一直就是道绕不过去的坎。面试的时候,也常常会被问及装饰器的相关知识。总感受本身的理解很浅显,不够深入。是时候作出改变,对Python的装饰器作个全面的了解了。python
直接上代码,看看装饰器到底干了些什么?面试
from functools import wraps import time def time_cost(func): @wraps(func) def f(*args, **kwargs): start_time = time.time() func(*args, **kwargs) end_time = time.time() print(end_time - start_time) return f @time_cost def test(*args, **kwargs): time.sleep(1.0) if __name__ == "__main__": test()
上面的Python代码,运行后,会给出test函数的执行时间。代码的执行顺序大概以下,首先是将test做为值传递给time_cost函数,返回函数f,而后再调用f,这是带有time_cost装饰器的test函数的大体执行过程。闭包
从中,不难看出,即便不使用装饰器符号,咱们利用Python的语言特性,也能达成上述目的。用装饰器符号的好处是简化了代码,增长了代码的可读性。app
这是一段很是简单的对函数使用装饰器的Python代码。等等,@wraps(func)
是什么鬼?悄悄干了什么哇?函数
咱们稍微修改下上述代码,结果以下:code
from functools import wraps import time def time_cost(func): def f(*args, **kwargs): start_time = time.time() func(*args, **kwargs) end_time = time.time() print(end_time - start_time) print('hello world') return f @time_cost def test(*args, **kwargs): time.sleep(1.0) if __name__ == "__main__": print(test.__name__)
发现输出了hello world
,同时输出test.__name__
,竟然变成了f
,并非咱们预期的test
。根据这样的输出结果,咱们不可贵出,其实被装饰器time_cost
修饰过的函数test本质上已经等同于time_cost(test)
,此时访问test.__name__
实际上访问的是time_cost(test).__name__
,获得的固然就是f
啦。当咱们加上@wraps(func)
,此时test.__name__
变成了test
。orm
下面介绍带参数的装饰器,更加难了。在谈论带参数的装饰器之间,首先得引入一个概念,那就”闭包“。若是你之前用过脚本语言,好比JavaScript,那么必定会很熟悉闭包这个概念。下面是一个闭包样例对象
def add(a): def wrapper(c): return a + c return wrapper if __name__ == "__main__": add3 = add(3) add9 = add(9) print(add3(4) == 7) print(add9(1) == 10)
从中能够看出,在调用add3的时候,wrapper内部还能够访问到a的值,这就是闭包的做用。理解了闭包,理解带参数的装饰器就容易多了。ip
from functools import wraps def logging(level): def outer_wrapper(func): @wraps(func) def inner_wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=level, func=func.__name__)) return func(*args, **kwargs) return inner_wrapper return outer_wrapper @logging(level='WARN') def show(msg): print('message:{}'.format(msg)) if __name__ == "__main__": show('hello world!')
上面给出了一个带参数装饰器的示例。根据咱们前面的铺垫,咱们不难分析得出,上面的执行过程是logging(level='WARN')->outer_wrapper(show)->inner_wrapper()
,因此咱们能够理解,在被logging修饰后的show其实就是logging(level='WARN')(show)
,执行show('hello world!')
其实就是在执行logging(level='WARN')(show)()
。注意与不带参数的装饰器的区别,带参数的装饰器比不带参数的装饰器多套了一层,对应的装饰器也有了调用。由于在使用装饰器的时候,带了括号,因此装饰器自己多套了一层。被装饰器修饰过的函数在被调用的时候,实际上执行的是装饰器最内层的函数,其他层的在函数被修饰时就已经执行了。it
是否是以为很是天然?对的,我之前对装饰器的理解也就停留在不带参数的装饰器这一深度。
依然先上代码
from functools import wraps import time class time_cost: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): start_time = time.time() result = self.func(*args, **kwargs) end_time = time.time() print(end_time - start_time) return result @time_cost def test(*args, **kwargs): time.sleep(1.0) if __name__ == "__main__": test()
上面的基于类实现的不带参数的装饰器实际上利用的是Python中的可调用对象特性,凡是实现了__call__
方法的类的实例是能够被调用的。所以被time_cost
修饰过的test
函数本质上已经变成了time_cost类的实例了。调用test方法的时候,实际上执行的是__call__
方法。
下面介绍稍微复杂一点的基于类实现的带有参数的装饰器。
from functools import wraps class logging: def __init__(self, level): self.level = level def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=self.level, func=func.__name__)) return func(*args, **kwargs) return wrapper @logging(level='WARN') def show(msg): print('message:{}'.format(msg)) if __name__ == "__main__": show('hello world!')
不一样于基于类实现的不带参数的装饰器,基于类实现的带参数的装饰器在__call__
里面多了一层wrapper。被装饰器修饰的show方法本质上是logging(level='WARN')(show)
,此时调用show方法,实际上执行的是wrapper方法。
如今看来,其实装饰器也没有很复杂,在实际的项目中用装饰器能够带来很大便利。