Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 10 из 20 Начальный 15 мин 100 XP

*args в Python: сколько аргументов угодно

Функция, которой нельзя заранее сказать, сколько значений придет: два числа, пять или ни одного. Разбираем звёздочку: упаковку в кортеж и распаковку при вызове.

Редакция Питоники

До сих пор число аргументов у наших функций было вшито в объявление: функция про аргументы требовала ровно столько значений, сколько параметров объявлено. Но жизнь вносит поправки: суммировать нужно два числа, потом пять, потом вообще ни одного. Писать под каждый случай отдельную функцию — та же копипаста, от которой мы когда-то ушли. Нужна функция, которая примет сколько угодно значений. Для этого у Python есть звёздочка.

Что такое *args в Python: коротко

Звёздочка в объявлении функции собирает все позиционные аргументы вызова в один кортеж: def total(*nums). Функция становится безразличной к числу аргументов — их может быть два, пять или ни одного. Внутри args — обычный кортеж, который перебирают циклом или отдают sum. Знак звёздочки при вызове делает обратное: f(*data) распаковывает список в отдельные аргументы.

Проблема: сколько значений придёт

функция с вшитым числом аргументов
def sum2(a, b):
    return a + b
Фрагмент: sum2 складывает ровно два числа. Для трёх слагаемых понадобится sum3, для четырёх — sum4: число аргументов вшито в объявление, и под каждый случай нужна новая функция.

*args: упаковка в кортеж

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

звёздочка упаковывает вызов
def show(*args):
    print(args)
    print(type(args))

show(10, 20, 30)
Вывод
(10, 20, 30)
<class 'tuple'>

Три значения, переданные через запятую, превратились в один кортеж — набор из трёх чисел в круглых скобках. Звёздочка в объявлении функции собирает все позиционные аргументы вызова в один кортеж. Имя args — соглашение, а не требование языка: *nums или *values работают так же, хотя имя args встречается в коде чаще всего. Главное свойство кортежа — с ним внутри функции можно делать всё то же, что и с обычными данными: перебирать циклом, считать сумму, проверять длину.

Сумма любого числа аргументов

Классика применения — суммирование. Аккумулятор из курса циклов перебирает кортеж, и функция становится безразличной к числу аргументов:

сумма любого числа значений
def total(*nums):
    acc = 0
    for n in nums:
        acc += n
    return acc

print(total(1, 2, 3))
print(total(10, 20, 30, 40))
print(total())
Вывод
6
100
0

Три вызова — три разных числа аргументов, и все сработали. Даже пустой вызов легален: кортеж окажется пустым, цикл не выполнится ни разу, и вернётся стартовое значение аккумулятора — ноль. Цикл-аккумулятор можно заменить встроенной функцией sum(nums) из урока про встроенные функции — тела функции станет одна строка. А вот пример с другим правилом: максимум из любого числа значений — по каркасу максимума из курса циклов:

максимум любого числа значений
def biggest(*nums):
    return max(nums)

print(biggest(3, 9, 4))
print(biggest(17))
Вывод
9
17

Обычные аргументы и *args вместе

Звёздочка не отменяет обычные параметры — они просто идут первыми. Всё, что встанет в вызове до положенного числа обычных аргументов, попадёт в них, а всё остальное — в кортеж:

заголовок и значения
def report(title, *values):
    print('Отчёт:', title)
    for v in values:
        print(' -', v)

report('Итоги дня', 5, 3, 8)
Вывод
Отчёт: Итоги дня
 - 5
 - 3
 - 8

Первый аргумент вызова встал в параметр title, остальные три упаковались в кортеж values. Отсюда жёсткое правило: после параметра со звёздочкой обычных позиционных параметров быть не может — Python уже не сможет понять, где кончается кортеж и начинается следующий обычный аргумент. Параметры после звёздочки можно передавать только по имени:

после звёздочки — только по имени
def report(*lines, footer):
    for line in lines:
        print(line)
    print(footer)

report('Начало', 'Конец', footer='готово')
report('Начало', 'Конец', 'готово')
Первый вызов напечатает обе строки и «готово». Второй упадёт с TypeError: report() missing 1 required keyword-only argument — параметр footer объявлен после звёздочки, его передают только именем: footer='готово'.

Распаковка: звёздочка при вызове

Звёздочка умеет и обратную операцию. В объявлении она собирает аргументы, а при вызове — раскладывает список на отдельные аргументы. Разница видна на одном примере:

список целиком против распаковки
def show(*args):
    print(args)

data = [10, 20, 30]
show(data)
show(*data)
Вывод
([10, 20, 30],)
(10, 20, 30)

