Программирование

Что такое *args и **kwargs в Python: объяснение с примерами

*args и **kwargs в Python: как одинарная * собирает дополнительные позиционные в tuple, а двойная ** — именованные в dict. Примеры, порядок параметров и ошибки.

Что означают *args и **kwargs в определениях функций Python?

В Python, какова цель синтаксиса * (одной звездочки) и ** (двойной звездочки) при использовании в параметрах функций? Объясните их поведение на этих примерах:

python
def foo(x, y, *args):
 pass

def bar(x, y, **kwargs):
 pass

Для информации о том, как эти синтаксисы работают в вызовах функций, см.: Что означают ** (двойная звездочка/звездочка) и * (звездочка) в вызове функции?

В Python *args и **kwargs дают функции гибкий интерфейс: *args собирает лишние позиционные аргументы в кортеж, а **kwargs — именованные аргументы в словарь, что удобно для вариативных вызовов и обёрток. В сигнатуре def foo(x, y, *args): все дополнительные позиционные попадут в args как tuple, а в def bar(x, y, **kwargs): дополнительные ключ=значение соберутся в kwargs как dict. Для распаковки при вызове используются те же символы: * для итерируемых и ** для отображений.


Содержание


Что такое *args и **kwargs в Python?

Коротко: звёздочки в определении функции говорят интерпретатору “собери оставшиеся аргументы”. Одинарная звёздочка (*args) упаковывает дополнительные позиционные аргументы в кортеж (tuple). Двойная звёздочка (**kwargs) упаковывает дополнительные именованные аргументы в словарь (dict). Это стандартная соглашение: переменные обычно называются args и kwargs, но имена могут быть любыми — главное звёздочки.

Примеры:

python
def foo(x, y, *args):
 print("x:", x)
 print("y:", y)
 print("args:", args) # tuple

foo(1, 2) # args = ()
foo(1, 2, 3, 4) # args = (3, 4)
python
def bar(x, y, **kwargs):
 print("x:", x)
 print("y:", y)
 print("kwargs:", kwargs) # dict

bar(1, 2) # kwargs = {}
bar(1, 2, a=3, b=4) # kwargs = {'a': 3, 'b': 4}

Если хотите подробное объяснение и дополнительные примеры сигнатур — полезно руководство по функциям на русском языке, например docs-python.ru.


Как * и ** работают при вызове функции (распаковка)

Звёздочки применимы не только в определениях — их также используют при вызове для распаковки коллекций в аргументы.

  • *iterable распаковывает последовательность в позиционные аргументы.
  • **mapping распаковывает словарь в именованные аргументы (ключи должны быть строками, соответствующими именам параметров).

Примеры:

python
def f(a, b, c):
 print(a, b, c)

lst = [1, 2, 3]
d = {'a': 1, 'b': 2, 'c': 3}

f(*lst) # то же, что f(1, 2, 3)
f(**d) # то же, что f(a=1, b=2, c=3)

Можно смешивать:

python
f(1, *[2], c=3) # a=1, b=2, c=3

Ошибки при распаковке — частая боль: если аргумент передан и позиционно, и по имени, Python выбросит TypeError: “got multiple values for argument”. Пример:

python
def g(x, y):
 pass

g(1, x=2) # TypeError: g() got multiple values for argument 'x'

Про распаковку подробнее см. статью о упаковке/распаковке параметров, например на Metanit.


Поведение примеров: def foo(x, y, *args) и def bar(x, y, **kwargs)

Разберём ваши конкретные определения и типичные вызовы.

  1. def foo(x, y, *args)
  • x и y — обязательные первые два аргумента (передаются позиционно или по имени).
  • Всё, что идёт после них без имени, попадёт в args как кортеж.

Пример использования:

python
def foo(x, y, *args):
 print(x, y, args)

foo(10, 20) # -> 10 20 ()
foo(10, 20, 30) # -> 10 20 (30,)
foo(10, 20, 30, 40) # -> 10 20 (30, 40)
  1. def bar(x, y, **kwargs)
  • x и y — обязательные позиции/имена.
  • Любые дополнительные именованные пары будут в kwargs как словарь.
