Python convert args to kwargs

前端 未结 6 1041
南方客
南方客 2021-02-05 07:57

I am writing a decorator that needs to call other functions prior to call of the function that it is decorating. The decorated function may have positional arguments, but the f

6条回答
  •  别跟我提以往
    2021-02-05 08:13

    Here's a newer method to solve this using inspect.signature (for Python 3.3+). I'll give an example that can be run / tested yourself first and then show how to modify the original code with it.

    Here's a test function which just sums up any args/kwargs given to it; at least one argument is required (a) and there's one keyword-only argument with a default value (b), just to test different aspects of function signatures.

    def silly_sum(a, *args, b=1, **kwargs):
        return a + b + sum(args) + sum(kwargs.values())
    

    Now let's make a wrapper for silly_sum which can be called in the same way as silly_sum (with an exception which we'll get to) but that only passes in kwargs to the wrapped silly_sum.

    def wrapper(f):
        sig = inspect.signature(f)
        def wrapped(*args, **kwargs):
            bound_args = sig.bind(*args, **kwargs)
            bound_args.apply_defaults()
            print(bound_args) # just for testing
    
            all_kwargs = bound_args.arguments
            assert len(all_kwargs.pop("args")) == 0
            all_kwargs.update(all_kwargs.pop("kwargs"))
            return f(**all_kwargs)
        return wrapped
    

    sig.bind returns a BoundArguments object, but this doesn't take defaults into account unless you call apply_defaults explicitly. Doing so will also generate an empty tuple for args and an empty dict for kwargs if no *args/**kwargs were given.

    sum_wrapped = wrapper(silly_sum)
    sum_wrapped(1, c=9, d=11)
    # prints 
    # returns 22
    

    Then we just get the dictionary of arguments and add any **kwargs in. The exception to using this wrapper is that *args can't be passed to the function. This is because there are no names for these, so we can't convert them into kwargs. If passing them through as a kwarg named args is acceptable, that could be done instead.


    Here is how this can be applied to the original code:

    import inspect
    
    
    class mydec(object):
        def __init__(self, f, *args, **kwargs):
            self.f = f
            self._f_sig = inspect.signature(f)
    
        def __call__(self, *args, **kwargs):
            bound_args = self._f_sig.bind(*args, **kwargs)
            bound_args.apply_defaults()
            all_kwargs = bound_args.arguments
            assert len(all_kwargs.pop("args")) == 0
            all_kwargs.update(all_kwargs.pop("kwargs"))
            hozer(**all_kwargs)
            self.f(*args, **kwargs)
    

提交回复
热议问题