山地人

Python 装饰器

山地人
山地人
2021-07-19

装饰器通过接收函数,并对函数进行功能增强,之后返回这个增强功能的函数。本次教程,我们就来学习如何创建并使用装饰器。

装饰器

下面定义了一个简单的sum(a, b)函数,然后运行这个函数,并对运行的结果进行输出。

运行沙盒,观察结果。

如果这样的定义很多,我们就要写很多测试代码,能否对这个过程进行简化。如果你了解高阶函数,可能会想到这样的解决方案:

def test(fn):
def wrapper(*args):
print(f"{fn.__name__}{inspect.signature(fn)}的结果是{fn(*args)}")
return wrapper

我们定义了一个高阶函数test,这个函数可以接收一个参数fn。你可以把函数传入到fn中,在test内部会生成一个新的函数,我给他取名叫test也就是一个包装函数。然后在这个函数内部会把之前我们做的打印函数名称和执行函数运行结果的事情都处理掉。

利用这样一套机制下来,我们以后再要写类似的测试就方便了,直接使用test(fn)创建一个要测试的目标函数的包装函数,然后利用这个包装函数去执行,就能得到带有标题结果的输出。

inspect.signature用来获取fn的参数签名。

运行沙盒,观察结果。

这个和我们要说的装饰器有什么关系呢,其实装饰器要做的事情就是我们上面的这个高阶函数做的事情。下面我们看看使用装饰器怎么处理这个问题。

def testThis():
...
@testThis
def sum(a, b):
...

装饰器的使用很简单,只需要先定义一个高阶函数。然后在你需要应用装饰器的函数上,使用@后面跟上testThis装饰器函数名。这个testThis装饰器就应用上了。

运行沙盒,观察结果。

需要注意的是,装饰器会改变原来函数的行为,所以如果这是你需要的效果最好,如果不是你得使用其他方法,比如之前的高阶函数的方式并不会修改原有的函数。只是生成了一个新的函数。

因此可以把装饰器的应用过程理解成:

@testThis
def sum(a, b):
...
# 上面的行为等价于
sum = testThis(sum)

装饰器的使用,让对函数的功能增强变得更为优雅。

保留注释

上面的sum使用@testThis装饰后,有一个缺陷,sum函数内部的文档注释失效了。你可以使用help(sum)或者print(sum.__doc__)测试下sum函数的文档注释,发现编程了wrapper函数,但这不是我们需要的。

为了保留下sum函数的元信息,我们需要用到另一个装饰器:@wraps(fn)利用它帮我们保留住sum自己的文档注释等原信息。

运行沙盒,观察结果。

带参数的装饰器

装饰器也能支持参数,带参数的装饰器可以让其变得更加灵活和强大。下面,我们还是对testThis装饰器进行改造,让他支持自定义自定义标题。

def testFn(title=None):
def decorate(fn):
@wraps(fn)
def wrapper(*args):
...
return wrapper
return decorate
@testFn("标题")
def sum(a,b):
...

带参数的装饰器,在定义是需要三层,最外层的函数获取装饰器中的参数,中间一层的用来接收fn

运行沙盒,观察结果。

装饰器的应用

在Web开发中,装饰器常用于权限鉴别,日志记录等。

from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.headers.get('Authorization', None)
if not auth:
return authenticate({'code': 'authorization_header_missing', 'description': '需要授权标头'})
...
return decorated
@requires_auth
def articles():
...

至此,本篇教程也到了该和你说再见的时候了,我们下期再见。

学完本篇互动教程,如果你觉得体验不错,可以把网页链接发送给你的小伙伴,让他/她也来感受一下。当然,你也可以继续看看网站上其他的的互动教程,希望`idev365`能够给你带来收获。

学习教程的过程中碰到了问题,或者对idev365有什么改进意见和想法,欢迎加入idev365微信内测群,和山地人交流你的想法。