Вызов show(data) передал один аргумент — список целиком, и кортеж получился из одного элемента, внутри которого лежит список. Вызов show(*data) распаковал список: три отдельных числа вошли в кортеж на равных. Упаковка в объявлении и распаковка при вызове — зеркальные операции одной звёздочки. Распаковывать можно и кортежи, и любые перебираемые наборы. Соберём итоговый пример: приветствие любому числу гостей — заголовок обязателен, имена — сколько пришлют:

приветствие любому числу гостей
def greet_all(greeting, *names):
    for name in names:
        print(f'{greeting}, {name}!')

greet_all('Привет', 'Анна', 'Борис')
greet_all('Пока', 'Вера')
Вывод
Привет, Анна!
Привет, Борис!
Пока, Вера!
Обычный параметрПараметр со звёздочкой
Принимает ровно один аргументПринимает любое число аргументов — от нуля
Аргумент приходит как естьАргументы собираются в кортеж
Обязателен, если нет умолчанияВызов без него легален
До звёздочки — позиционные и именованныеПосле звёздочки — только по имени

Позиционные значения научились собирать в кучу. Но бывают и именованные аргументы, которых заранее не знаешь: цвет, размер, режим — какие именуемые настройки пришлют в следующий раз? Для них у звёздочки есть сестра с двумя знаками — о ней следующий урок.

args — это обычный кортеж

Первое, что стоит понять: внутри функции args — не какая-то особая сущность, а обычный кортеж. С ним можно делать всё, что с кортежом: мерить длину, брать элемент по индексу, резать, распаковывать. Превращать его в список заранее не нужно — это лишняя работа.

всё, что умеет кортеж
def show(*args):
    print(len(args))
    print(args[0])
    print(args[-1])
    print(args[:2])

show(10, 20, 30, 40)
Вывод
4
10
40
(10, 20)

Четыре аргумента — длина четыре, первый элемент десять, последний сорок, срез из двух — пара (10, 20). Так что если внутри функции нужен список, который можно менять, — это отдельная история: кортеж неизменяем, и про это отдельный pitfall ниже.

Звёздочка в присваивании

У звёздочки есть ещё одно применение, о котором забывают: в левой части присваивания она забирает «всё остальное» в список. Механизм тот же, только направление другое — в *args она собирает, здесь раскидывает.

первый элемент и остальные
first, *rest = [10, 20, 30, 40]

print(first)
print(rest)

head, *middle, tail = [1, 2, 3, 4, 5]
print(head, middle, tail)
Вывод
10
[20, 30, 40]
1 [2, 3, 4] 5

В первой строке first забрал первый элемент, а rest — все остальные, и собрались они в список, а не в кортеж. Во второй строке звёздочка стоит посередине: всё, что между head и tail, уходит в middle. С таким приёмом удобно работать с результатом, где важен первый элемент, а с остальными — просто вместе: например, отделить заголовок файла от данных.

Когда *args лишний

Звёздочка — не универсальный ответ на вопрос «а как передать список». Если данные всегда приходят одним списком, честнее объявить параметр-список: вызов останется коротким, а в отладке будет видно, где данные приходят.

СитуацияКак объявитьПочему так лучше
Данные всегда приходят спискомОдин параметр-списокВызов не размазывается по строкам
Значения однотипные и их многоПараметр-список и циклПроще читать и проверять
Значения передаются поштучно, число неизвестно*argsВызов остаётся коротким: total(1, 2, 3)
Набор нужно передать дальше*args и f(*args)Данные не теряются при пересылке

Мы держимся правила: набор — значит параметр-список, а *args оставляем для вызовов, где значения идут поштучно. Смешивать оба подхода в одной функции не стоит: через месяц никто не вспомнит, почему в одном месте значения передаются через запятую, а в другом — одним списком.

Практика: отчёт с произвольным числом строк

Соберём вместе всё, что разобрали: заголовок обязателен, строки — сколько пришлют, а в конце подпись передаётся только по имени.

отчёт с любым числом строк
def report(title, *lines, footer='конец отчёта'):
    print('===', title, '===')
    for line in lines:
        print(' -', line)
    print('---', footer)

report('Продажи за день', 'хлеб — 45', 'молоко — 80')
report('Склад', 'хлеб — 12', 'молоко — 3', footer='данные на 18:00')
Вывод
=== Продажи за день ===
 - хлеб — 45
 - молоко — 80
--- конец отчёта
=== Склад ===
 - хлеб — 12
 - молоко — 3
--- данные на 18:00

Два вызова — разное число строк и разная подпись, а тело функции одно. Именно так выглядит код, который перестаёт размножаться: вместо report2, report3 и report7 у вас одна функция, которая принимает столько данных, сколько нужно.

Всё вместе: обычные, *args и именованные

