Dado
Dado

Reputation: 23

Decorator that changes certain argument of function

Working on a new version of the library I change one of the default arguments of several functions. So I'd like to add a temporary warning that occurs when user calls a function without explicitly specified parameters (so function is called with its defaults).

It could be easily done just by adding the warning function and calling it inside each of base functions:

def warning(formatting):
    if formatting is None:
        sys.stderr.write("WARNING: The default format has changed to new_format")
        return 'new_format'
    return formatting

def my_function(arg1, arg2, formatting=None):
    formatting = warning(formatting)
    ...  # the following function code

However it would be more convenient to do it using decorator (for code readability). So I've implemented something like this:

def check_default_format(fun):
    def warning(*a, **kw):
        if 'formatting' not in kw.keys() or kw['formatting'] is None:
            kw['formatting'] = 'new_format'
            sys.stderr.write("WARNING: The default format has changed to new_format")
        return fun(*a, **kw)
    return warning

@check_default_format
def my_function(arg1, arg2, formatting=None):
    ...  # the function code

That works as expected when I call my_function without formatting parameter and if formatting is specified as a keyword parameter. But how to include the possibility that my_function can be called with only positional parameters? Calling decorated my_function('arg1', 'arg2', 'some_format') will produce an TypeError due to duplication of formatting parameter.

Note: I cannot assume that the formatting is always the 3rd parameter as I need to decorate different functions. I also cannot change the parameters order to preserve backward compatibility.

Upvotes: 0

Views: 229

Answers (1)

Aran-Fey
Aran-Fey

Reputation: 43136

In python 3, you can use the inspect module's Signature.bind_partial:

def check_default_format(fun):
    @wraps(fun)
    def wrapper(*a, **kw):
        sig= inspect.signature(fun)
        args= sig.bind_partial(*a, **kw)

        if 'formatting' not in args.arguments or args.arguments['formatting'] is None:
            kw['formatting'] = 'new_format'
            sys.stderr.write("WARNING: The default format has changed to new_format")
        return fun(*a, **kw)
    return wrapper

Upvotes: 1

Related Questions