Python装饰器
【装饰器有什么用】
顾名思义,就是在原有的业务函数原封不动的同时但却能够及其方便地为其增加额外的功能,避免改动原有的业务代码产生不必要的bug从而增加工作量。
就好比家里辛辛苦苦装修完毕,逛街的时候又发现了一些小的装饰品特别喜欢。于是买回家直接找个合适的地方摆放即可,而不用重新装修房子大动干戈,也就是起到了“装饰”的作用。
典型的使用场景有:排查问题时追加一些额外的调试日志;代码执行效率低的时候追加计时功能定位代码;需要校验权限的场景;等等
【什么是装饰器】
装饰器的实质就是一个闭包函数,而闭包函数的特殊之处在于:
-
- 是个多层嵌套定义的函数
- 参数就是要被“装饰”的函数的名称
- 内层函数引用了外层函数定义的变量或者传参
- 外层函数return返回了内层函数的引用(注意:正确形式是return functionName这种,不是return functionName())
【装饰器的代码形式】
在早些时候 (Python Version < 2.4,2004年以前),装饰器的写法类似下面形式:
def debug(func): def in_func(): print("starting") func() print("stopped") return in_func #@debug #等价于下面那种繁琐的写法:outputName=debug(outputName) def outputName(): print("\tHello,%s" % ("Elon Musk")) outputName=debug(outputName) #装饰器原来的调用方式 if __name__ == "__main__": outputName()
在之后版本的Python中支持了@语法糖,上面的代码等同于下面这种更加简洁的写法:
def debug(func): def in_func(): print("starting") func() print("stopped") return in_func @debug #等价于下面那种繁琐的写法:outputName=debug(outputName) def outputName(): print("\tHello,%s" % ("Elon Musk")) #outputName=debug(outputName) #装饰器原来的调用方式 if __name__ == "__main__": outputName()
【装饰可以传参的函数】
上面示例被装饰器装饰的函数过于简单,因为不需要传参。目的只是为了用最简洁的代码给大家展示清楚装饰器的使用形式。
但是,实际工作中因为业务的复杂度往往需要参数传递。接下来,我将继续优化示例代码,给大家展示如何装饰需要传递参数的函数:
def debug(func): def in_func(name): #内层函数直接给一个参数 print("starting") func(name) #在内层函数直接调用执行目标函数 print("stopped") return in_func @debug #等价于下面那种繁琐的写法:outputName=debug(outputName) def outputName(name): #函数的定义需要传参 print("\tHello,%s" % (name)) #outputName=debug(outputName) #装饰器原来的调用方式 if __name__ == "__main__": outputName("Elon Musk")
上面的例子中,装饰器已经可以装饰需要传递参数的函数了。
但是,实际工作中的业务代码及其复杂,传递的参数数量很多且类型多种多样,怎么办呢?此时,我们需要使用到Python中的可变参数*arg、**kwarg:
def debug(func): def in_func(*arg,**kwarg): #内层函数直接万能的可变参数 print("starting") func(*arg,**kwarg) #在内层函数直接调用万能的可变参数 print("stopped") return in_func @debug #等价于下面那种繁琐的写法:outputName=debug(outputName) def outputName(name): #函数的定义需要传参 print("\tHello,%s" % (name)) #outputName=debug(outputName) #装饰器原来的调用方式 if __name__ == "__main__": outputName("Elon Musk")
以上代码的执行结果如下:
【可以传参的装饰器】
至此,大家已经掌握了装饰器的基本用法,恭喜恭喜!
不过,以上示例中的装饰器比较简单,因为装饰器本身不能传参,目的只是为了用最简洁的代码给大家展示清楚装饰器的基本使用。
接下来,我将继续优化示例代码,给大家展示装饰器如何传递参数:
import time def debug(n): #最外层函数用来接收装饰器的传参 def func1(func): #内部第一层函数才是真正的装饰器 def func2(*args,**kwargs): #内层函数直接万能的可变参数 for i in range(1,n+1,1): time.sleep(1) print("Time flies: %d" % (i)) print("starting") func(*args,**kwargs) #在内层函数直接调用万能的可变参数 print("stopped") return func2 #装饰器除了最内层的函数,其余外层的函数都需要return函数名 return func1 #装饰器除了最内层的函数,其余外层的函数都需要return函数名 @debug(3) #装饰器携带参数,让sleep N秒后再执行目标函数 def outputName(name): print("\tHello,%s" % (name)) if __name__ == "__main__": outputName("Elon Musk")
运行结果如下:
可见,要想装饰器本身能够传参,只需要在无参装饰器之外再封装一层即可。
为什么是这样呢?其实,前面通过@debug(3)来装饰outputName(name)相当于执行了语句outputName=debug(3)(outputName)。其原理本质可以分解为以下3个步骤:
-
- 先执行函数debug(3),此时返回闭包函数func1
-
接着执行函数func1(outputName),此时返回内层函数func2
-
再执行赋值语句outputName=func2(outputName)
现在,我们再来看看改写之后的代码及其运行结果: