Вы уже писали функции с обычными параметрами: def area(width, height):. Такая функция требует ровно два аргумента, не больше и не меньше. А что делать, если заранее не известно, сколько значений придёт — одно, три или десять? Добавлять параметры про запас неудобно и ненадёжно: рано или поздно кто-то передаст ещё одно значение, и функция сломается.
Python решает эту задачу двумя специальными обозначениями в заголовке функции: *args и **kwargs. С ними функция принимает сколько угодно позиционных и сколько угодно именованных аргументов. Так устроена, например, встроенная функция print() — ей можно передать один аргумент, а можно десять.
Разберём, что происходит внутри функции с такими аргументами, в каком порядке их можно комбинировать и как передавать их дальше, в другую функцию.
*args — произвольное число позиционных аргументов
Представьте почтовый ящик без ограничения по размеру: сколько бы писем вы ни отправили, все они туда попадут, а хозяин потом разберёт их по одному. Звёздочка перед именем параметра в заголовке функции работает похоже — она говорит Python: «все позиционные аргументы, которые не достались обычным параметрам, сложи в один ящик и отдай сюда».
Официально это называется упаковкой аргументов (packing). Параметр с одной звёздочкой в заголовке функции принято называть *args — это не ключевое слово, а просто договорённость программистов на Python (от английского «arguments»). Можно было бы написать *numbers или *items, и код бы работал точно так же, но чужой код будет читать сложнее: все привыкли именно к args.
def greet(*args):
print(type(args))
print(args)
greet("Привет")
greet("Привет", "Алиса")
greet("Привет", "Алиса", "!")
Разберём построчно, что здесь происходит:
def greet(*args):— функция принимает любое число позиционных аргументов; внутри функции все они будут собраны в один параметрargs.print(type(args))— выведет<class 'tuple'>. Каким бы способом ни вызвалиgreet, внутриargsвсегда будет кортеж.greet("Привет")— передан один аргумент, поэтомуargsстанет кортежем из одного элемента:('Привет',).greet("Привет", "Алиса")— передано два аргумента,argsбудет равен('Привет', 'Алиса').greet("Привет", "Алиса", "!")— три аргумента — три элемента в кортеже:('Привет', 'Алиса', '!').
for, обращение по индексу, функция len().Раз внутри args лежит кортеж, с ним можно работать как с любым другим кортежем — например, посчитать сумму чисел в цикле:
def total(*args):
result = 0
for num in args:
result += num
return result
print(total(1, 2, 3))
print(total(10, 20, 30, 40))
print(total())
По шагам:
result = 0— переменная-накопитель, с неё начинаем считать сумму (так же, как делали в циклах раньше).for num in args:— перебираем кортежargsточно так же, как перебирали бы список чисел.result += num— на каждом шаге прибавляем текущее число к накопителю.total(1, 2, 3)выведет6,total(10, 20, 30, 40)—100.total()выведет0: аргументов не передали,argsстал пустым кортежем(), цикл ни разу не выполнился, и вернулось начальное значение накопителя.
**kwargs — произвольное число именованных аргументов
Теперь представьте анкету, где каждое поле подписано: «имя», «возраст», «город». Заранее не известно, сколько полей заполнит конкретный человек, но каждое значение подписано, поэтому перепутать их невозможно. Именно так работает **kwargs: две звёздочки перед именем параметра говорят Python собрать все именованные аргументы (вида ключ=значение), которые не достались обычным параметрам, в один словарь.
Имя kwargs — сокращение от «keyword arguments» (именованные аргументы), тоже просто общепринятое соглашение, а не требование языка.
def show_info(**kwargs):
print(type(kwargs))
print(kwargs)
show_info(name="Алиса", age=25)
show_info(city="Москва", country="Россия")
Что здесь происходит:
def show_info(**kwargs):— функция принимает любое число именованных аргументов; все они соберутся в параметрkwargs.print(type(kwargs))— выведет<class 'dict'>: внутри функцииkwargs— обычный словарь.show_info(name="Алиса", age=25)— вызов с двумя именованными аргументами. Внутри функцииkwargsстанет равен{'name': 'Алиса', 'age': 25}: ключ — это имя аргумента слева от знака=, значение — то, что справа.show_info(city="Москва", country="Россия")— здесьkwargsбудет{'city': 'Москва', 'country': 'Россия'}.
*args собирает позиционные аргументы в кортеж, **kwargs — именованные в словарь.Раз kwargs — обычный словарь, переберём его так же, как в уроке про словари — через .items():
def print_profile(**kwargs):
for key, value in kwargs.items():
print(f" {key}: {value}")
print_profile(name="Алиса", age=25, city="Москва")
for key, value in kwargs.items():—.items()отдаёт пары ключ-значение, аforсразу раскладывает каждую пару в две переменные:keyиvalue.print(f" {key}: {value}")— для каждой пары печатает строку видаname: Алисас отступом в начале.- Вызов выведет три строки, в том порядке, в каком аргументы передали при вызове:
name: Алиса,age: 25,city: Москва.
Строгий порядок параметров в заголовке функции
У Python есть чёткое правило, в каком порядке разные виды параметров могут стоять в заголовке функции. Нарушите порядок — получите SyntaxError ещё до запуска программы, интерпретатор даже не станет пытаться выполнить код. Порядок такой:
def order(a, b, *args, sep=", ", **kwargs):
pass
order(1, 2, 3, 4, 5, sep=" | ", color="red")
# a=1, b=2, args=(3, 4, 5), sep=" | ", kwargs={'color': 'red'}
Как аргументы разошлись по параметрам:
aиb— обычные параметры, забирают первые два позиционных значения:1и2.*argsзабирает всё, что осталось из позиционных аргументов:3,4,5— получится кортеж(3, 4, 5).sep=", "— параметр после*args. Такой параметр называют именованным-только (keyword-only): передать в него значение можно только по имени, не по позиции.**kwargsзаберёт все оставшиеся именованные аргументы, которых нет среди обычных параметров:color="red"попадёт вkwargsкак{'color': 'red'}.
order(1, 2, " | ") не задаст sep — строка " | " просто окажется третьим элементом args. Единственный способ — написать sep=" | " явно.А вот так писать нельзя — порядок нарушен, и код даже не запустится:
def bad(a, **kwargs, *args):
pass
# SyntaxError: параметр после **kwargs недопустим
Здесь **kwargs стоит раньше *args. Python требует, чтобы **kwargs всегда был последним параметром в заголовке — ни один параметр не может идти после него.
Когда *args и **kwargs работают вместе
Часто оба обозначения используют в одной функции сразу — тогда она принимает вообще что угодно: любые позиционные аргументы, любые именованные, в любом количестве.
def universal(*args, **kwargs):
print("Позиционные:", args)
print("Именованные:", kwargs)
universal(1, 2, name="Алиса", role="admin")
universal("hello")
universal()
Что выведет каждый вызов:
universal(1, 2, name="Алиса", role="admin"):argsзаберёт позиционные1и2в кортеж(1, 2),kwargsзаберёт именованные в словарь{'name': 'Алиса', 'role': 'admin'}.universal("hello"): один позиционный аргумент, поэтомуargsравен('hello',), а именованных не было —kwargsстанет пустым словарём{}.universal(): вообще без аргументов — оба соберутся пустыми:()и{}.
В сигнатуре можно смешать обычные параметры, *args и **kwargs одновременно — так часто пишут функции-логгеры:
def log(level, **meta):
details = ", ".join(f"{k}={v}" for k, v in meta.items())
print(f"[{level}]", details)
log("INFO", user="Алиса", action="login")
", ".join(f"{k}={v}" for k, v in meta.items())— для каждой пары ключ-значение изmetaсобирается кусочек видаkey=value, а", ".join(...)склеивает все кусочки через запятую с пробелом.log("INFO", user="Алиса", action="login"):levelполучает"INFO", а два именованных аргумента уходят вmeta. Вывод:[INFO] user=Алиса, action=login.
Распаковка при вызове: те же звёздочки, обратная операция
Всё, что было выше — это звёздочки в заголовке функции. Но те же символы можно поставить и при вызове функции, перед готовым списком или словарём — тогда они сделают противоположное: не соберут аргументы, а разберут коллекцию обратно на отдельные значения. Официально это называется распаковкой (unpacking) аргументов.
def describe(name, age, city):
print(f"{name}, {age} лет, {city}")
data = ["Алиса", 25, "Москва"]
describe(*data)
По шагам:
data = ["Алиса", 25, "Москва"]— обычный список из трёх значений.describe(*data)— звёздочка передdataв вызове говорит Python: разложи список на отдельные аргументы. Это то же самое, что написатьdescribe("Алиса", 25, "Москва")вручную.- Функция получит три отдельных значения в параметры
name,age,cityи выведетАлиса, 25 лет, Москва.
Так же работает двойная звёздочка со словарём — она распаковывает пары ключ-значение в именованные аргументы:
person = {"name": "Алиса", "age": 25, "city": "Москва"}
describe(**person)
**person разбирает словарь на именованные аргументы: ключи становятся именами параметров, значения — их значениями. Ключи словаря должны совпадать с именами параметров функции — иначе Python не поймёт, куда положить значение, и выбросит ошибку.
* в заголовке функции (def f(*args)) — собирает аргументы в кортеж. * при вызове (f(*data)) — распаковывает коллекцию обратно в отдельные аргументы. Символ один, но смотрите, где именно он стоит: в def или в скобках вызова.Собрать и распаковать можно и в одной программе — это и есть паттерн «передача насквозь»: функция принимает произвольные аргументы через *args/**kwargs, а затем распаковывает их и передаёт дальше, в другую функцию:
def send_email(to, subject):
print(f"Кому: {to}")
print(f"Тема: {subject}")
def send_with_log(*args, **kwargs):
print("Отправляем письмо...")
send_email(*args, **kwargs)
print("Готово")
send_with_log("alice@mail.ru", subject="Привет")
Что здесь происходит:
send_with_log("alice@mail.ru", subject="Привет")— обёртка соберёт позиционный аргумент вargs = ("alice@mail.ru",), а именованный — вkwargs = {"subject": "Привет"}.send_email(*args, **kwargs)— собранныеargsиkwargsтут же распаковываются обратно в отдельные аргументы дляsend_email. Получается тот же вызов, что иsend_email("alice@mail.ru", subject="Привет").- Главная выгода:
send_with_logне знает, какие параметры есть уsend_email. Она добавляет своё поведение и передаёт всё, что получила, дальше без изменений.
Частые ошибки
Параметр после **kwargs
def f(**kwargs, a): — SyntaxError. **kwargs всегда должен быть последним параметром в заголовке функции, ничего не может идти после него.
Попытка обратиться к kwargs по номеру
kwargs[0] внутри функции вызовет KeyError: kwargs — словарь, а не список, у него нет позиции 0. Обращайтесь по ключу: kwargs["name"], или перебирайте через .items().
Именованный-только параметр передан по позиции
Если в заголовке def f(*args, sep=", "):, вызов f(1, 2, " | ") не задаст sep — строка " | " просто станет третьим элементом args. Нужно писать f(1, 2, sep=" | ").
Путаница между сбором и распаковкой
Одна и та же звёздочка в заголовке собирает аргументы, а при вызове — распаковывает коллекцию. Если забыть об этом, легко не понять, почему func(*my_list) передаёт несколько отдельных аргументов, а не один список.
Что важно запомнить
- *args в заголовке функции собирает все лишние позиционные аргументы в кортеж.
- **kwargs в заголовке функции собирает все лишние именованные аргументы в словарь.
- Порядок параметров в заголовке строгий: обычные параметры → *args → именованные-только параметры → **kwargs. Нарушение —
SyntaxError. - Имена
argsиkwargs— это соглашение, а не требование языка, но лучше его придерживаться, чтобы код было легко читать. - Те же звёздочки при вызове функции работают наоборот — распаковывают готовый список или словарь в отдельные аргументы:
f(*lst),f(**dct). - Сбор и распаковка вместе дают паттерн «передача насквозь»:
func(*args, **kwargs)— основа для функций-обёрток, которые ничего не знают о параметрах функции внутри.
Проверьте себя
6 вопросов
Сумма через *args
Напишите функцию sum_all(*args), которая принимает любое количество чисел и возвращает их сумму.
Если аргументов нет — верните 0. Например, sum_all(1, 2, 3) должна вернуть 6.