顾名思义,从字面意思可以理解为,它是用来"装饰"Python的工具,使得代码更具有Python简洁的风格。装饰器本质上是Python函数,能够实现让其他函数在不需要做任何代码变动的前提下增加额外功能。
装饰器是通过某种方式来增强函数的功能。当然,我们可以通过很多方式来增强函数的功能,只是装饰器有一个无可替代的优势——简洁且不改变函数内部代码。
只需要在被修饰函数上方填加一个@修饰函数,就可以对这个函数增加额外功能。
装饰器最大的优势是用于解决重复性的操作,其主要使用的场景有如下几个:
当然,如果遇到其他重复操作的场景也可以从装饰器的角度思考,如何提高代码的简洁性。通过可以抽离大量与函数功能本身无关的重复代码到装饰器中,概括的讲,装饰器的作用就是为已经存在的函数添加额外的功能。
下面就以一个简单的例子来看一下它的作用。如果我们要对每个函数的增加重试机制,不使用装饰器,通常会是这样的,如下:
在ops函数里通过for遍历以及try … except … else异常处理实现简单重试,执行上述代码,输出结果为:
接下来,我们使用装饰器,实现重试机制,具体实现如下:
通过编写一个重试机制的装饰器fun_retry,被修饰函数ops的作为装饰器的参数,然后返回函数retry。
在fun_retry中嵌套了一个retry函数,那么retry是如何获取fun这个参数来执行的?,可是retry并没有接受fun这个传参,这就是Python里的闭包的概念,闭包就是指运行时自带上下文的函数,如这里的retry函数,它运行的时候自带了上层函数fun_retry传给他的fun这个函数,所以才可以在运行时对fun进行处理和输出。
然后再每个被修饰函数上面加上@fun_retry来调用装饰器,对不同的函数增加重试机制,即可省略每个函数里面的7行代码,实现在函数不需要做任何代码变动的前提下增加重试功能。
执行上述代码,输出结果为:
通过执行结果,也可以发现,当Python解释器执行到@fun_retry时,就开始进行装饰了,相当于执行了如下代码:fun_retry(ops)。
通过前面简单的实例介绍,应该已经大致清楚装饰器的作用和基本用法——通过闭包来实现装饰器,函数(ops)作为外层函数(fun_retry)的传入参数,然后在内层函数(retry)中运行、附加功能,随后把内层函数作为结果逐层返回。
除了上述简单的用法,装饰器还有更高的灵活性,例如带参数的装饰器。
带参数的修饰器并没有太复杂,其实就是在上述基本的装饰器的基础上在外面套一层接收参数的函数。
比如,我们认为现有的重试机制灵活性很差,需要它更加灵活的进行重试,例如支持修改重试次数、重试等待时间等,这时候可以把重试次数、重试等待时间作为装饰器的参数,如下:
可以看出,在原有的基础上装饰器外层又嵌套了一层函数fun_retry_more用来接收参数,这样的话在ops函数前面调用时可以给装饰器传入参数,这样的输出结果是:
可能有人会有一个疑问,如果我的被修饰函数 ops 需要参数怎么办?如果 ops 函数接收两个、三个参数,甚至更多呢?当装饰器不知道 ops 到底有多少个参数时,我们可以用*args 来代替,同样对于关键字参数,我们可以使用**kwargs来代替,args是一个数组,kwargs一个字典,我们在上面例子中也有所体现,如def retry(args, **kwargs)。这样它就能够接受任意数量和类型的参数并把它们传递给被包装的方法,使得我们能够用这个装饰器来装饰任何方法。
我们仅需要对retry闭包进行如上修改,将fun()的返回值赋给re_v,并且retry闭包中增加return 返回re_v即可,执行上述代码,结果如下:
在修饰器中有一个细节很少有人提及,那就是保留被修饰对象的元信息的装饰器
什么是函数的元信息?
就是函数本书的一些基本信息,例如函数名、函数文档等,我们可以通过func.__name__获取函数名、可以通过func.__doc__获取函数的文档信息,用户也可以通过注解等方式为函数添加元信息。
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表,如下:
使用装饰器极大地复用了代码,但是他有一个缺点,就是被修饰函数的元信息丢失了,执行上述代码,执行结果如下:
可以通过使用Python自带模块functools中的wraps来保留被修饰函数的元信息。
只需要在代码中加入@wraps(fun)即可保留函数的元信息。执行上述代码,输出结果为:
一个函数还可以同时定义多个装饰器,比如:
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于: