Search code examples
pythonparameter-passingpython-decoratorskeyword-argument

Is there a built-in way to simulate assignment of values to arguments?


I have a decorator that I use to suppress and log exceptions within a function. The code is something like this:

def log_exceptions(func):
    def wrapper(*args, **kwargs):
        try:
            ret = func(*args, **kwargs)
        except Exception as e:
            print(e)
            print(args, kwargs)  # also log what arguments caused the exception
        return ret
    return wrapper

A problem here is that, it is hard to manually match the printed argument values to the argument names of the function, because positional arguments could also go inside kwargs, and there may be args and kwargs arguments in the inner function as well. So, it would be useful to match the args and kwargs values in the wrapper with argument names in the inner function.

My question is then, is there a built-in way to do this matching? If not, what would be an elegant way to implement it?


Solution

  • If the signature can be determined, then inspect.signature(func).bind(*args, **kwargs) does the job.

    inspect.signature(func) (lowercase s) attempts to compute a Signature object (capital S) representing a callable's signature, and the resulting Signature's bind method matches the signature against provided arguments, returning a BoundArguments object. The arguments attribute of the resulting object is an ordered mapping of parameter names to values, excluding arguments that fell back on default values.

    Here's an example of how you might use it:

    import inspect
    
    try:
        sig = inspect.signature(func)
    except (TypeError, ValueError):
        print('Could not determine function signature.')
    else:
        try:
            bound = sig.bind(*args, **kwargs)
        except TypeError:
            print('Could not bind arguments to function signature.')
            print('Signature:', sig)
            print('Positional arguments:', args)
            print('Keyword arguments:', kwargs)
        else:
            # You can use bound.apply_defaults() to include defaults
            print('Bound arguments (defaults not included):')
            for name, value in bound.parameters:
                print('{}: {!r}'.format(name, value))