*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 или вашей явной проверкой до выполнения основной логики.
Карта параметров и распаковки вызова
- Символы
/и*задают границы позиционных-only и keyword-only параметров. args— кортеж лишних позиционных значений,kwargs— словарь оставшихся именованных.- Известные параметры следует объявлять явно, чтобы опечатки обнаруживались при связывании.
*iterableи**mappingраспаковывают данные при вызове, но не проверяют их бизнес-смысл.
Вопросы о variadic и keyword-only параметрах
Обязаны ли переменные называться args и kwargs?
Нет, значимы звёздочки. Имена можно менять, но общепринятые args и kwargs быстро сообщают читателю роль параметров.
Можно ли поставить keyword-only параметры без **kwargs?
Да. Одиночная звёздочка создаёт границу: def save(path, *, overwrite=False). Такой вариант особенно полезен, когда произвольные дополнительные ключи запрещены.
В каком порядке записываются разные параметры?
Сначала идут позиционные-only и разделитель /, затем обычные параметры, *args или одиночная *, keyword-only параметры и в конце **kwargs. Конкретная сигнатура может использовать только нужные группы.