I have hundreds of functions test1, test2, ...
Function testX may or may not have kwargs:
def test1(x, z=1, **kwargs):
pass
def test2(x, y=1):
pass
def test3(x, y=1, z=1):
pass
...
and I have a function call_center:
tests = [test1, test2, test3]
def call_center(**kwargs):
for test in tests:
test(**kwargs)
call_center will be called with various input parameters:
call_center(x=1,y=1)
call_center(x=1,z=1)
I don't want to filter kwargs by inspect.signature of the called function, because these function will be called millions of times, filtering at runtime costs a lot.
How can I define a decorator extend_kwargs that adds kwargs to functions that do not have kwargs?
I can use signature to find if kwargs exists.
def extender(func):
sig = signature(func)
params = list(sig.parameters.values())
if any(
param.name
for param in sig.parameters.values()
if param.kind == param.VAR_KEYWORD
):
return func
# add kwargs to func here
...
return func
and i tried add Parameter to signature like this
kwargs = Parameter("__auto_kwargs", Parameter.VAR_KEYWORD)
params.append(kwargs)
new_sig = sig.replace(parameters=params)
func.__signature__ = new_sig
It seems signature is just signature, does not affect the execution.
and i tried to modify __code__ of func.
old_code = func.__code__
code = old_code.replace(
co_varnames=old_code.co_varnames + ("__auto_kwargs",),
co_nlocals=old_code.co_nlocals + 1,
)
func.__code__ = code
Still not work
Refer to the accepted answer, the following code works.
The judgment is simplified and add the modification of co_flags.
def extender(func):
if not func.__code__.co_flags & inspect.CO_VARKEYWORDS:
code = func.__code__
func.__code__ = code.replace(
co_flags=code.co_flags | inspect.CO_VARKEYWORDS,
co_varnames=code.co_varnames + ("kwargs",),
co_nlocals=code.co_nlocals + 1,
)
return func
If you don't want to modify the original function directly, see the accepted answer
To add variable keyword parameters (kwargs) to a function that does not have it you can re-create the function with types.FunctionType but with the code object replaced with one that has the inspect.CO_VARKEYWORDS flag enabled in the co_flags attribute, the kwargs name added to the co_varnames attribute, and the number of local variables incremented by 1 in the co_nlocals attribute.
So a decorator that adds kwargs to functions that do not have it can look like:
import inspect
from types import FunctionType
def ensure_kwargs(func):
if func.__code__.co_flags & inspect.CO_VARKEYWORDS:
return func # already supports kwargs
return FunctionType(
code=func.__code__.replace(
co_flags=func.__code__.co_flags | inspect.CO_VARKEYWORDS,
co_varnames=func.__code__.co_varnames + ('kwargs',),
co_nlocals=func.__code__.co_nlocals + 1
),
globals=func.__globals__,
name=func.__name__,
argdefs=func.__defaults__,
closure=func.__closure__
)
so that:
def test1(x, z=1, **kwargs):
print(f'{x=}, {z=}, {kwargs=}')
def test2(x, y=1):
print(f'{x=}, {y=}')
def test3(x, y=1, z=1):
print(f'{x=}, {y=}, {z=}')
def call_center(**kwargs):
for test in tests:
test(**kwargs)
tests = list(map(ensure_kwargs, [test1, test2, test3]))
call_center(x=1, y=2)
call_center(x=3, z=4)
outputs:
x=1, z=1, kwargs={'y': 2}
x=1, y=2
x=1, y=2, z=1
x=3, z=4, kwargs={}
x=3, y=1
x=3, y=1, z=4
Demo: https://ideone.com/0aeT2m
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With