$ sudo teach IT
Модуль 5 · Коллекции · Урок 5.5

Кортежи и распаковка

Коллекция, которую нельзя менять, и способ разобрать её на переменные одной строкой

Теория~30 минутНовичокtuple()НеизменяемостьРаспаковкаОператор *Обмен переменных

Зачем нужна коллекция, которую нельзя менять

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

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

Кортеж (tuple) — упорядоченная неизменяемая коллекция элементов. Записывается в круглых скобках (), элементы разделяются запятыми. Слово встречается почти в каждой библиотеке Python, поэтому знать термин обязательно.

Как создать кортеж

Проще всего перечислить значения через запятую в круглых скобках — так же, как список, только со скобками другой формы.

color = (255, 128, 0)
print(color)
print(type(color))

Разберём построчно.

  • color = (255, 128, 0) — три числа в скобках через запятую превращаются в один кортеж и сохраняются в переменной color.
  • print(color) — напечатает кортеж целиком: (255, 128, 0).
  • print(type(color)) — знакомая функция type() покажет <class 'tuple'>: перед нами кортеж, а не список.

Круглые скобки на самом деле необязательны — кортеж создаёт сама запятая. Это называют упаковкой: несколько значений через запятую Python молча упаковывает в один кортеж.

point = 10, 20
print(point)
print(type(point))
  • point = 10, 20 — скобок нет, но запятая между числами уже говорит Python: собери из них кортеж.
  • print(point) — выведет (10, 20), Python сам добавил скобки при печати.
  • print(type(point)) — снова <class 'tuple'>, хотя скобки мы не писали.

Есть и функция tuple() — она превращает в кортеж другую коллекцию, например список:

numbers = tuple([1, 2, 3])
print(numbers)
  • tuple([1, 2, 3]) — на входе список, на выходе кортеж с теми же элементами в том же порядке: (1, 2, 3).
Ловушка: кортеж из одного элемента

Если написать x = (5), вы получите не кортеж, а обычное число 5 — Python решит, что скобки здесь просто группируют выражение, как в математике. Чтобы Python понял, что вы хотите кортеж из одного элемента, после значения нужно поставить запятую.
x = (5)
print(type(x))

y = (5,)
print(type(y))

x получит тип int, а y — tuple с единственным элементом. Разница в одной запятой, но она полностью меняет тип значения. Запомните это правило: одноэлементный кортеж — это всегда (значение,).

Неизменяемость: что будет, если попробовать изменить

Официальный термин для главного свойства кортежа — иммутабельность (от английского immutable, «неизменяемый»). Это значит, что после создания кортежа ни один его элемент нельзя ни заменить, ни удалить, ни добавить новый.

rgb = (255, 128, 0)
rgb[0] = 100
  • rgb = (255, 128, 0) — создаём кортеж из трёх чисел.
  • rgb[0] = 100 — пытаемся присвоить новое значение первому элементу, обратившись к нему по индексу, как в списке.

Эта строка не выполнится — программа остановится с ошибкой TypeError: 'tuple' object does not support item assignment. Дословно: «объект типа tuple не поддерживает присваивание по элементу». Это не сбой программы, а сознательная защита: вы сами объявили эти данные постоянными, и Python honestly её соблюдает.

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

Кортеж и список похожи — оба хранят несколько значений по порядку. Разница в том, можно ли их менять после создания:

Свойство Список Кортеж
Скобки [ ] ( )
Можно изменять после создания Да Нет
Методы Много: append, insert, remove, sort и другие Два: count и index
Когда использовать Данные будут меняться: корзина, список задач Данные фиксированы: координаты, дата, цвет

Чтение элементов и два метода кортежа

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

student = ("Алина", 19, "ФИТ", 4.8)
print(student[0])
print(student[-1])
print(student[1:3])
print(len(student))
print("ФИТ" in student)
  • student[0] — первый элемент по индексу 0, выведет Алина.
  • student[-1] — последний элемент с конца, выведет 4.8.
  • student[1:3] — срез с индекса 1 до индекса 3 (не включая его), выведет (19, 'ФИТ') — срез кортежа тоже кортеж.
  • len(student) — вернёт 4, в кортеже четыре элемента.
  • "ФИТ" in student — проверка на вхождение, вернёт True.