Звёздочки можно комбинировать, и самая полная форма сигнатуры выглядит так: def report(title, *values, footer='конец', **options). Слева направо: обязательный позиционный параметр, потом произвольное число позиционных, потом параметры только для именованной передачи, потом произвольное число именованных.

полная форма сигнатуры
def report(title, *values, footer='конец', **options):
    print('===', title, '===')
    print(' значения:', values)
    print(' подпись:', footer)
    print(' опции:', options)

report('Продажи', 5, 3, footer='итог', unit='руб', city='Казань')
Вывод
=== Продажи ===
 значения: (5, 3)
 подпись: итог
 опции: {'unit': 'руб', 'city': 'Казань'}

Позиционные значения уехали в кортеж values, параметр после звёздочки — в footer, а всё, что не нашло своего места, — в словарь options. Правило разбора простое: сначала обычные позиционные, потом *args забирает остаток позиционных, потом параметры после звёздочки принимают только имена, а **kwargs забирает все оставшиеся именованные.

Форма в сигнатуреЧто собираетЧем достать
обычный параметрровно одно значениесамо значение
*argsвсе остальные позиционныекортеж args
параметр после *argsтолько именованную передачупо имени: f(x, footer='готово')
**kwargsвсе остальные именованныесловарь kwargs
Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

def total(*nums):
    return sum(nums)

print(total(1, 2))
print(total(5, 5, 5, 5))
def demo(a, *rest):
    print(a, rest)

demo(1, 2, 3)
def show(*args):
    print(args)

values = [4, 9]
show(values)
show(*values)
Проверь себя
0 / 9

1. Что такое args внутри функции def f(*args)?

2. Функция total(*nums) считает сумму аккумулятором. Что вернёт вызов total() без аргументов?

3. Сколько обычных позиционных параметров можно объявить после *args?

4. Что делает звёздочка в вызове f(*prices), где prices — список?

5. Что произойдёт при вызове args.append(10) внутри функции со звёздочкой?

6. Чем отличается вызов show(data) от show(*data) у функции def show(*args)?

7. Что напечатает print(args[0]) внутри функции f(*args), вызванной как f(1, 2, 3)?

8. Что получится после выполнения first, *rest = [1, 2, 3]?

9. Когда вместо *args лучше объявить обычный параметр-список?

Карточки терминов
Запомнено: 0 / 8
Практика

Напиши функцию average(*numbers), которая возвращает среднее арифметическое любого числа аргументов (сумма, делённая на количество). Вызови её с тремя числами 2, 4, 6 и с пятью числами 1, 2, 3, 4, 5 — оба результата напечатай.

practice.py
Вопросы и ответы по уроку

Как принять в функцию переменное число аргументов в Python?

Объявить параметр со звёздочкой: def total(*nums). Все позиционные аргументы вызова соберутся в кортеж nums, который перебирают циклом или отдают встроенной функции sum(nums). Вызов без аргументов тоже легален — кортеж будет пустым.

Урок 12: встроенные функции Python

Чем звёздочка в объявлении отличается от звёздочки при вызове?

В объявлении def f(*args) звёздочка упаковывает: все аргументы вызова собираются в один кортеж. При вызове f(*data) звёздочка распаковывает: элементы списка становятся отдельными аргументами. Одно знание работает в обе стороны — это зеркальные операции.

Можно ли передать список в функцию с *args?

Да, двумя способами. f(список) передаст его как один цельный аргумент — он ляжет в кортеж единственным элементом. f(*список) распакует список, и каждый элемент станет отдельным аргументом. Второе обычно и нужно: например, f(*prices) для функции, складывающей цены.

Что за параметры, которые после *args можно передать только по имени?

Такие параметры называют keyword-only: они объявлены после звёздочки, и позиционно их не передать — только именем, как f(1, 2, footer='готово'). Иначе Python отвечает TypeError о пропущенном keyword-only аргументе. Обычные именованные аргументы и умолчания разобраны в отдельном уроке.

Урок 4: умолчания и именованные аргументы

Как узнать, сколько аргументов пришло в функцию с *args?

По длине кортежа: len(args). args — обычный кортеж, поэтому работают и индексация args[0], и срез args[:2], и распаковка в несколько переменных.

Чем *args отличается от обычного параметра-списка?

Способом вызова. С параметром-списком данные приходят одним набором: f(prices). С *args значения передаются поштучно: total(1, 2, 3), и функция не знает заранее, сколько их будет. Если данные всегда приходят набором — выбирайте параметр-список.

Что означает first, *rest = [1, 2, 3]?

Распаковку с остатком: first заберёт первый элемент, а rest соберёт все остальные в список. Звёздочка в присваивании работает как зеркало *args — раскладывает набор, а не собирает его.

Понравился урок? Сошлитесь на него

«Звёздочка в объявлении функции собирает все позиционные аргументы вызова в один кортеж.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.