*args, **kwargs и управляемые параметры функций Python

Сигнатура функции задаёт не только имена параметров, но и допустимый способ передачи каждого значения. Python различает позиционные-only, позиционные или именованные,…

Сигнатура функции задаёт не только имена параметров, но и допустимый способ передачи каждого значения. Python различает позиционные-only, позиционные или именованные, variadic-позиционные и keyword-only параметры, а также приём оставшихся именованных аргументов. Точная сигнатура превращает ошибочный вызов в ранний TypeError, а не в неоднозначное поведение внутри функции.

Виды параметров в сигнатуре

Параметры до символа / можно передать только по позиции. Обычные параметры между / и * допускают обе формы. *args собирает лишние позиционные значения в кортеж. После одиночной звёздочки или *args параметры становятся keyword-only. Наконец, **kwargs собирает оставшиеся именованные аргументы в словарь.

def format_event(event, /, *details, level="INFO", **context):
    detail_text = ", ".join(str(value) for value in details) or "-"
    context_text = ", ".join(
        f"{key}={value}" for key, value in sorted(context.items())
    ) or "-"
    return f"[{level}] {event}; details={detail_text}; context={context_text}"


line = format_event(
    "login",
    42,
    "mobile",
    level="WARNING",
    region="eu",
    user="ada",
)
print(line)

Ожидается [WARNING] login; details=42, mobile; context=region=eu, user=ada. Сортировка контекста в примере нужна только для стабильного представления; сам kwargs является обычным словарём.

Зачем ограничивать способ вызова

Позиционный-only параметр полезен, когда его имя — внутренняя деталь API или когда позиция естественна. Keyword-only параметры хороши для флагов и нескольких однотипных настроек: connect(host, timeout=5, verify=True) читается лучше, чем ряд неочевидных чисел и булевых значений.

*args и **kwargs не делают API автоматически гибким. Если функция принимает всё, опечатка tiemout=5 может затеряться в словаре вместо немедленной ошибки. Остаточные аргументы оправданы у адаптера, декоратора или функции, которая действительно пересылает расширяемый набор, но известные настройки лучше перечислять явно.

Распаковка аргументов при вызове

Звёздочки в вызове выполняют обратную операцию: *sequence передаёт элементы как позиционные аргументы, а **mapping — пары строковый ключ/значение как именованные.

details = [42, "mobile"]
options = {"level": "WARNING", "user": "ada"}

print(format_event("login", *details, **options))

Результат использует те же правила связывания, что и явный вызов. Ключи mapping должны быть строками, а повторная передача одного именованного параметра двумя способами приводит к ошибке. Распаковка удобна на границе уже проверенных данных; она не заменяет валидацию произвольного пользовательского словаря.

Значения по умолчанию и изменяемость

Выражения значений по умолчанию вычисляются один раз при выполнении def, а не заново для каждого вызова. Поэтому параметр items=[] накапливает изменения между вызовами. Для нового списка обычно применяют items=None и создают коллекцию внутри. Это правило не связано напрямую с args, но особенно важно в длинных сигнатурах с необязательными настройками.

Как читать ошибки связывания аргументов

Сообщения got multiple values for argument, unexpected keyword argument и missing required positional argument возникают на этапе связывания. Сначала прочитайте имя функции и параметра в полном traceback. Затем посмотрите сигнатуру через inspect.signature(function) или help(function) и разверните проблемный *args либо **kwargs в repr. Например, вызов func(10, value=20) ошибочен, если первое значение уже связано с обычным параметром value. Не ловите такой TypeError внутри функции: исправьте контракт вызова.

Проектируем строгую сигнатуру request

Спроектируйте функцию request(method, url, /, *, timeout=5, retries=0): первые два значения должны быть позиционными, настройки — только именованными. Напишите три успешных вызова и по одному вызову с позиционным timeout, неизвестным ключом и пропущенным URL. Затем сделайте адаптер, принимающий **options, но разрешающий только два известных ключа перед передачей. Самостоятельная проверка успешна, если неправильные вызовы завершаются понятным TypeError или вашей явной проверкой до выполнения основной логики.

Карта параметров и распаковки вызова

Вопросы о variadic и keyword-only параметрах

Обязаны ли переменные называться args и kwargs?

Нет, значимы звёздочки. Имена можно менять, но общепринятые args и kwargs быстро сообщают читателю роль параметров.

Можно ли поставить keyword-only параметры без **kwargs?

Да. Одиночная звёздочка создаёт границу: def save(path, *, overwrite=False). Такой вариант особенно полезен, когда произвольные дополнительные ключи запрещены.

В каком порядке записываются разные параметры?

Сначала идут позиционные-only и разделитель /, затем обычные параметры, *args или одиночная *, keyword-only параметры и в конце **kwargs. Конкретная сигнатура может использовать только нужные группы.

Источники