Распаковка, *args и **kwargs в сигнатурах и вызовах
Содержание курса
Объявление *args и **kwargs в сигнатуре функции
До этого момента * и ** работали на стороне вызова — разворачивали готовые коллекции в аргументы. Теперь переходим на другую сторону: в саму сигнатуру функции.
*args: ловушка для лишних позиционных аргументов
Когда вы пишете *args в параметрах функции, Python собирает все позиционные аргументы, которые не «достались» конкретным параметрам, в один кортеж — и кладёт его в переменную args.
def summarize(label, *args):
print(f"Label: {label}")
print(f"Values: {args}")
print(f"Type of args: {type(args)}")
summarize("scores", 90, 85, 78)
# Label: scores
# Values: (90, 85, 78)
# Type of args: <class 'tuple'>
label получил первый аргумент "scores", всё остальное ушло в args. Внутри функции args — обычный кортеж, его можно итерировать, передавать в len, суммировать через sum(args). Ничего особенного — просто кортеж.
Имя args — лишь соглашение, принятое в сообществе. Синтаксис держится на звёздочке, а не на имени:
def summarize(label, *values): # работает так же
print(sum(values))
Но отходить от args без причины не стоит: читатель привык к этому имени и сразу понимает, что перед ним переменное число позиционных аргументов.
**kwargs: ловушка для лишних именованных аргументов
Аналогично, **kwargs в сигнатуре собирает все именованные аргументы, которые не совпали ни с одним явным параметром, — и кладёт их в словарь {имя: значение}.
def configure(**kwargs):
print(f"Type of kwargs: {type(kwargs)}")
for key, value in kwargs.items():
print(f" {key} = {value}")
configure(host="localhost", port=5432, debug=True)
# Type of kwargs: <class 'dict'>
# host = localhost
# port = 5432
# debug = True
Внутри функции kwargs — обычный словарь. Можно делать kwargs.get("port", 80), проверять "debug" in kwargs, передавать его дальше. Двойная звёздочка — только синтаксис объявления; дальше это просто dict.
Контракт порядка параметров
Python жёстко регламентирует, в каком порядке параметры могут идти в сигнатуре:
- Позиционные параметры (обычные, с дефолтом или без)
*args- Параметры, передаваемые только по имени (keyword-only)
**kwargs
Любое нарушение этого порядка — SyntaxError ещё до запуска программы:
def bad(**kwargs, *args): # SyntaxError
pass
Важное следствие: параметры, объявленные после *args, автоматически становятся keyword-only. Передать их позиционно уже невозможно — все позиционные аргументы к тому моменту уже поглощены *args.
def process(*args, verbose):
if verbose:
print(args)
process(1, 2, 3, verbose=True) # OK
process(1, 2, 3, True) # TypeError: process() takes 0 positional arguments...
Во втором вызове True уходит в args, и параметр verbose остаётся незаполненным — Python не знает, что вы имели в виду именно его.
Если keyword-only параметру нужен дефолт, он объявляется прямо в сигнатуре:
def process(*args, verbose=False):
if verbose:
print(args)
Тогда process(1, 2, 3) работает без ошибки — verbose берёт значение по умолчанию.
Этот порядок — не произвол. Python нужно однозначно разграничить, какой аргумент куда идёт. Если **kwargs стоял бы перед *args, интерпретатор не смог бы понять, где заканчиваются именованные аргументы и начинаются позиционные.
