在学习Python过程中,总觉得装饰器看起来很难,这篇文章带大家搞懂Python装饰器的实现逻辑。先来看一个统计函数运行时间的装饰器
importtimedeftimer(func): """ 计时器装饰器 """definner(): """ 内层函数 """# 统计时间start=time.time() ret=func() end=time.time() # 打印运行时间print("%.2f"%end-start) # 返回函数运行的结果returnretreturninner# 装饰器# 或者是f = timer(f)deff(): time.sleep(2) # 此时的f其实是inner函数# 运行函数f()
上面的@timer
是Python的语法糖,等价写法如下
deff(): pass# 等价于f=timer(f)
可以发现,@timer
其实就是将被装饰的函数当作参数传了进去,然后又重新赋给了f
。之所以可以这样做,是因为Python一切皆对象,一个函数可以作为参数传到另一个函数里面,函数也可以当成变量从函数中返回出来。基于这一点,我们分析一下,上面的计时器timer
是如何实现的。
timer
是一个函数,它接收一个被装饰函数作为参数,因此timer
必须有一个参数用来接收被装饰函数。我们目的是在运行f
的时候可以自动统计f
运行的时间,所以必然要有一个记录开始时间和结束时间的变量,也就是start和end
。这里抛出第一个问题,start和end
应该放在哪里?先来看下面一段代码
deftimer(func): start=time.time() ret=func() end=time.time() deff(): time.sleep(2)
上面这段代码将start
和end
置于 func()
之前和之后,可以达到统计运行时间的效果,但是这里会出现一个问题,f
会在被@timer
装饰后立刻运行,而非主动运行f()
的时候去统计时间,出现这样的问题,是因为前面提到@
的这种写法等价于f = timer(f)
,把f
传进去可以发现,确实会立刻就运行了。这里抛出第二个问题,如何让传进去的f
延迟运行?,即,在主动运行f()
的时候,才去统计时间
解决方法是,在timer
内部再加一层函数
deftimer(func): definner(): start=time.time() ret=func() end=time.time() print('%.2f'%end-start) returnretreturninner
通过这种方法,可以达到将f
装饰后,返回一个函数,即
f=timer(f) # timer的返回值其实是inner
这样,统计部分的代码只有在f()
的时候,才会去运行,从而达到了延迟运行的效果。之所以可以这样做,是因为Python中支持闭包的特性,即在外层函数运行结束后,内层函数可以继续使用外层函数中的变量,这里所使用的外层函数的变量是func
。
到这里,你就可以实现一个最简单的不带任何参数的装饰器。但这还远远不够,当有一天你的f
函数需要增加参数以后,这个装饰器就不再适用。我们先来看一个带参数的f
是如何定义并被调用的
# 定义deff(name): print(name) # 调用f('Jack')
当添加上装饰器以后,需要保证f
可以接收参数,我们再次回到这段代码
deftimer(func): definner(): start=time.time() ret=func() end=time.time() print('%.2f'%end-start) returnretreturninnerf=timer(f)
被装饰以后的f
其实是timer
内部inner
函数,所以,只需要更改inner
函数,只要inner
函数可以接收参数,那么装饰后的f
就可以接收参数。通过可变参数,可以做到不管f
的参数怎么变,都可以正确接收。inner
函数的具体的改造如下
deftimer(func): definner(*args, **kwargs): start=time.time() ret=func(*args, **kwargs) end=time.time() print('%.2f'%end-start) returnretdeff(name): print(name) f('Jack')
除了被装饰函数有参数的情况,装饰器函数也可以接收参数,比如
10) (deff(name): print(name)
要想完成这个需求,只需要在inner
函数外面再加一层函数,像下面这样
# 装饰器先接收参数deftimer(count): """ 计时器装饰器 """deffunc_wrapper(func): # 通过可变参数,可以处理各种各样的函数传参definner(*args, **kwargs): """ 内层函数 """# 统计时间start=time.time() # 将参数值传递到被装饰的函数中ret=func(*args, **kwargs) print(time.time() -start) # 返回函数运行的结果returnretreturninner# 返回真正的装饰器函数returnfunc_wrapper10) (deff(name): print(name)
这样做的的原理可以用下面的代码理解
timer=timer(10) # 这是timer其实是func_wrapperdeff(name): print(name)
相当于在装饰器函数装饰之前,先把参数传进去,然后返回一个真正的装饰器函数,之后就和前面的过程一样了。