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

Вы пишете функцию с несколькими параметрами, вызываете её — и получаете либо TypeError, либо тихую подмену не того аргумента. Или ставите def add_item(item, cart=[]), а потом обнаруживаете, что все вызовы кладут вещи в одну корзину. Аргументы в Python выглядят просто, пока не встречаешь порядок объявления, значения по умолчанию и звёздочки. Разберём приёмы, которые убирают эти грабли. Актуально для Python 3.10 и новее; ключевые правила работают начиная с 3.x в целом.
Позиционные и именованные: очевидное решение ломается на порядке
Когда параметров два-три, вы передаёте значения по порядку и не задумываетесь. Проблема появляется, когда параметров пять, половина — с одинаковым типом, и вы через месяц не помните, что идёт третьим.
def create_user(name, age, city, is_admin, notify):
print(name, age, city, is_admin, notify)
# Позиционный вызов — легко перепутать is_admin и notify
create_user("Анна", 30, "Москва", True, False)
# Именованный вызов — читается сам по себе
create_user(name="Анна", age=30, city="Москва", is_admin=True, notify=False)
Оба вызова рабочие. Но во втором вы физически не перепутаете два булевых флага местами — имя привязано к значению. Цена именованного вызова — он длиннее. Правило простое: чем больше параметров и чем однотипнее их значения, тем сильнее аргумент за именованный вызов.
Смешивать можно, но с ограничением
Позиционные аргументы обязаны идти до именованных. Иначе интерпретатор не поймёт, к чему относится значение:
# Так работает
create_user("Анна", 30, city="Москва", is_admin=True, notify=False)
# Так — SyntaxError: positional argument follows keyword argument
create_user(name="Анна", 30, "Москва", True, False)
Значения по умолчанию: где прячется самый частый баг новичка
Параметр со значением по умолчанию можно не передавать при вызове. Это удобно, но с изменяемыми1 значениями по умолчанию срабатывает ловушка, на которую попадается почти каждый.
# НЕПРАВИЛЬНО: список создаётся один раз при объявлении функции
def add_item(item, cart=[]):
cart.append(item)
return cart
print(add_item("яблоко")) # ['яблоко']
print(add_item("хлеб")) # ['яблоко', 'хлеб'] — а не ['хлеб']
Значение по умолчанию вычисляется один раз — в момент, когда Python читает def, а не при каждом вызове. Один и тот же список живёт между вызовами. Правильный приём — использовать None как маркер и создавать объект внутри:
# ПРАВИЛЬНО
def add_item(item, cart=None):
if cart is None:
cart = []
cart.append(item)
return cart
print(add_item("яблоко")) # ['яблоко']
print(add_item("хлеб")) # ['хлеб']
Если значение по умолчанию — список, словарь или множество, почти всегда правильный ответ None, а реальный объект создаётся в теле функции.
С неизменяемыми значениями (None, число, строка, кортеж) проблемы нет — их нельзя изменить на месте, подмены между вызовами не будет.
Звёздочки: *args и **kwargs
Когда заранее неизвестно, сколько аргументов придёт, используют упаковку. Имена args и kwargs — соглашение, важны именно звёздочки.
*argsсобирает лишние позиционные аргументы в кортеж;**kwargsсобирает лишние именованные аргументы в словарь.
def log_event(event, *args, **kwargs):
print("событие:", event)
print("доп. позиционные:", args) # кортеж
print("доп. именованные:", kwargs) # словарь
log_event("login", "192.168.0.1", 200, user="anna", ok=True)
# событие: login
# доп. позиционные: ('192.168.0.1', 200)
# доп. именованные: {'user': 'anna', 'ok': True}
Обратная операция — распаковка. Звёздочки при вызове разворачивают коллекцию в отдельные аргументы:
def point(x, y, z):
return x, y, z
coords = [1, 2, 3]
print(point(*coords)) # то же, что point(1, 2, 3)
values = {"x": 1, "y": 2, "z": 3}
print(point(**values)) # то же, что point(x=1, y=2, z=3)
Порядок объявления: жёсткая последовательность
В сигнатуре функции параметры идут строго в одном порядке. Нарушите — получите SyntaxError ещё до запуска.
| Позиция | Что | Пример |
|---|---|---|
| 1 | обычные позиционные | a, b |
| 2 | со значением по умолчанию | c=0 |
| 3 | сбор позиционных | *args |
| 4 | только именованные | d, e=1 |
| 5 | сбор именованных | **kwargs |
def demo(a, b, c=0, *args, d, e=1, **kwargs):
...
# d идёт после *args, поэтому передаётся ТОЛЬКО по имени
demo(1, 2, 3, 4, 5, d=10, extra="x")
Всё, что стоит после *args, становится keyword-only: такие параметры нельзя передать позиционно, только по имени. Это способ заставить вызывающего писать явно.
Голая звёздочка и слэш
Одиночная * в сигнатуре делает всё после неё keyword-only без сбора лишних позиционных. Символ / (positional-only, доступен с Python 3.8) делает всё до него передаваемым только позиционно:
def connect(host, port, /, *, timeout=30):
...
connect("localhost", 5432, timeout=10) # работает
connect(host="localhost", port=5432) # TypeError: host, port только позиционно
Новичку это редко нужно самому, но вы встретите такое в чужих сигнатурах и в стандартной библиотеке — стоит узнавать в лицо.
Когда что выбирать
- Два-три обязательных значения — обычные позиционные, ничего не усложняйте.
- Есть необязательные параметры — значение по умолчанию; для изменяемых типов только
None+ создание внутри. - Много флагов или однотипных значений — сделайте их keyword-only через
*, чтобы вызов читался. - Обёртка или декоратор, пробрасывающий чужие аргументы —
*args, **kwargs.
Что ломается чаще всего
TypeError: got multiple values for argument 'x'— вы передали значение и позиционно, и по имени одновременно.SyntaxError: non-default argument follows default argument— параметр без умолчания стоит после параметра с умолчанием.- Тихая подмена данных между вызовами — изменяемое значение по умолчанию (список, словарь).
TypeError: missing 1 required keyword-only argument— забыли передать keyword-only параметр по имени.
Что делать дальше
Разобравшись с порядком аргументов, посмотрите в сторону аннотаций типов: def create_user(name: str, age: int) -> dict делает сигнатуру самодокументируемой и ловит часть ошибок статическим анализатором вроде mypy. Следующий шаг — functools.wraps для декораторов, где как раз пригодится проброс *args, **kwargs. Раздел документации о параметрах — Reference → Function definitions; там же описаны positional-only и keyword-only тонкости.
Сочинение: Тема, тезис, аргументы, готовый текст нужного объёма
Спросить за 12 ₽Источники: Python Language Reference — Function definitions, Python Tutorial — Defining Functions
Частые вопросы
В чём разница между *args и **kwargs?
Почему нельзя ставить список значением по умолчанию?
А если у меня Python 3.7 или старше — всё это работает?
Почему я получаю TypeError: got multiple values for argument?
Как заставить вызывающего писать аргументы по имени?
Чем keyword-only отличается от обычного параметра с умолчанием?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.