Программирование PythonPython CoreРазработчик Python-библиотек

Практическая ситуация: в публичной функции часть параметров нужно запретить передавать позиционно. Каким ме...

Практическая ситуация: в публичной функции часть параметров нужно запретить передавать позиционно. Каким механизмом Python это обеспечивает?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Для этого используют параметры, доступные только по имени: в сигнатуре функции ставят отдельный символ * перед такими параметрами. Попытка передать их позиционно завершится исключением TypeError.

Исторический контекст

Механизм появился в Python 3 как средство сделать вызовы функций более явными и устойчивыми к изменениям API. Он решает проблему неоднозначных позиционных аргументов, когда вызывающему коду трудно понять назначение параметра, а добавление нового параметра может изменить смысл существующих вызовов.

Постановка проблемы

Позиционные аргументы удобны для небольшого числа очевидных параметров, но плохо подходят для настроек, флагов и редко используемых опций. Вызов с несколькими значениями одного типа может быть формально корректным, но практически непонятным и легко привести к ошибке.

Если библиотека позднее меняет порядок таких параметров, старый позиционный вызов может начать означать другое. Обязательная передача по имени делает намерение вызывающего кода явным и снижает риск несовместимых изменений.

Подробное решение

Символ * в сигнатуре отделяет позиционные параметры от keyword-only-параметров. Всё, что находится справа от него, можно передать только с указанием имени.

def connect(host, *, timeout=5, secure=True): return host, timeout, secure connect("db", timeout=10) # connect("db", 10) # TypeError

В примере host допускает позиционную передачу, а timeout и secure требуют ключевого синтаксиса. Если у keyword-only-параметра нет значения по умолчанию, он остаётся обязательным, но всё равно должен передаваться по имени.

Такой механизм проверяется Python во время связывания аргументов с параметрами, до выполнения тела функции. Это отличается от ручной проверки внутри функции: при ручной проверке некорректный вызов уже мог пройти через дополнительную логику, а сообщение об ошибке обычно будет менее точным.

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

Ситуация из практики

В библиотеке HTTP-клиент имеет параметры адреса, тайм-аута, повторных попыток и проверки сертификата. Вариант с одной длинной последовательностью позиционных параметров краток, но вызов вроде request(url, 10, 3, False) трудно безопасно прочитать.

Можно оставить все параметры позиционными, но это ухудшает читаемость и делает перестановку параметров опасной. Можно принимать произвольные **kwargs, однако тогда ошибки в именах параметров обнаруживаются только собственной валидацией, а часть информации о контракте теряется.

Выбранный вариант — оставить обязательный адрес позиционным, а настройки сделать keyword-only: request(url, timeout=10, retries=3, verify=False). Такой API явно показывает назначение аргументов, автоматически отклоняет неизвестный способ передачи и упрощает расширение сигнатуры. Цена решения — необходимость указывать имена для параметров настройки.

Что кандидаты часто упускают

  1. Является ли keyword-only-параметр обязательно необязательным?

Нет. Положение после * определяет только способ передачи, а не наличие значения по умолчанию. Параметр без значения по умолчанию обязателен, поэтому его нужно указать по имени; отсутствие такого аргумента вызовет TypeError.

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

Да. После параметра *args все следующие параметры автоматически становятся доступными только по имени. Это позволяет функции принять произвольное число позиционных аргументов и одновременно задать именованные настройки с проверяемой сигнатурой.

  1. Что произойдёт с неизвестным именованным аргументом, если в функции есть **kwargs?

Он будет собран в словарь kwargs, а не отклонён автоматически. Это удобно для проксирования или расширяемых API, но ослабляет проверку контракта: опечатка в имени может незаметно попасть в словарь. Если строгая сигнатура важнее расширяемости, **kwargs добавлять не следует или его содержимое нужно явно валидировать.