Кортежи и распаковка
Теория
Кортеж (tuple) — упорядоченная неизменяемая коллекция. Он похож на список: те же индексы, срезы, len и in. Но изменить кортеж после создания нельзя — ни заменить элемент, ни добавить, ни удалить. Поэтому кортеж подходит для записей с фиксированной структурой, где каждая позиция имеет смысл: позиция заказа (product_id, quantity, price), строка из базы данных, пара «широта, долгота».
С кортежами тесно связана распаковка — присваивание нескольких переменных за раз: product_id, qty, price = item. Число переменных должно совпадать с числом элементов, а оставшиеся элементы можно собрать звёздочкой: first, *rest = .... Распаковка работает с любой последовательностью, но чаще всего встречается с кортежами — в циклах, при обмене значений и (дальше по курсу) при возврате нескольких значений из функции.
Наглядно
Таблица 1. Список и кортеж: чем отличаются и когда что выбирать.
list |
tuple |
|
|---|---|---|
| Запись | [42, 2, 1499] |
(42, 2, 1499) или 42, 2, 1499 |
| Изменяемость | можно менять | нельзя менять |
| Смысл | однородные элементы, их число меняется | запись: каждая позиция — своё поле, число полей фиксировано |
| Пример в магазине | товары в корзине | позиция заказа (product_id, quantity, price) |
| Ключ словаря | нельзя | можно, если внутри только неизменяемые значения |
Таблица 2. Варианты распаковки на кортеже item = (42, 2, 1499) и order = (1042, "paid", "мышь", "коврик").
| Запись | Результат | Комментарий |
|---|---|---|
pid, qty, price = item |
42, 2, 1499 |
число переменных = числу элементов |
pid, qty = item |
ValueError |
элементов больше, чем переменных |
order_id, status, *products = order |
products == ["мышь", "коврик"] |
* собирает остаток в список |
order_id, *_ = order |
order_id == 1042 |
_ — «это значение не нужно» |
a, b = b, a |
значения поменялись местами | обмен без временной переменной |
На практике
Задача: хранить позиции заказа как записи (product_id, quantity, price), разбирать их на поля и считать итог заказа.
Шаг 1. Создаём кортеж
Позиция заказа — три поля в фиксированном порядке:
item = (42, 2, 1499) print(item[0], item[-1], len(item)) print(type(item))
Результат:
42 1499 3 <class 'tuple'>
Шаг 2. Убеждаемся, что кортеж не меняется
Попробуем поменять количество в позиции:
item[1] = 3
Результат:
TypeError: 'tuple' object does not support item assignment
Методов, меняющих кортеж (append, remove), тоже нет: item.append(5) — AttributeError. «Изменить» кортеж — значит создать новый: item = (item[0], 3, item[2]).
Шаг 3. Распаковываем
Разбираем запись на поля одной строкой:
product_id, quantity, price = item print(product_id, quantity, price) print(quantity * price)
Результат:
42 2 1499 2998
Шаг 4. Следим за числом переменных
Если переменных меньше или больше, чем элементов, распаковка падает:
product_id, quantity = item
Результат:
ValueError: too many values to unpack (expected 2)
Шаг 5. Собираем остаток звёздочкой
Запись заказа: номер, статус, а дальше — сколько угодно товаров:
order = (1042, "paid", "мышь", "коврик") order_id, status, *products = order print(order_id, status, products)
Результат:
1042 paid ['мышь', 'коврик']
*products забирает всё, что осталось, и всегда становится списком — даже пустым.
Шаг 6. Создаём кортеж из одного элемента
Кортеж создают запятые, а не скобки. Без запятой скобки — просто скобки:
sizes = ("XL") one_size = ("XL",) print(type(sizes), type(one_size))
Результат:
<class 'str'> <class 'tuple'>
Шаг 7. Меняем значения местами
В акции «старая и новая цена» перепутали значения — меняем их распаковкой:
old_price, new_price = 990, 1499 old_price, new_price = new_price, old_price print(old_price, new_price)
Результат:
1499 990
Справа сначала собирается кортеж (1499, 990), потом он распаковывается в переменные слева.
Шаг 8. Распаковываем в цикле
Заказ — список позиций-кортежей; распаковка прямо в заголовке for:
items = [(42, 2, 1499), (7, 1, 5990), (13, 3, 490)] total = 0 for product_id, quantity, price in items: total += quantity * price print(total)
Результат:
10458
Частые ошибки
("XL")— не кортеж. Это строка в скобках. Кортеж из одного элемента —("XL",). А"L" in ("XL")проверяет подстроку и даётTrue.- Число переменных при распаковке. Не совпадает —
ValueError: too many values to unpackилиnot enough values to unpack. Если длина может меняться —*rest. - Попытка изменить кортеж.
item[1] = 3—TypeError,item.append(...)—AttributeError. Создавайте новый кортеж. - «Неизменяемый» кортеж со списком внутри.
(42, ["black"])нельзя изменить по индексу, но список внутри меняется:item[1].append("white")работает. Подробно — в теме «Изменяемые и неизменяемые типы».
Итог
Кортеж
- Упорядоченный и неизменяемый: индексы, срезы,
len,in— как у списка; менять нельзя. - Подходит для записей с фиксированной структурой:
(product_id, quantity, price). - Кортеж создают запятые:
42, 2, 1499; из одного элемента —("XL",).
Распаковка
a, b, c = t— число переменных равно числу элементов.first, *rest = t— остаток в список;_— для ненужных значений.a, b = b, a— обмен значениями.- Работает в заголовке
for:for pid, qty, price in items:.
Вопросы на собеседовании
Зачем нужен кортеж, если есть список?
Кортеж неизменяем: его можно безопасно передавать и хранить, не боясь, что кто-то поменяет содержимое; он может быть ключом словаря и элементом множества (если внутри неизменяемые значения); он занимает немного меньше памяти. По смыслу кортеж — запись с фиксированными полями, список — коллекция однородных элементов.
Как создать кортеж из одного элемента?
С запятой: (42,) или просто 42,. (42) — это число 42 в скобках: кортеж создают запятые, а скобки только группируют.
Что выведет t = (1, 2, [3, 4]); t[2].append(5); print(t)?
(1, 2, [3, 4, 5]). Кортеж хранит ссылки на объекты и не даёт заменить их, но сам список внутри изменяемый — его содержимое меняется.
Глубже
Почему кортеж со списком внутри не может быть ключом словаря, хотя «неизменяемый», — в курсе «Python под капотом», тема «Hashable и неизменяемость».