python装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。装饰器实际上就是一个函数,其有如下两个特别之处:

1.参数是一个函数
2.返回值是一个函数

一、无参装饰器

这里先实现一个简单的装饰器功能,在任何函数执行前先打印hello world ,示例如下:

decorator-01
decorator-01

调用装饰器时,只需要在想要调用的函数前通过@+修饰器函数名称即可。另外在定义修饰器函数时,需要注意,最后return返回的是一个函数体,即wrapper,而不是wrapper(),如果后面加上括号,就代表函数的执行了。在注释部分我也写了下推导的函数执行流程。

不过上面的修饰器在执行时,发现在对后面有参数传入的情况下,执行会出现报错。接下来我们修改下让修饰器支持参数。

二、带参数修饰器

先看下面的代码和执行结果:

decorator-02
decorator-02

上面的代码我们修改了wrapper函数,使其支持函数传入,并将传入的函数又传参给了修饰器的参数func函数。执行的时候发现add函数可以正常执行,不过对于没有参数的函数run,在加了修饰器执行时出了报错,提示少了两个参数。

三、同时满足有参和无参

之所以使用修饰器,就是考虑到可以方便的处理通用情况问题,一会儿可以参考,一会儿又不可以传参,显然不具备通用性,这里可以借助python下的*args,**kwargs这对兄弟来解决该问题。*args表示任何多个无名参数,它是一个tuple,可以接受传入的任意个参数;**kwargs表示关键字参数,它是一个dict,可以接受任意多个key-vaule形式的参数,如a=b,c=100这类的,其会在接受后转换为{‘a’:’b’,’c’:100} 这样的格式。代码如下:

decorator-03
decorator-03

上面的代码执行并没有报错,不过细心查看,会发现print run.__name__ 这行执行的结果和我们想想的不同,本来打印函数的属性时,应该属出函数本身相关的属性信息,结果输出的是wrapper函数的。如果还是参照示例1中注释的函数推导的步骤推算,发现也确实应该输出为wrapper。哪如何避免该问题呢?

四、解决修饰器中函数属性改变

decorator-04
decorator-04

通过引入functools模块里的wraps方法,我们在修饰器定义的时候,再对func函数本身增加wraps修饰方法,就可以保留原方法的属性 。

五、总结

通过以上示例,我们得出有关修饰器的如下总结:

1、装饰的使用是通过@符号,放在函数的上面;
2、装饰器中定义的函数,要使用*args,**kwargs两对兄弟的组合;
3、需要functools.wraps在装饰器中的函数上把传进来的这个函数进一个包裹,这样就不会丢失原来的函数的__name__等属性。