一文看懂Python的装饰器
在 Python 中使用装饰器,可以在不修改代码的前提下,为已有的函数添加新功能,例如打印日志、缓存数据等。
为什么需要装饰器
假如你要为某个函数添加新功能。直接的办法是,在该函数中实现这个功能,或者在新的函数中实现它,然后在该函数中调用新函数。
间接的办法是执行新的函数,新函数实现了该功能,并调用原来的函数。装饰器便是这样的函数。
既然直接的办法能解决问题,为什么还需用装饰器?
在实际中,随着业务的变化,项目会越来越复杂,比如:
类之间重复的代码越来越多。
项目代码包含大量与业务无关的功能,例如调试、缓存、鉴权等。
为了解决上述问题,第一个办法是把重复的代码和业务无关的功能抽象出来,然后在新的类中实现。这样一来,新类与旧类在项目中耦合,新类的改变会影响到旧类,项目的维护成本增加。
第二个思路是,在旧类需要的时候(编译或运行时)动态地加入这些功能,即面向切面编程。切面代表了一个功能点,站在业务的角度,它是与业务逻辑无关的功能。
面向对象的思想是把业务逻辑划分成不同的类,而面向切面为业务逻辑提供补充功能。前者是纵向切分,后者是横行切分。一纵一横,保持项目代码简洁。
装饰器实现的就是切面功能点。下文用一个例子说明三种类型点装饰器:普通装饰器、带参数的装饰器和装饰器类。
普通装饰器
我们即将实现一个计时器,它的功能是计算函数的运行时间。使用效果如下所示:
装饰器 的输入参数是一个函数对象,返回的结果也是一个函数对象。返回的函数是一个附加了新功能的函数。
在上面的例子中, 装饰器 实际上是一个执行如下操作的语法糖。
完整代码如下所示。
装饰后的函数名
在上述例子中,如果我们打印使用装饰器之后的函数 的名称,结果是 。
在一些情况下,我们希望保持原来函数的名称,这时可以利用Python自带的装饰器 来装饰 ,从而保持函数名称不变(代码如下)。
带参数的装饰器
下面我们要把计时器的功能稍微扩充一下。实现一个带参数的计时器,其中参数 代表了计时的单位:。
前面提到,装饰器函数的输入必须是一个函数对象。注意到函数名加括号 代表执行一个函数,因此只要让函数 的执行结果返回一个“普通的装饰器”即可。
装饰器类
我们还想给计时器增加新的计时单位 。直接的方法是在 函数中增加相应的逻辑。
但是,随着装饰器要实现的功能变得复杂,如果所有的逻辑在一个函数里实现,不利于协作和维护。这时可以用装饰器类,来实现对应的功能。
最后说一句:不要滥用装饰器。