代码环境:python3.6

什么是装饰器

装饰器是 python 的一种语法糖,本质是一个可调用的对象,其参数是一个被装饰的函数。装饰器可能会处理被装饰的函数并返回,或者将其替换成另一个函数或对象。

装饰器两种常用的用法

  • 原封不动返回被装饰函数

这种用法出现在很多 python web 框架中,例如把 url 地址映射到 http 响应的函数上的注册处。

下面举个简单的例子:

def register(func):
    # 内部无新函数
    print('running register {}'.format(func))
    return func


@register
def my_func():
    print('running my_func()')


if __name__ == "__main__":
    my_func()
  • 把被装饰函数替换成新函数

大多数装饰器,通常内部会定义一个闭包结构的函数,将其返回替换被装饰函数。

这种用法最常见,用于不修改原函数的基础上增加额外的功能,比如计算函数的运行时间、输出指定格式日志等。

下面用一个装饰器输出函数运行时间:

def running_time(func):
    # 内部有新函数
    def print_running_time(*args):
        '''打印函数运行时间'''
        t0 = time.time()
        result = func(*args)
        need_time = time.time() - t0
        print('新列表生成时间(秒):{:.8f}'.format(need_time))
        return result

    return print_running_time


@running_time
def new_list(n):
    '''生成一个新列表'''
    temp_list = []
    for x in range(n):
        temp_list.append(x * (x + 1))
    return temp_list


if __name__ == "__main__":
    print('新列表长度:{}'.format(len(new_list(12345))))
    print('new_list 函数的 __name__ 属性:{}'.format(new_list.__name__))
    print('new_list 函数的 __doc__ 属性:{}'.format(new_list.__doc__))

执行结果:

running register <function my_func at 0x0000000002889B70>
新列表生成时间(秒):0.00250006
新列表长度:12345
new_list 函数的 __name__ 属性:print_running_time
new_list 函数的 __doc__ 属性:打印函数运行时间

python何时执行装饰器

从上述例子中我们注意到,在调用new_list(12345)打印出结果之前,结果栏先输出了装饰器中的print语句,这说明:

装饰器在导入模块@func时立即执行。

functools.wraps装饰器

在上述结果中,我们还注意到另一个特点:new_list函数的__name____doc__属性都被替换成装饰器内部函数的相关属性。所以,我们需要改进上面的例子,使用functools.wraps装饰器把相关属性从func复制到新函数中。

改进例子如下:

from functools import wraps


def running_time(func):
    # 内部有新函数

    @wraps(func)
    # 此处使用 wraps 装饰器
    def print_running_time(*args):
        '''打印函数运行时间'''
        t0 = time.time()
        result = func(*args)
        need_time = time.time() - t0
        print('新列表生成时间(秒):{:.8f}'.format(need_time))
        return result

    return print_running_time


@running_time
def new_list(n):
    '''生成一个新列表'''
    temp_list = []
    for x in range(n):
        temp_list.append(x * (x + 1))
    return temp_list


if __name__ == "__main__":
    print('新列表长度:{}'.format(len(new_list(12345))))
    print('new_list 函数的 __name__ 属性:{}'.format(new_list.__name__))
    print('new_list 函数的 __doc__ 属性:{}'.format(new_list.__doc__))

执行结果:

running register <function my_func at 0x0000000002879B70>
新列表生成时间(秒):0.00250006
新列表长度:12345
new_list 函数的 __name__ 属性:new_list
new_list 函数的 __doc__ 属性:生成一个新列表

观察改进后例子的运行结果,new_list函数的相关属性已恢复正常。


oldk
3 声望2 粉丝