А методов у кортежа всего два — осознанное решение авторов Python: раз кортеж нельзя менять, ему незачем иметь методы вроде append или remove.

scores = (5, 3, 4, 5, 5, 2, 4)
print(scores.count(5))
print(scores.index(3))
  • scores.count(5) — метод count() считает, сколько раз значение встречается в кортеже; пятёрка встречается трижды, выведет 3.
  • scores.index(3) — метод index() возвращает индекс первого вхождения значения; тройка стоит на позиции 1, выведет 1. Если бы искомого значения в кортеже не было, Python выбросил бы ошибку ValueError.

Распаковка: раскладываем кортеж по переменным

Допустим, у вас есть кортеж с тремя значениями, и каждое нужно положить в свою переменную. Можно писать name = student[0], age = student[1], faculty = student[2] — три строки с индексами. А можно одной строкой: перечислить имена переменных через запятую слева от знака равенства, и Python сам разложит значения по местам.

Это действие называют распаковкой (unpacking). Если упаковка — это когда несколько значений через запятую собираются в один кортеж, то распаковка — обратный процесс: кортеж «разбирают» обратно на отдельные переменные.
student = ("Алина", 19, "ФИТ")
name, age, faculty = student
print(name)
print(age)
print(faculty)
  • student = ("Алина", 19, "ФИТ") — кортеж из трёх значений.
  • name, age, faculty = student — слева три имени через запятую, справа кортеж из трёх элементов. Python берёт элементы по порядку: первый элемент кортежа уходит в name, второй — в age, третий — в faculty.
  • Дальше name, age и faculty — обычные отдельные переменные, никакого кортежа в них уже нет.
Количество переменных и элементов должно совпадать. Если слева переменных больше или меньше, чем элементов справа, Python остановится с ошибкой ValueError.
a, b = (1, 2, 3)
a, b, c = (1, 2)

В первой строке слева две переменные, а справа три значения — лишнему деться некуда, будет ValueError: too many values to unpack. Во второй строке наоборот — переменных больше, чем значений, будет ValueError: not enough values to unpack.

Распаковка работает не только с кортежами, но и с любой упорядоченной коллекцией — например, со списком, потому что механизм один и тот же: элементы по порядку раскладываются по переменным. a, b, c = [10, 20, 30] сработает точно так же, как с кортежем.

Оператор * — когда часть элементов не важна поштучно

Бывает нужно взять только первый элемент, а всё остальное сложить в один список, не считая заранее, сколько там элементов. Для этого перед одной из переменных ставят звёздочку *. Она говорит Python: «всё, что не досталось остальным переменным, собери сюда одним списком».

numbers = (1, 2, 3, 4, 5)
first, *rest = numbers
print(first)
print(rest)
  • first, *rest = numbers — first без звёздочки получает ровно один элемент, первый. Звёздочка перед rest означает: собери туда всё, что осталось.
  • print(first) — выведет 1.
  • print(rest) — выведет [2, 3, 4, 5]. Даже если исходный кортеж, звёздочка всегда собирает остаток именно в список, а не в кортеж.

Звёздочку можно поставить и в середине, и последней переменной — Python сам вычислит, сколько элементов отдать каждой обычной переменной, а остаток отдаст переменной со звёздочкой:

records = (2019, 2020, 2021, 2022, 2023)
first_year, *middle_years, last_year = records
print(first_year)
print(middle_years)
print(last_year)
  • first_year без звёздочки первым по счёту — получает первый элемент: 2019.
  • *middle_years в середине — заберёт всё, что осталось после того, как first_year и last_year получили по одному элементу: [2020, 2021, 2022].
  • last_year без звёздочки последним — получает последний элемент: 2023.
Ограничение: в одном присваивании звёздочка может стоять только один раз. Запись вида *a, *b = (1, 2, 3) Python не поймёт — это синтаксическая ошибка, а не ошибка выполнения: программа не запустится вообще.

Обмен значений переменных местами

Представьте, что нужно поменять местами значения двух переменных. В большинстве языков программирования для этого заводят третью, временную переменную, чтобы ничего не потерять при перезаписи. В Python это делается одной строкой — благодаря той же упаковке и распаковке, которые мы только что разобрали.

