繁体   English   中英

单个 function 上的多个装饰器

[英]Multiple decorators on single function

如何将多个装饰器添加到 function 以便每个配置组合成一个最终字典?

我的解释可能没有意义,但看看这个例子。


def instruction(name):
  def decorator(callback):
    def wrapper(self, *args, **kwargs):
      # somehow combine every additional dict into this dict
      return {
        "name": name,
        "callback": lambda: callback(self, *args, **kwargs)
      }
    return wrapper
  return decorator

def layout(config):
  def decorator(_):
    return {"layout": config}
  return decorator

# example decorator
def documentation(text):
  def decorator(_):
    return {"documentation": text}
  return decorator

@instruction("poke")
@layout({"address": 2, "data": 1})
@documentation("handle poke instruction") # additional decorators
def instruction_poke(self):
  address = self.serial_read_bytes(2)
  data = self.serial_read_bytes(1)
  print(f"poke {address=} {data=}")
  # ...

def main():
  self = ... # object with the serial_read_bytes method
  instruction = instruction_poke(self)
  print(instruction)
  # expected output
  # {
  #   "name": poke,
  #   "documentation": "handle poke instruction",
  #   "layout": {"address": 2, "data": 1},
  #   "callback": <lambda to original function>
  # }

if __name__ == "__main__":
  main()

我遇到了麻烦,因为多个装饰器具有类似堆栈的行为。

我目前的方法是将每个装饰器的字段存储在一个 constants.py 文件中,在我看来,这很麻烦,要弄乱几个文件。

我很感激你的时间:)

您可以创建另一个级别的闭包来概括装饰器配置器函数的创建。

The wrapper function should distinguish between an actual callback function and a stacked decorator, which can be done by testing if the code object of the callback function is that of the wrapper function itself, in which case it should simply return the dict returned by callback function添加了给定的键值对:

def add_config(key):
    def configurer(value):
        def decorator(callback):
            def wrapper(*args, **kwargs):
                if callback.__code__ is wrapper.__code__:
                    return {key: value, **callback(*args, **kwargs)}
                return {key: value, 'callback': lambda: callback(*args, **kwargs)}
            return wrapper
        return decorator
    return configurer

instruction, layout, documentation = map(
    add_config, ['name', 'layout', 'documentation']
)

@instruction('poke')
@layout({"address": 2, "data": 1})
@documentation("handle poke instruction")
def instruction_poke(i):
    return i + 1

instruction = instruction_poke(2)
print(instruction)
print(instruction['callback']())

这输出:

{'name': 'poke', 'layout': {'address': 2, 'data': 1}, 'documentation': 'handle poke instruction', 'callback': <function add_config.<locals>.configurer.<locals>.decorator.<locals>.wrapper.<locals>.<lambda> at 0x7fd9476fad30>}
3

演示: https://replit.com/@blhsing/SadHugeEquation

根据您对 go 的要求,有多种选择。 @blhsing 的那个当然效果很好。 这里还有一些:

用字典换行(简单)

def wrap_with_info(**kwargs):
    def decorator(obj):
        if isinstance(obj, dict):
            return obj | kwargs
        # `obj` must be a function
        assert callable(obj)
        return kwargs | {"callback": obj}
    return decorator

这允许您像这样装饰:

@wrap_with_info(
    name="poke",
    documentation="handle poke instruction",
    layout={"address": 2, "data": 1},
)
def your_function():
    pass

也像这样:

@wrap_with_info(name="poke")
@wrap_with_info(documentation="handle poke instruction")
@wrap_with_info(layout={"address": 2, "data": 1})
def your_function():
    pass

请注意,此方法本质上总是your_function转换为字典:

print(your_function)

给出这个 output:

{'layout': {'address': 2, 'data': 1}, 'callback': <function your_function at 0x7fd1184ae710>, 'documentation': 'handle poke instruction', 'name': 'poke'}

装饰返回字典

from functools import wraps


def add_info(**kwargs):
    def decorator(func):
        def wrapper(*func_args, **func_kwargs):
            @wraps(func)
            def callback():
                return func(*func_args, **func_kwargs)
            return kwargs | {"callback": callback}
        return wrapper
    return decorator

然后你也这样装饰:

@add_info(
    name="poke",
    documentation="handle poke instruction",
    layout={"address": 2, "data": 1},
)
def another_function():
    pass

但是现在another_function仍然是一个可调用的,它返回包含原始 function (包装)和附加数据的字典:

print(another_function())

给出这个 output:

{'name': 'poke', 'documentation': 'handle poke instruction', 'layout': {'address': 2, 'data': 1}, 'callback': <function another_function at 0x7fb4ae7a67a0>}

functools.wraps装饰器确保当我们将原始函数的签名粘贴到callback中时保留它。 这就是为什么你在 output 中看到它的名字,而不是像...local.callback...之类的东西。


(旁注:我使用|表示法来组合字典,自 Python 3.9 起可用。如果这对您不起作用,您可以使用其他方式,例如dict.update 。)


优点

看起来你对装饰器所做的只是将键值对添加到围绕实际 function 的信息字典中。 这种方法不需要使用多个装饰器。 相反,您可以根据 function 传递任意数量的键值对。

它们也可以动态构建,如果这是您需要的,并且如果您提前知道要添加到函数中的数据:

info_x = {"spam": 1, "eggs": 2}
info_y = {"info": "abc"}
info_z = {"something": ["else"]}

...

@add_info(**info_x, **info_y)
def foo():
    pass


@add_info(**info_y, **info_z)
def bar():
    pass


@wrap_with_info(**info_z, **info_x)
def baz():
    pass


print(foo())
print(bar())
print(baz)

给出这个 output:

{'spam': 1, 'eggs': 2, 'info': 'abc', 'callback': <function foo at 0x7f5d1abb29e0>}
{'info': 'abc', 'something': ['else'], 'callback': <function bar at 0x7f5d1abb29e0>}
{'something': ['else'], 'spam': 1, 'eggs': 2, 'callback': <function baz at 0x7efd492aeb00>}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM