Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add function parameter kwargs during runtime?

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

like image 854
lei zhang Avatar asked Dec 06 '25 15:12

lei zhang


1 Answers

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

like image 186
blhsing Avatar answered Dec 09 '25 02:12

blhsing



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!