a = 10
b = 20
a, b = b, a
print(a)
print(b)
  • a = 10 и b = 20 — исходные значения.
  • a, b = b, a — ключевая строка. Справа Python сначала полностью вычисляет выражение b, a: берёт текущие значения переменных (20 и 10) и упаковывает их в новый кортеж (20, 10). Только после этого начинается присваивание слева: кортеж распаковывается в a и b.
  • print(a) и print(b) — выведут 20 и 10: значения поменялись местами.

Главное здесь — порядок действий: вся правая часть вычисляется до того, как хоть одна переменная слева поменяет значение, поэтому старые значения не «теряются» и временная переменная не нужна.

Распаковка прямо в заголовке цикла for

Когда в списке лежат кортежи — например, товары в формате «название, цена, количество» — распаковку можно делать прямо в заголовке цикла for, без отдельной строки присваивания.

products = [
    ("Молоко", 89.90, 2),
    ("Хлеб", 45.50, 3),
]
total = 0
for name, price, qty in products:
    total += price * qty
print(total)
  • products — список, где каждый элемент — кортеж из трёх значений.
  • for name, price, qty in products: — на каждом шаге цикла Python берёт очередной кортеж из products и сразу распаковывает его в три переменные: name, price и qty. Без распаковки пришлось бы писать item[0], item[1], item[2] внутри цикла — так гораздо понятнее.
  • total += price * qty — на каждом шаге к общей сумме прибавляется стоимость одной позиции.
  • print(total) — после цикла выведет итоговую сумму по всем товарам.

Это тот же приём, что и при переборе пар «индекс, значение»: каждый шаг цикла отдаёт готовый кортеж, и вы сразу раскладываете его на переменные по тому же правилу распаковки.

Частые ошибки

Забыли запятую в одноэлементном кортеже. size = (42) — это число, а не кортеж, нужна запятая: size = (42,). Программа не упадёт сразу, а поведёт себя не так, как вы ожидали: дальше size[0] вызовет TypeError, потому что у числа нет индексов.
Попытка изменить элемент кортежа. point = (1, 2), затем point[0] = 5 — TypeError. Если данные должны меняться, кортеж — неправильный выбор, нужен список.
Число переменных не совпадает с числом элементов при распаковке. a, b = (1, 2, 3) — ValueError: too many values to unpack. Заранее считайте элементы или используйте звёздочку для сбора остатка.
Две звёздочки в одном присваивании. *a, *b = (1, 2, 3, 4) — синтаксическая ошибка ещё до запуска: звёздочка в присваивании может быть только одна.

Что важно запомнить

  • Кортеж — упорядоченная неизменяемая коллекция: (1, 2, 3). Скобки создают упаковку, но реально кортеж создаёт запятая — скобки можно и не писать.
  • Одноэлементный кортеж — всегда с запятой: (5,). Без запятой это просто число в скобках.
  • Изменить кортеж нельзя: любая попытка присвоить, добавить или удалить элемент завершится TypeError.
  • Читать из кортежа можно так же, как из списка: индексы, отрицательные индексы, срезы, len(), in.
  • У кортежа всего два метода: count() считает вхождения значения, index() находит индекс первого вхождения.
  • Распаковка — a, b, c = коллекция: раскладывает элементы по переменным одной строкой. Количество переменных и элементов должно совпадать, иначе ValueError.
  • Звёздочка *rest собирает всё, что не досталось обычным переменным, в список — даже если элемент один или их нет вовсе. В одном присваивании она может стоять только один раз.
  • Обмен переменных a, b = b, a работает без временной переменной: правая часть целиком собирается в кортеж, и только потом распаковывается.
  • Распаковку можно делать прямо в заголовке цикла for, когда перебираете коллекцию кортежей — это избавляет от обращения по индексам внутри цикла.

Проверьте себя

5 вопросов

Работа с кортежами

Даны переменные name, age и grade с данными одного студента.

  • Соберите из них кортеж student в порядке (имя, возраст, оценка).
  • Получите из student обратно те же значения по индексам в переменные name2, age2, grade2.

Дальше даны переменные a и b. Поменяйте их значения местами без временной переменной и сохраните результат в те же имена a и b.

Анализ кортежа

Premium

Стоимость корзины товаров

Premium

Первый и последний

Premium