leezu
leezu

Reputation: 532

Inspect if an argument was passed positionally or via keyword

Consider a function with signature f(a, b). In future, I would like to change the signature to f(a, *, b), disallowing b to be passed as positional argument. To reduce the impact of the change, I want to first deprecate specifying b positionally, warning users that do so.

For that I would like to write something like:

def f(a, b):
    frame = inspect.currentframe()
    if b in frame.specified_as_positional:
        print('Do not do that')
    else:
        print('Good')

The result would be that

>>> f(1, 2)
'Do not do that'
>>> f(1, b=2)
'Good'

inspect.getargvalues(frame) does not seem to be sufficient. The ArgInfo object just provides

>>> f(1,b=2)
ArgInfo(args=['a', 'b'], varargs=None, keywords=None, locals={'a': 1, 'b': 2})

Is such inspection even possible in Python? Conceptually the interpreter does not seem to be required to remember if a argument was specified positionally or as keyword.

Python 2 support would be nice to have but is not strictly required.

Upvotes: 15

Views: 460

Answers (2)

Ben Vizena
Ben Vizena

Reputation: 51

Here is a pretty hacky solution:

def f(a, c=None, b=None):
    if c is not None:
        print("do not do that")
    else:
        print("good")

where input f(1, b=2) prints good and f(1, 2) prints do not do that

Upvotes: 4

Nathan Werth
Nathan Werth

Reputation: 5263

You can use a wrapper to add an extra step between the user and the function. In that step, you can examine the arguments before the names matter. Note that this depends on the fact that b doesn't have a default value and always must be given as an arg.

functools.wraps is used to make the decorated function resemble the original in a bunch of ways.

import functools
import warnings

def deprecate_positional(fun):
    @functools.wraps(fun)
    def wrapper(*args, **kwargs):
        if 'b' not in kwargs:
            warnings.warn(
                'b will soon be keyword-only',
                DeprecationWarning,
                stacklevel=2
                )
        return fun(*args, **kwargs)
    return wrapper

@deprecate_positional
def f(a, b):
    return a + b
>>> f(1, b=2)
3
>>> f(1, 2)
Warning (from warnings module):
  File "C:/Users/nwerth/Desktop/deprecate_positional.py", line 36
    print(f(1, 2))
DeprecationWarning: b will soon be keyword-only
3

Upvotes: 4

Related Questions