「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」
函数装饰器
函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
举个栗子:
@decorate
def target():
print('running target()')
# 相当于
def target():
print('running target()')
target = decorate(target)
装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二个特性是,它们在被装饰的函数定义之后立即运行;这通常是在导入时(即 Python 加载模块时)。
装饰器通常在一个模块中定义,然后应用到其他模块中的函数上。
大多数装饰器会在内部定义一个函数,然后将其返回。
标准库中的装饰器
Python 内置了三个用于装饰方法的函数:property 、classmethod 和 staticmethod 。
另一个常见的装饰器是 functools.wraps ,它的作用是协助构建行为良好的装饰器。标准库中最值得关注的两个装饰器是 lru_cache 和 singledispatch (Python 3.4 新增)。 这两个装饰器都在 functools 模块中定义。
functools.lru_cache
functools.lru_cache 实现了备忘 (memoization)功能。它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。LRU 三个字母是 “Least Recently Used” 的缩写,表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。
lru_cache 可以使用两个可选的参数来配置。它的签名是:
functools.lru_cache(maxsize=128, typed=False)
maxsize 参数指定存储多少个调用的结果。缓存满了之后,旧的结果会被扔掉,腾出空间。为了得到最佳性能,maxsize 应该设为 2 的幂。 typed 参数如果设为 True ,把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整数参数(如 1 和 1.0 )区分开。
因为 lru_cache 使用字典存储结果,而且键根据调用时传入的定位参数和关键字参数创建,所以被 lru_cache 装饰的函数,它的所有参数都必须是可散列的。
functools.singledispatch
因为 Python 不支持重载方法或函数,所以我们不能使用不同的签名定义同一函数名的函数的变体,也无法使用不同的方式处理不同的数据类型。在 Python 中,一种常见的做法是使用一串 if/elif/elif ,调用专门的函数,如 functionA_str 、functionA_int ,等等。这样不便于模块的用户扩展,还显得笨拙:时间一长,分派函数 functionA 会变得很大,而且它与各个专门函数之间的耦合也很紧密。
functools.singledispatch 装饰器可以把整体方案拆分成多个模块,甚至可以为你无法修改的类提供专门的函数。使用 @singledispatch 装饰的普通函数会变成泛函数(generic function): 根据第一个参数的类型,以不同方式执行相同操作的一组函数。
可以在系统的任何地方和任何模块中注册专门函数。
举个栗子:
from functools import singledispatch
import numbers
import html
@singledispatch # 标记处理 object 类型的基函数
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@htmlize.register(str) # 各个专门函数使用 @«base_function».register(«type») 装饰。
def _(text): # 专门函数的名称无关紧要;_ 是个不错的选择
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)
@htmlize.register(numbers.Integral)
def _(n):
return '<pre>{0} (0x{0:x})</pre>'.format(n)
参数化装饰器
让装饰器接受其他参数:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。
举个栗子:
registry = set()
def register(active=True): # 接受一个可选的关键字参数
def decorate(func): # 真正的装饰器
print('running register(active=%s)->decorate(%s)' % (active, func))
if active: # 只有 active 参数的值(从闭包中获取)是 True 时才注册 func
registry.add(func)
else: # 如果 active 不为真,而且 func 在 registry 中,那么把它删除
registry.discard(func)
return func # decorate 是装饰器,必须返回一个函数
return decorate
@register(active=False) # @register 工厂函数必须作为函数调用,并且传入所需的参数
def f1():
print('running f1()')
@register() # 即使不传入参数,register 也必须作为函数调用,即要返回真正的装饰器 decorate
def f2():
print('running f2()')