编写装饰器并保存函数的元数据,代码简洁之道

    软件开发中的重要一条真理就是“不要重复自己的工作”。通常当我们需要创建高度重复的代码时,都可以寻找到一个更加优雅的解决方案。

1    给函数添加一个包装,让它做一点额外的工作

    当我们需要让一个函数拥有计时统计、打印日志的功能时,往往选择的方案就是直接在函数体中增加需要的代码。这在只有一两个函数的时候还可以接受,但是如果需要让一个项目中的所有函数都具有这样的功能时,就会变得十分繁琐。

    这时候,就需要我们使用“装饰器”了。示例如下:

from functools import wraps
import time

def logit(func):
    '''
    使用装饰器来打印函数调用信息
    '''

    @wraps(func)
    def wrapper(*args, **kwargs):
        print('start func {}'.format(func.__name__),
              time.strftime('at %Y %m %d %H:%M:%S', time.localtime()))
        result = func(*args, **kwargs)
        print('finish func {}'.format(func.__name__),
              time.strftime('at %Y %m %d %H:%M:%S', time.localtime()))
        return result
    return wrapper

    下面是使用演示:

>>> @logit
def countdown(n):
    while(n > 0):
        n -= 1
>>> countdown(10000000)
start func countdown at 2018 09 16 16:45:23
finish func countdown at 2018 09 16 16:45:24

    只需要在函数定义时为它增加一个装饰器(@logit),这个函数就能告诉我们它开始运行的时间以及结束运行的时间!

    装饰器其实就是一个函数,它可以接受一个函数作为输入并返回一个新的函数作为输出。

    装饰器内部的代码一般会涉及创建一个新的函数,利用*args和**kwargs可以接收任意的参数。在这个函数内部,我们调用原来的输入函数(即被包装的那个函数,它是装饰器的输入参数)并返回它的结果。此时,这个新创建的wrapper函数就会作为装饰器的结果返回,取代了原本的函数。

    需要强调的一点是,装饰器一般来说不会修改调用的签名,也不会修改被包装函数返回的结果。这里使用了*args和**kwargs来确保可以接受任何形式的输入参数。装饰器的返回值几乎总是同调用func(*args,**kwargs)的结果一致,这里的func就是那个未被包装过的原始函数。

2    那装饰器函数中的装饰器@wraps有什么作用

    装饰器@wraps可以用来保存底层的元数据,比如函数名、文档字符串、函数注解以及调用签名。

    如果没有使用该装饰器,获取上一个例子中countdown函数的元数据就会看起来像这样:

>>> countdown.__name__
'wrapper'
>>> countdown.__doc__
>>> countdown.__annotations__

    @wraps的另一个重要特性就是可以通过__wrapped__属性来访问那个被包装的函数,而该属性同时也可以使得装饰器函数可以合适的将底层被包装的函数的签名暴露出来:

>>> from inspect import signature
>>> print(signature(countdown))
(n:int)

QQ浏览器截图20180915190227.png

猜你喜欢

转载自blog.51cto.com/13972242/2175762