Python 向装饰器添加参数,一个常见的需求是使用额外的参数去自定义一个装饰器。我们可以做一些更复杂的处理,而不只是简单地创建一个复合函数 f o g(x)
。使用参数化装饰器可以创建 (f(c) o g)(x)
。前面已经使用了参数 C 作为创建封装器 f(c)
的一部分。这个参数化的复合函数 f(c) o g
之后便可以和实际数据 x 一同使用了。
在Python中,可以编写如下代码:
@deco(arg)
def func(x):
something
这里包含两个步骤。首先是将参数应用于抽象装饰器以创建具体的装饰器,然后将该具体的装饰器,即参数化的函数deco(arg)
,应用于基函数定义中来创建装饰器函数。
该装饰器函数的效果如下所示:
def func(x):
return something(x)
concrete_deco = deco(arg)
func= concrete_deco(func)
实际上做了以下三件事:
(1) 定义一个函数func()
;
(2) 对参数arg
应用抽象装饰器deco()
来创建一个具体装饰器concrete_deco()
;
(3) 对基函数应用该具体装饰器concrete_deco()
来创建函数的装饰器版本,实际上就是deco(arg)(func)
。
带参数的装饰器包含了对最终函数的间接构造。这似乎已经超越了单纯的高阶函数,进入了更为抽象的领域,即创建高阶函数的高阶函数。
可以扩展能感知不良数据的装饰器来创建一个更灵活的转换器。下面定义一个@bad_char_remove
装饰器,它以待删除字符为参数。这个参数化的装饰器如下:
import decimal
def bad_char_remove(
*char_list: str
) -> Callable[[F], F]:
def cr_decorator(function: F) -> F:
@wraps(function)
def wrap_char_remove(text: str, *args, **kw):
try:
return function(text, *args, **kw)
except (ValueError, decimal.InvalidOperation):
cleaned = clean_list(text, char_list)
return function(cleaned, *args, **kw)
return cast(F, wrap_char_remove)
return cr_decorator
一个参数化的装饰器具有两个内部函数定义。
- 抽象装饰器:
cr_decorator
函数会将其绑定的自由变量char_list
变成具体的装饰器。随后返回该装饰器将其应用于函数,这将返回一个封装在wrap_char_remove
函数内的函数。这里作为类型提示的类型变量F
声明了封装操作将保留被封装函数的类型。 - 被装饰的封装器:
wrap_char_remove
函数会用封装版本替代原始函数。由于使用了@wraps
装饰器,因此被封装的基函数的名字将替代新函数的__name__
属性(以及其他属性)。
整个装饰器bad_char_remove()
函数的任务是将参数绑定到抽象装饰器并返回具体装饰器。类型提示阐明了返回值是一个能将Callable
函数转换为另一个Callable
函数的Callable
对象。之后根据语言规则,会将具体装饰器应用于之后的函数定义。
下面的clean_list()
函数用于删除在给定列表中的所有字符。
from typing import Tuple
def clean_list(text: str, char_list: Tuple[str, ...]) -> str:
if char_list:
return clean_list(
text.replace(char_list[0], ""), char_list[1:])
return text
其中的规则十分简单,因此使用了递归方法,也可以把它优化成一个循环。
可以使用@bad_char_remove
装饰器创建转换函数,如下所示·:
@bad_char_remove("$", ",")
def currency(text: str, **kw) -> Decimal:
return Decimal(text, **kw)
上面使用了装饰器来封装currency()
函数。函数currency()
的本质特征是对decimal.Decimal
构造器的引用。
这个currency()
函数可以处理不同的数据格式了。
>>> currency("13")
Decimal('13')
>>> currency("3.14")
Decimal('3.14')
>>> currency("1,701.00")
Decimal('1701.00')
接下来可以使用一个相对简单的map(currency, row)
方法处理输入数据,以便将源数据从字符串转换为可用的Decimal
值了。错误处理语句try:/except:
已经隔离到了一个用于构建复合转换函数的函数中。
可以使用类似的设计来创建可以容错空值的函数。这些函数将使用类似的try:/except:
封装器,但只简单地返回None
值。