简体   繁体   English

如何编写更新关键字参数的 python 装饰器?

[英]How to write python decorator that updates keyword argument?

The goal is to write a decorator that updates one keyword argument of the wrapped function.目标是编写一个装饰器来更新包装的 function 的一个关键字参数。 In the following code wrapper attempts to update kwarg1 :在以下代码包装器中尝试更新kwarg1

import inspect                                                                                                                                                                                         [0/300678]
from functools import wraps

def override_me(arg, kwarg1="default kwarg1", kwarg2="default kwarg2"):
    print(f"override_me {arg} kwarg1={kwarg1} kwarg2={kwarg2}")

def append_kwarg1(func):
    original_kwarg1_default = (
        inspect.signature(func).parameters["kwarg1"].default
    )

    @wraps(func)
    def wrapper(*args, kwarg1=original_kwarg1_default, **kwargs):
        func(*args, kwarg1=kwarg1 + "_patched!", **kwargs)

    return wrapper

override_me = append_kwarg1(override_me)


override_me("passed_arg")
override_me("passed_arg", kwarg1="passed_kwarg1_named")
override_me("passed_arg", "passed_kwarg1_as_arg") # TypeError: override_me() got multiple values for argument 'kwarg1'

This, however, fails when kwarg1 is passed as a positional argument.但是,当kwarg1作为位置参数传递时,这会失败。

Edit: Clarifications as pointed in comments: override_me signature cannot be changed (think: external module).编辑:注释中指出的澄清:无法更改override_me签名(认为:外部模块)。

A working solution based on the fact that even a keyword argument has an index.一个可行的解决方案,基于即使是关键字参数也有索引这一事实。 Then depending on the length of args passed by caller we can determine if the argument of interest has been passed positionally or by keyword and update it either in args or kwargs :然后根据调用者传递的args的长度,我们可以确定感兴趣的参数是按位置传递还是通过关键字传递,并在argskwargs中更新它:


import inspect                                                                                                                                                                                         
from functools import wraps

def override_me(arg, kwarg1="default kwarg1", kwarg2="default kwarg2"):
    print(f"override_me {arg} kwarg1={kwarg1} kwarg2={kwarg2}")

def append_kwarg1(func):
    params = inspect.signature(func).parameters
    kwarg1_index = next(
        x[0] for x in zip(range(len(params)), params.items()) if x[1][0] == "kwarg1"
    )

    def update(v):
        return v + "_patched!"

    @wraps(func)
    def wrapper(*args, **kwargs):
        if len(args) > kwarg1_index:
            args = (
                args[:kwarg1_index]
                + (update(args[kwarg1_index]),)
                + args[kwarg1_index + 1 :]
            )
            func(*args, **kwargs)
        else:
            kwargs["kwarg1"] = update(kwargs.get("kwarg1", params["kwarg1"].default))
            func(*args, **kwargs)

    return wrapper


override_me = append_kwarg1(override_me)

override_me("passed_arg", kwarg1="passed_kwarg1_named")
override_me("passed_arg")
override_me("passed_arg", "passed_kwarg1_as_arg")
override_me("passed_arg", "passed_kwarg1_as_arg", "passed_kwarg2_as_arg")

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

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