Python装饰器
引入装饰器
如果想在一个函数执行前后执行一些别的代码,比如打印一点日志用来输出这个函数的调用情况那应该怎么做呢?
也可以用以下方式来实现这种效果
这就是Python装饰器的一个简单使用
什么是装饰器?
装饰器是用于软件设计模式的名称。 装饰器可以动态地改变函数,方法或类的功能,而不必直接使用子类或改变被装饰的函数的源代码。Python装饰器是对Python语法的一种特殊改变,它允许我们更方便地修改函数,方法以及类。
当我们按照以下方式编写代码时:
和单独执行下面的步骤是一样的:
装饰器内部的代码一般会创建一个新的函数,利用*args
和**kwargs
来接受任意的参数,上述代码中的wrap()函数就是这样的。在这个函数内部,我们需要调用原来的输入函数(即被包装的函数,它是装饰器的输入参数)并返回它的结果。但是也可以添加任何想要添加的代码,比如在上述代码中输出函数的调用情况,也可以添加计时处理等等。这个新创建的wrap函数会作为装饰器的结果返回,取代了原来的函数。
所以在Python中,装饰器的参数是一个函数, 返回值是一个函数的函数。
装饰器的示例:计时处理
写一个装饰器,用来计算一个函数的执行时间
如果要对add函数计时:
如果要对sleep函数计时:
保存被装饰函数的元信息
什么是函数的元信息
比如装饰器的名称,装饰器的doc等等。我们可以使用dir函数列出函数的所有元信息:dir(sleep)
,输出结果如下
可以看到有很多的元信息,我们比较常用的是__name__
和__doc__
这两个属性\
而且__doc__
属性也就是函数的文档信息,可以通过help函数查看得到
为什么要保存被装饰函数的元信息
改写装饰器的应用1:计时处理中的sleep函数如下:
以上代码输出结果如下:
可以发现sleep函数的__name__
是wrap,而不是sleep,而__doc__
属性为空,而不是sleep函数的docstring。也就是说经过装饰器装饰过后的函数的元信息发生了改变,这时候如果程序需要函数的元信息,那么就有问题了。
如何保存被装饰函数的元信息
方案1:手动给被装饰函数的元信息赋值
以__name__
和__doc__
这两个属性为例
输出结果如下
可以发现,__name__
和__doc__
这两个属性确实赋值成功了。
我们可以将元信息赋值的过程改写为函数,如下
这样修改后,同样可以解决问题。
继续修改copy_properties函数,使得copy_properties可以返回一个函数
同样可以问题。
如果继续修改copy_properties函数,使得_copy函数是一个装饰器,传入dst,返回dst,修改如下:
copy_properties在此处返回一个带参数的装饰器,因此可以直接按照装饰器的使用方法来装饰wrap函数,这个修改copy_properties函数的过程称为函数的柯里化。
functools库的@wraps装饰器本质上就是copy_properties函数的高级版本:包含更多的函数元信息。首先查看wrap装饰器的帮助信息:
wrap装饰器函数的原型是:
所以这个装饰器会复制module等元信息,但是也不是所有的元信息,并且会更新dict。
使用示例如下:
编写一个带参数的装饰器
如果上述的timeit装饰器,我们需要输出执行时间超过若干秒(比如一秒)的函数的名称和执行时间,那么就需要给装饰器传入一个参数s,表示传入的时间间隔,默认为1s。
我们可以给写好的装饰器外面包一个函数timeitS,时间间隔s作为这个函数的参数传入,并且对内层的函数可见,然后这个函数返回写好的装饰器。
输出结果如下:
所以,我们可以将带参数的装饰器理解为:
- 带参数的装饰器就是一个函数, 这个函数返回一个不带参数的装饰器