编写装饰器以转换函数的输入:基本。
编写一个函数,使其成为任何单输入变压器的输入变换装饰器:
这是一种方法:
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!'
但是,如果我们要转换函数的其他或全部输入,该怎么办?同样,编写一个特定的装饰器是基本的,但是似乎没有一种自然的方法可以为一般情况编写一个(参数化的)包装器。
有什么想法吗?
答案 0 :(得分:0)
以下是我自己问题的一些答案。如果我觉得更完整,我打算选择其他人的答案作为答案。
似乎没有避免在预处理函数上加上协议的方法,除非有一种模糊的方法来优雅地处理args / kwargs难题。 (仅因为我不知道而遮遮掩掩。)
这里有几种选择。
预处理返回(已转换)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字典
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!'
预处理返回一个(已转换的)(args,kwargs)元组
通过使预处理函数返回转换后的args(元组)和kwargs(dict),您可以(排序)两全其美。
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'