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