Что такое *args и **kwargs в Python: объяснение с примерами
*args и **kwargs в Python: как одинарная * собирает дополнительные позиционные в tuple, а двойная ** — именованные в dict. Примеры, порядок параметров и ошибки.
Что означают *args и **kwargs в определениях функций 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?
- Как * и ** работают при вызове (распаковка)
- Поведение примеров: def foo(x, y, *args) и def bar(x, y, **kwargs)
- Порядок и нюансы сигнатур (позиционные, *args, ключевые-only, **kwargs)
- Практические советы и распространённые ошибки
- Источники
- Заключение
Что такое *args и **kwargs в Python?
Коротко: звёздочки в определении функции говорят интерпретатору “собери оставшиеся аргументы”. Одинарная звёздочка (*args) упаковывает дополнительные позиционные аргументы в кортеж (tuple). Двойная звёздочка (**kwargs) упаковывает дополнительные именованные аргументы в словарь (dict). Это стандартная соглашение: переменные обычно называются args и kwargs, но имена могут быть любыми — главное звёздочки.
Примеры:
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)
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распаковывает словарь в именованные аргументы (ключи должны быть строками, соответствующими именам параметров).
Примеры:
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)
Можно смешивать:
f(1, *[2], c=3) # a=1, b=2, c=3
Ошибки при распаковке — частая боль: если аргумент передан и позиционно, и по имени, Python выбросит TypeError: “got multiple values for argument”. Пример:
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)
Разберём ваши конкретные определения и типичные вызовы.
- def foo(x, y, *args)
- x и y — обязательные первые два аргумента (передаются позиционно или по имени).
- Всё, что идёт после них без имени, попадёт в args как кортеж.
Пример использования:
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)
- def bar(x, y, **kwargs)
- x и y — обязательные позиции/имена.
- Любые дополнительные именованные пары будут в kwargs как словарь.
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}
Комбинация в одной функции:
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}
Ещё одно практическое применение — “форвардинг” аргументов в обёртках/декораторах:
def wrapper(*args, **kwargs):
print("before")
result = func(*args, **kwargs)
print("after")
return result
Такой паттерн широко встречается в коде и позволяет не знать заранее сигнатуру обёртываемой функции (см. примеры на Tproger).
Порядок и нюансы сигнатур
Сигнатура функции в Python имеет чёткий порядок параметров. Упрощённо:
- Позиционные-only (с разделителем
/в Python 3.8+; встречается редко). - Позиционные или именованные (обычные параметры).
- *args (var-positional) — собирает оставшиеся позиционные.
- Параметры только по ключу (keyword-only) — те, что идут после
*или после*args. - **kwargs (var-keyword) — собирает все оставшиеся именованные.
Примеры явного разделения:
def f(a, b, /, c, *args, d, **kwargs):
...
Если в сигнатуре стоит просто * (без имени), то всё, что после него — доступно только как keyword-only:
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 не сильно влияют, но добавляют небольшую обёртку.
Небольшой пример проверки неожиданных ключей:
def configure(**kwargs):
allowed = {'host', 'port', 'timeout'}
extra = set(kwargs) - allowed
if extra:
raise TypeError(f"Unexpected keys: {extra}")
# дальше использовать kwargs['host'] и т.д.
Источники
- Что такое args и kwargs в Python? — Habr
- Переменные аргументов args и kwargs — docs-python.ru
- Функция с переменным количеством аргументов в Python: args и kwargs — Tproger
- Упаковка и распаковка в параметрах функций — Metanit
- Что такое args и kwargs? — Pythonist.ru
- Вопросы и ответы по args/kwargs — Stack Overflow на русском
Заключение
Коротко: *args собирает дополнительные позиционные аргументы в tuple, **kwargs — дополнительные именованные аргументы в dict; при вызове * и ** — распаковывают итерируемые и отображения в позиционные и именованные аргументы соответственно. Используйте args kwargs для гибкости (форвардинг, обёртки, вариативные API), но не заменяйте ими явную, понятную сигнатуру без необходимости.