python
def bar(x, y, **kwargs):
 print(x, y, kwargs)

bar(1, 2) # -> 1 2 {}
bar(1, 2, color='red') # -> 1 2 {'color': 'red'}
bar(1, 2, color='red', z=5) # -> 1 2 {'color': 'red', 'z': 5}

Комбинация в одной функции:

python
def example(a, b, *args, c=0, **kwargs):
 print(a, b, args, c, kwargs)

example(1, 2, 3, 4, c=9, d=10)
# -> a=1, b=2, args=(3,4), c=9, kwargs={'d':10}

Ещё одно практическое применение — “форвардинг” аргументов в обёртках/декораторах:

python
def wrapper(*args, **kwargs):
 print("before")
 result = func(*args, **kwargs)
 print("after")
 return result

Такой паттерн широко встречается в коде и позволяет не знать заранее сигнатуру обёртываемой функции (см. примеры на Tproger).


Порядок и нюансы сигнатур

Сигнатура функции в Python имеет чёткий порядок параметров. Упрощённо:

  1. Позиционные-only (с разделителем / в Python 3.8+; встречается редко).
  2. Позиционные или именованные (обычные параметры).
  3. *args (var-positional) — собирает оставшиеся позиционные.
  4. Параметры только по ключу (keyword-only) — те, что идут после * или после *args.
  5. **kwargs (var-keyword) — собирает все оставшиеся именованные.

Примеры явного разделения:

python
def f(a, b, /, c, *args, d, **kwargs):
 ...

Если в сигнатуре стоит просто * (без имени), то всё, что после него — доступно только как keyword-only:

python
def f(a, b, *, c):
 # c можно передать только как c=...
 ...

Ещё нюансы:

  • Имена args и kwargs — соглашение, а не требование. Можно писать *rest, **options.
  • *args собирает в tuple (неизменяемый), **kwargs — в dict (изменяемый).
  • Если вы ожидаете конкретные ключи в kwargs, лучше извлекать их (kwargs.pop('name', default)) и проверять оставшиеся ключи, чтобы обнаружить опечатки в именах параметров.

О часто используемом паттерне в классах: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) — это удобный способ перенаправить аргументы базовому классу (пример обсуждается на Stack Overflow на русском).


Практические советы и распространённые ошибки

  • Не используйте *args/**kwargs, чтобы скрыть плохо продуманную API. Лучше явно перечислить параметры, если они известны.
  • Когда пишете обёртку, применяйте functools.wraps, чтобы не потерять метаданные функции.
  • Проверяйте и извлекайте нужные ключи из kwargs: value = kwargs.pop('name', default). Если остались неожиданные ключи — можно бросить исключение.
  • Будьте осторожны с дубликатами: f(1, x=1) для функции с параметром x приведёт к ошибке.
  • Для строгой типизации при форвардинге аргументов смотрите ParamSpec (typing) — если используете аннотации.
  • Маленькая оптимизация: если функция часто вызывается и аргументы фиксированы — явные параметры быстрее и читаемее; *args/**kwargs не сильно влияют, но добавляют небольшую обёртку.

Небольшой пример проверки неожиданных ключей:

python
def configure(**kwargs):
 allowed = {'host', 'port', 'timeout'}
 extra = set(kwargs) - allowed
 if extra:
 raise TypeError(f"Unexpected keys: {extra}")
 # дальше использовать kwargs['host'] и т.д.

Источники


Заключение

Коротко: *args собирает дополнительные позиционные аргументы в tuple, **kwargs — дополнительные именованные аргументы в dict; при вызове * и ** — распаковывают итерируемые и отображения в позиционные и именованные аргументы соответственно. Используйте args kwargs для гибкости (форвардинг, обёртки, вариативные API), но не заменяйте ими явную, понятную сигнатуру без необходимости.

Авторы
Проверено модерацией
Модерация
Что такое *args и **kwargs в Python: объяснение с примерами