如何编写更新关键字参数的 python 装饰器?
How to write python decorator that updates keyword argument?
目标是编写一个装饰器来更新包装函数的一个关键字参数。
在以下代码包装器中尝试更新 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'
但是,当 kwarg1
作为位置参数传递时,这会失败。
编辑:
注释中指出的说明:override_me
签名无法更改(想想:外部模块)。
基于即使关键字参数也有索引这一事实的有效解决方案。然后根据调用者传递的 args
的长度,我们可以确定感兴趣的参数是按位置传递还是按关键字传递,并在 args
或 kwargs
中更新它:
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")
目标是编写一个装饰器来更新包装函数的一个关键字参数。
在以下代码包装器中尝试更新 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'
但是,当 kwarg1
作为位置参数传递时,这会失败。
编辑:
注释中指出的说明:override_me
签名无法更改(想想:外部模块)。
基于即使关键字参数也有索引这一事实的有效解决方案。然后根据调用者传递的 args
的长度,我们可以确定感兴趣的参数是按位置传递还是按关键字传递,并在 args
或 kwargs
中更新它:
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")