【问题标题】:How to intercept and preproccess arguments with decorators?如何使用装饰器拦截和预处理参数?
【发布时间】:2019-08-01 17:51:00
【问题描述】:

我有一种情况,我正在编写表单的许多函数

def func(arg):
    arg=pre_process(arg)
    return do_work(arg)

我希望能够以更简单的方式对 arg 进行预处理。我尝试使用如下示例中的装饰器


from typing import Callable


def super_decorator(arg_name: str, pre_fn: Callable) -> Callable:
    def decorator(fn):
        def inner(*args, **kwargs):
            assert arg_name in kwargs.keys()
            kwargs[arg_name] = pre_fn(arg_name)
            return fn(*args, **kwargs)

        return inner

    return decorator



#example

def pre_processor(x):
    return "pre-processed"


@super_decorator("arg1", pre_processor)
def func(*, arg1=None):
    return arg1


print(func(arg1="hello world"))

使用这种技术我得到了正确的输出

pre-processed

但是这有点 hacky,我必须强制关键字参数。有没有更好的办法?

【问题讨论】:

  • 必须强制关键字参数是什么意思?
  • 如果预处理的参数不固定为第一个(或第二个等)位置参数,您只需要关键字参数。
  • 好点,我猜不是使用关键字参数,而是将参数的索引与预处理器函数一起传递给最外层的装饰器并这样做。

标签: python python-3.x python-decorators


【解决方案1】:

您编写的装饰器比您的第一个示例所需的要通用得多。你可以简单地写

def compose(preprocessor):
    def decorator(f):
        def inner(arg):
            return f(preprocessor(arg))
        return inner
    return decorator


@compose(pre_processor)
def func(arg):
    return do_work(arg)

因此,为了确定您的super_decorator 是否设计良好,您需要更准确地定义它试图解决的问题。


我使用名称compose 是因为您的超级装饰器只是组合运算符的柯里化形式。如果你知道任何 Haskell,你会简单地写

func = do_work . pre_processor

【讨论】:

    【解决方案2】:

    我见过的一种模式是使用对函数的引用作为您希望将这些函数应用于的参数的注释

    from typing import Callable
    from functools import wraps
    from inspect import signature
    from collections import OrderedDict
    
    
    def use_annotations(func):
        sig = signature(func)
        @wraps(func)
        def inner(*args, **kwargs):
            bound = sig.bind(*args, **kwargs)
            bound.apply_defaults()
            bound.arguments = OrderedDict(
                (param, sig.parameters[param].annotation(value))
                if isinstance(sig.parameters[param].annotation, Callable)
                else (param, value)
                for param, value in bound.arguments.items()
            )
            return func(*bound.args, **bound.kwargs)
        return inner
    
    
    def pre_processor(x):
        return "pre-processed"
    
    
    @use_annotations
    def func(*, arg1: pre_processor=None):
        return arg1
    
    
    print(func(arg1="hello world"))
    

    这可能不适用于大多数希望您使用注释进行类型提示的工具。

    【讨论】:

    • 以前没用过签名,挺好看的
    【解决方案3】:

    有没有更好的办法?

    不,我不这么认为。

    代码审查时间。好的,在查看了您的代码后,我竖起大拇指。 LGTM。发货!

    对不起。我明白为什么你会觉得有很多仪式 在这段代码中进行,但你写的是最自然的表达方式。 感谢注释和使用typing

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-13
      • 2016-06-12
      • 1970-01-01
      • 1970-01-01
      • 2016-01-30
      • 1970-01-01
      • 2018-07-21
      • 2019-10-21
      相关资源
      最近更新 更多