编写装饰器来转换函数的输入:Basic。
编写一个函数,为任何单输入变换器创建输入转换装饰器:简单
这是一种方式:
def input_wrap_decorator(preprocess):
def decorator(func):
def func_wrapper(*args, **kwargs):
return func(preprocess(*args, **kwargs))
return func_wrapper
return decorator
考虑以下功能:
def red_riding_hood(adj, noun='eyes'):
return 'What {adj} {noun} you have!'.format(adj=adj, noun=noun)
使用示例:
assert red_riding_hood('big') == 'What big eyes you have!'
assert red_riding_hood('long', 'ears') == 'What long ears you have!'
我们的input_wrap_decorator
允许我们随意轻松地改变red_riding_hood
的第一个参数:
wrapped_func = input_wrap_decorator(lambda x: x.upper())(red_riding_hood)
assert wrapped_func('big') == 'What BIG eyes you have!'
wrapped_func = input_wrap_decorator(lambda x: 'very ' + x)(red_riding_hood)
assert wrapped_func('big') == 'What very big eyes you have!'
但是,如果我们想要转换函数的其他或所有输入呢?同样,编写一个特定的装饰器是基本的,但似乎没有一种自然的方式来编写(参数化)包装器的一般情况。
有任何想法吗?
以下是我自己的问题的一些答案。如果我发现它更完整,我打算选择别人的答案作为答案。
看起来没有避免在预处理函数上强加协议,除非有一种模糊的方法来优雅地处理args / kwargs难题。 (只是因为我不知道它而模糊不清。)
这有几个选择。
preprocess返回(转换)args元组
def wrap_args_deco(preprocess):
"""Preprocess needs to return the tuple of args (non-keyworded arguments)
that should be passed on to the decorated func."""
def decorator(func):
def func_wrapper(*args, **kwargs):
# NOTE: the only difference with input_wrap_decorator is the * before the preprocess
return func(*preprocess(*args, **kwargs))
return func_wrapper
return decorator
使用示例:
def trans_args(adj, noun):
'''adj is capitalized and noun is quoted'''
return adj.upper(), '"{}"'.format(noun)
wrapped_func = wrap_args_deco(trans_args)(red_riding_hood)
assert wrapped_func('big', 'eyes') == 'What BIG "eyes" you have!'
这里有一些限制。例如,您需要为没有默认值的所有参数指定转换,无论您是否要转换它们。
预处理返回(转换)kwargs dict
def wrap_kwargs_deco(preprocess):
"""Preprocess needs to return the dict of kwargs (keyworded, or named arguments)
that should be passed on to the decorated func."""
def decorator(func):
def func_wrapper(*args, **kwargs):
# NOTE: the only difference with input_wrap_decorator is the ** before the preprocess
return func(**preprocess(*args, **kwargs))
return func_wrapper
return decorator
例:
def trans_kwargs(adj, noun):
'''adj is capitalized and noun is quoted'''
return {'adj': adj.upper(), 'noun': '"{}"'.format(noun)}
wrapped_func = wrap_kwargs_deco(trans_kwargs)(red_riding_hood)
assert wrapped_func('big', 'eyes') == 'What BIG "eyes" you have!'
preprocess返回一个(转换的)(args,kwargs)元组
通过让预处理函数返回变换的args(元组)和kwargs(dict),你可以得到(sorta)两个世界中最好的。
def wrap_args_and_kwargs_deco(preprocess):
"""Preprocess needs to return a the tuple (arg, kwargs) where
arg is the list/tuple of (transformed) non-keyworded arguments and
kwargs is the dict of (transformed) keyworded (a.k.a. "named") arguments
that should be passed on to the decorated func."""
def decorator(func):
def func_wrapper(*args, **kwargs):
args, kwargs = preprocess(*args, **kwargs)
return func(*args, **kwargs)
return func_wrapper
return decorator
例:
def trans_kwargs(adj, noun):
return (adj.upper(),), {'noun': '"{}"'.format(noun)}
wrapped_func = wrap_args_and_kwargs_deco(trans_kwargs)(red_riding_hood)
assert wrapped_func('big', 'eyes') == 'What BIG "eyes" you have!'
为单个(关键字)参数指定变换器
上面提出的选项比以下更通用,因为它们可以将转换逻辑建立在所有参数值的全局视图上。但在大多数情况下,您可能只需要根据其名称转换参数。在这种情况下,这是一个提供更好界面的问题的解决方案。
from functools import wraps
def transform_args(**trans_func_for_arg):
"""
Make a decorator that transforms function arguments before calling the function.
Works with plain functions and bounded methods.
"""
def transform_args_decorator(func):
if len(trans_func_for_arg) == 0: # if no transformations were specified...
return func # just return the function itself
else:
@wraps(func)
def transform_args_wrapper(*args, **kwargs):
# get a {argname: argval, ...} dict from *args and **kwargs
# Note: Didn't really need an if/else here but I am assuming that...
# Note: ... getcallargs gives us an overhead that can be avoided if there's only keyword args.
if len(args) > 0:
val_of_argname = inspect.signature(func).bind_partial(*args, **kwargs).arguments
else:
val_of_argname = kwargs
for argname, trans_func in trans_func_for_arg.items():
val_of_argname[argname] = trans_func(val_of_argname[argname])
# apply transform functions to argument values
return func(**val_of_argname)
return transform_args_wrapper
return transform_args_decorator
这是一个功能覆盖范围比其他功能更多的示例:
# Example with a plain function
def f(a, b, c='default c'):
return "a={a}, b={b}, c={c}".format(a=a, b=b, c=c)
def prepend_root(x):
return 'ROOT/' + x
def test(f):
assert f('foo', 'bar', 3) == 'a=foo, b=bar, c=3'
ff = transform_args()(f) # no transformation specification, so function is unchanged
assert ff('foo', 'bar', 3) == 'a=foo, b=bar, c=3'
ff = transform_args(a=prepend_root)(f) # prepend root to a
assert ff('foo', 'bar', 3) == 'a=ROOT/foo, b=bar, c=3'
ff = transform_args(b=prepend_root)(f) # prepend root to b
assert ff('foo', 'bar', 3) == 'a=foo, b=ROOT/bar, c=3'
ff = transform_args(a=prepend_root, b=prepend_root)(f) # prepend root to a and b
assert ff('foo', 'bar', 3) == 'a=ROOT/foo, b=ROOT/bar, c=3'
test(f)
# Example with a bounded method
class A:
def __init__(self, sep=''):
self.sep = sep
def f(self, a, b, c):
return f"a={a}{self.sep} b={b}{self.sep} c={c}"
a = A(sep=',')
test(a.f)
# Example of decorating the method on the class itself
A.f = transform_args(a=prepend_root, b=prepend_root)(A.f)
a = A(sep=',')
assert a.f('foo', 'bar', 3) == 'a=ROOT/foo, b=ROOT/bar, c=3'