Проверьте, был ли аргумент передан позиционно или через ключевое слово

Рассмотрим функцию с сигнатурой f(a, b). В будущем я хотел бы изменить сигнатуру на f(a, *, b), запретив передачу b в качестве позиционного аргумента. Чтобы уменьшить влияние изменений, я хочу сначала отказаться от позиционирования b, предупреждая пользователей, которые это делают.

Для этого я хотел бы написать что-то вроде:

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

Результатом будет то, что

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

inspect.getargvalues(frame) не представляется достаточным. Объект ArgInfo просто обеспечивает

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

Возможна ли такая проверка даже в Python? Концептуально интерпретатору не требуется запоминать, был ли аргумент задан позиционно или как ключевое слово.

Было бы неплохо иметь поддержку Python 2, но это не обязательно.

Ответ 1

Вот довольно хакерское решение:

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

где intput: f(1, b=2) печатает good а f(1, 2) печатает do not do that

Ответ 2

Вы можете использовать оболочку, чтобы добавить дополнительный шаг между пользователем и функцией. На этом этапе вы можете проверить аргументы, прежде чем имена имеют значение. Обратите внимание, что это зависит от того факта, что b не имеет значения по умолчанию и всегда должно быть задано как arg.

functools.wraps используется для того, чтобы оформленная функция во многом напоминала оригинал.

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