Python:广义输入转换装饰器

问题描述 投票:1回答:1

编写装饰器来转换函数的输入: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!'

但是,如果我们想要转换函数的其他或所有输入呢?同样,编写一个特定的装饰器是基本的,但似乎没有一种自然的方式来编写(参数化)包装器的一般情况。

有任何想法吗?

python python-decorators
1个回答
0
投票

以下是我自己的问题的一些答案。如果我发现它更完整,我打算选择别人的答案作为答案。

看起来没有避免在预处理函数上强加协议,除非有一种模糊的方法来优雅地处理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'
© www.soinside.com 2019 - 2024. All rights reserved.