Python函数装饰器高级用法

在了解了Python函数装饰器基础知识和闭包之后,开始正式学习函数装饰器。

典型的函数装饰器

以下示例定义了一个装饰器,输出函数的运行时间:

image-20210527083935919

函数装饰器和闭包紧密结合,入参func代表被装饰函数,通过自由变量绑定后,调用函数并返回结果。

使用clock装饰器:

import time from clockdeco import clock @clock def snooze(seconds): time.sleep(seconds) @clock def factorial(n): return 1 if n < 2 else n*factorial(n-1) if __name__=='__main__': print('*' * 40, 'Calling snooze(.123)') snooze(.123) print('*' * 40, 'Calling factorial(6)') print('6! =', factorial(6)) # 6!指6的阶乘

输出结果:

image-20210527085352366

这是装饰器的典型行为:把被装饰的函数换成新函数,二者接受相同的参数,而且返回被装饰的函数本该返回的值,同时还会做些额外操作。

值得注意的是factorial()是个递归函数,从结果来看,每次递归都用到了装饰器,打印了运行时间,这是因为如下代码:

@clock def factorial(n): return 1 if n < 2 else n*factorial(n-1)

等价于:

def factorial(n): return 1 if n < 2 else n*factorial(n-1) factorial = clock(factorial)

factorial引用的是clock(factorial)函数的返回值,也就是装饰器内部函数clocked,每次调用factorial(n),执行的都是clocked(n)。

叠放装饰器 @d1 @d2 def f(): print("f")

等价于:

def f(): print("f") f = d1(d2(f)) 参数化装饰器

怎么让装饰器接受参数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。

示例如下:

registry = set() def register(active=True): def decorate(func): print('running register(active=%s)->decorate(%s)' % (active, func)) if active: registry.add(func) else: registry.discard(func) return func return decorate @register(active=False) def f1(): print('running f1()') # 注意这里的调用 @register() def f2(): print('running f2()') def f3(): print('running f3()')

register是一个装饰器工厂函数,接受可选参数active默认为True,内部定义了一个装饰器decorate并返回。需要注意的是装饰器工厂函数,即使不传参数,也要加上小括号调用,比如@register()。

再看一个示例:

import time DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' # 装饰器工厂函数 def clock(fmt=DEFAULT_FMT): # 真正的装饰器 def decorate(func): # 包装被装饰的函数 def clocked(*_args): t0 = time.time() # _result是被装饰函数返回的真正结果 _result = func(*_args) elapsed = time.time() - t0 name = func.__name__ args = ', '.join(repr(arg) for arg in _args) result = repr(_result) # **locals()返回clocked的局部变量 print(fmt.format(**locals())) return _result return clocked return decorate if __name__ == '__main__': @clock() def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)

这是给典型的函数装饰器添加了参数fmt,装饰器工厂函数增加了一层嵌套,示例中一共有3个def

标准库中的装饰器

Python内置了三个用于装饰方法的函数:property、classmethod和staticmethod,这会在将来的文章中讲到。本文介绍functools中的三个装饰器:functools.wraps、functools.lru_cache和functools.singledispatch。

functools.wraps

Python函数装饰器在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用(它能保留原有函数的名称和函数属性)。

示例,不加wraps:

def my_decorator(func): def wrapper(*args, **kwargs): '''decorator''' print('Calling decorated function...') return func(*args, **kwargs) return wrapper @my_decorator def example(): """Docstring""" print('Called example function') print(example.__name__, example.__doc__) # 输出wrapper decorator

加wraps:

import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): '''decorator''' print('Calling decorated function...') return func(*args, **kwargs) return wrapper @my_decorator def example(): """Docstring""" print('Called example function') print(example.__name__, example.__doc__) # 输出example Docstring functools.lru_cache

lru是Least Recently Used的缩写,它是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzxygs.html