Справочник по Python · модуль 4 · словари, множества и кортежи
Глава 4.1
Кортежи
в Python
Кортеж хранит упорядоченную последовательность, как список, но не позволяет заменить, добавить или удалить элемент после создания. Из этой неизменяемости следует главное его свойство — кортеж может быть ключом словаря и элементом множества, а список не может.
- Категория
- Структуры данных
- Уровень
- Базовый → продвинутый
- Связанные темы
- Списки, срезы, распаковка, словари
- Зачем это знать
- Хранить фиксированные записи и возвращать несколько результатов
§1Зачем нужен неизменяемый список
Первая реакция на кортеж обычно такая: зачем нужен список, который нельзя менять? Ответ в том, что запрет — это не ограничение, а гарантия, и она даёт три вещи, которых у списка нет.
- Защита от случайной правки. Координаты точки, дата, размер экрана — данные, которые меняться не должны. Кортеж делает это невозможным, а не «нежелательным».
- Сигнал читателю. Круглые скобки сообщают: здесь фиксированная запись из нескольких полей, а не набор однотипных элементов.
- Возможность быть ключом. Из неизменяемости следует хешируемость — только благодаря ей кортеж можно положить в словарь или множество. Это §8, и это главная причина, по которой кортеж вообще существует.
§2Список и кортеж
Кортеж (tuple) — упорядоченная неизменяемая последовательность. У элементов остаются позиции, индексы и срезы, но сам набор позиций после создания зафиксирован.
| Свойство | list | tuple |
|---|---|---|
| порядок элементов | сохраняется | сохраняется |
| доступ по индексу | есть | есть |
| срезы | есть | есть |
| изменение элемента | есть | нет |
методы append(), pop() | есть | нет |
| может быть ключом словаря | нет | да |
| запись | [10, 20] | (10, 20) |
Всё, что в главах 3.1–3.8 говорилось про чтение списка — индексы с нуля, отрицательные индексы, срезы, len(), перебор циклом, — работает и для кортежа без изменений. Отличается только вторая половина: всё, что связано с изменением.
§3Создание кортежа
point = (10, 20) empty = () letters = tuple("abc") print(point) print(empty) print(letters)
(10, 20)
()
('a', 'b', 'c')tuple() принимает любой итерируемый объект — список, строку, множество — и строит из его элементов новый кортеж.
§4Кортеж создаёт запятая
Здесь скрыта деталь, на которой спотыкаются почти все: кортеж создают не скобки, а запятая. Скобки лишь группируют выражение.
number = (5) singleton = (5,) print(type(number)) print(type(singleton))
<class 'int'> <class 'tuple'>
Обратное тоже верно: без скобок кортеж прекрасно создаётся.
values = 1, 2, 3 print(values) print(type(values))
(1, 2, 3) <class 'tuple'>
Именно поэтому return x, y из §12 возвращает кортеж, а a, b = b, a меняет значения местами: справа от знака = собирается кортеж, слева он распаковывается. Обмен значениями из главы 1.6 устроен именно так.
§5Индексы и срезы
Чтение кортежа ничем не отличается от чтения списка.
colors = ("red", "green", "blue", "white") print(colors[0]) print(colors[-1]) print(colors[1:3])
red
white
('green', 'blue')Обратите внимание на последнюю строку: срез списка даёт список, срез кортежа — кортеж. Тип сохраняется.
§6Неизменяемость
Попытка заменить элемент завершается ошибкой.
point = (10, 20) point[0] = 99
TypeError: 'tuple' object does not support item assignment
Методов, меняющих коллекцию, у кортежа нет вовсе — обращение к ним даёт другую ошибку.
(1, 2).append(3)
AttributeError: 'tuple' object has no attribute 'append'
Разница в типах исключений отражает разницу причин: в первом случае операция существует, но запрещена, во втором — метода нет как такового.
§7Что именно неизменяемо
Неизменяемость кортежа относится к его позициям, а не к содержимому этих позиций. Если внутри лежит список, он сохраняет собственную изменяемость.
record = ("Max", ["Python"]) record[1].append("SQL") print(record)
('Max', ['Python', 'SQL'])record ──→ ( "Max" , • )
│
└──→ ["Python", "SQL"] этот объект менять можно
менять нельзя: какие объекты лежат в позициях кортежа
менять можно: внутреннее состояние этих объектов
Это ровно та же граница, что у поверхностной копии в главе 3.12: внешний уровень защищён, внутренний — нет.
§8Хешируемость
Теперь главное следствие. Чтобы значение можно было использовать как ключ словаря или элемент множества, Python должен уметь вычислить от него хеш — число, по которому объект быстро находят. А вычислять хеш имеет смысл только у того, что не меняется: изменился объект — изменился бы и хеш, и найти его стало бы невозможно.
print(hash((1, 2)) == hash((1, 2))) print(hash([1, 2]))
True TypeError: unhashable type: 'list'
Слово unhashable в сообщении об ошибке — самое частое, что видит начинающий при работе со словарями и множествами. Означает оно всегда одно: вы пытаетесь использовать изменяемый объект там, где нужен неизменяемый.
| Тип | Изменяемый | Хешируемый | Годится в ключи |
|---|---|---|---|
int, str, bool | нет | да | да |
tuple | нет | да, если внутри нет изменяемых | да |
list, dict, set | да | нет | нет |
Средняя строка содержит оговорку, и она важна: кортеж со списком внутри тоже не хешируется — ведь его содержимое может измениться, как показал §7.
print(hash((1, [2])))
TypeError: unhashable type: 'list'
§9Составной ключ словаря
Практическая польза хешируемости — ключ, состоящий из нескольких частей.
temperatures = {
("Москва", "утро"): -3,
("Москва", "вечер"): -7,
}
print(temperatures[("Москва", "вечер")])
-7
Со списком та же запись невозможна.
data = {["Москва", "утро"]: -3}
TypeError: unhashable type: 'list'
По той же причине кортежи можно класть в множество, а списки нельзя, — об этом глава 4.5.
§10Операции над кортежами
Кортежи поддерживают те же операторы, что списки, — и все они создают новый объект, не трогая исходный.
left = (1, 2) right = (3, 4) print(left + right) print(("A",) * 3) print(2 in left)
(1, 2, 3, 4)
('A', 'A', 'A')
TrueОтсюда способ «изменить» кортеж: собрать новый и связать с тем же именем.
point = (10, 20) point = point + (30,) print(point)
(10, 20, 30)
Старый кортеж при этом не изменился — он просто перестал быть нужен. Если такие «изменения» нужны часто, значит выбран не тот тип: возьмите список.
§11Преобразование коллекций
numbers = [10, 20, 30] fixed = tuple(numbers) editable = list(fixed) print(fixed) print(editable)
(10, 20, 30) [10, 20, 30]
Приём с двойным преобразованием решает типичную задачу: получить изменённый вариант неизменяемых данных. Кортеж превращают в список, меняют, собирают обратно.
При преобразовании множества порядок результата заранее неизвестен: у множества позиционного порядка нет.
§12Несколько результатов функции
Функция возвращает ровно одно значение. Когда результатов несколько, они автоматически собираются в кортеж — благодаря правилу из §4.
def get_point(): return 10, 20 result = get_point() print(result) print(type(result))
(10, 20) <class 'tuple'>
Скобки в return писать не обязательно — их роль играет запятая. Это самое частое место, где кортеж появляется в коде, даже если о нём не думали.
§13Распаковка
Обратная операция — разложить кортеж по именам. Разбиралась она в главе 6.6, здесь важно, что источником обычно служит именно кортеж.
name, age, role = ("Max", 20, "admin") first, *middle, last = (10, 20, 30, 40) print(name, role) print(first, middle, last)
Max admin 10 [20, 30] 40
Число имён слева должно совпадать с числом элементов, иначе ValueError. Форма со звёздочкой снимает это требование для середины.
§14Пары словаря
Метод items() выдаёт пары «ключ и значение», и каждая такая пара — кортеж. Поэтому привычная запись цикла по словарю на самом деле распаковывает кортежи.
prices = {"book": 900, "pen": 120}
for name, price in prices.items():
print(name, price)
book 900 pen 120
То же самое с enumerate() из главы 3.14: он тоже выдаёт кортежи из индекса и значения. Словари подробно разбираются в следующей главе.
§15Два метода кортежа
Собственных методов у кортежа всего два — оба только читают.
| Метод | Результат | Пример |
|---|---|---|
count(value) | число совпадений | (1, 1, 2).count(1) → 2 |
index(value) | индекс первого совпадения | (10, 20).index(20) → 1 |
Сравните со списком, у которого методов больше десятка: разница ровно в том, что все остальные меняли коллекцию. Зато универсальные функции работают одинаково — len(), min(), max(), sum(), sorted(), enumerate().
data = (3, 1, 2) print(sorted(data)) print(tuple(sorted(data)))
[1, 2, 3] (1, 2, 3)
§16Когда кортеж, когда список
| Ситуация | Тип | Почему |
|---|---|---|
| координаты, дата, размер | tuple | фиксированная запись, меняться не должна |
| несколько результатов функции | tuple | получается сам собой |
| ключ словаря из нескольких частей | tuple | список не хешируется |
| список товаров, который пополняется | list | нужны append() и remove() |
| результат, который будут сортировать на месте | list | у кортежа нет sort() |
Практическое правило: элементы однородные и их количество меняется — список; поля разного смысла и их набор постоянен — кортеж. В (10, 20) первое число не «такое же, как» второе: это x и y.
§17Частые ошибки
| Что написано | Что произойдёт | Как правильно |
|---|---|---|
item = (5) | получится int, а не кортеж | item = (5,) |
point[0] = 7 | TypeError: присваивание запрещено | собрать новый кортеж |
data.append(x) для кортежа | AttributeError: метода нет | data = data + (x,) или взять список |
{[1, 2]: "a"} | TypeError: unhashable type: 'list' | {(1, 2): "a"} |
| ключ-кортеж со списком внутри | та же ошибка: вложенный список не хешируется | сделать все уровни неизменяемыми |
a, b = (1, 2, 3) | ValueError: число имён не совпало | добавить имя или a, *rest = ... |
| считать вложенный список неизменяемым | его методы продолжат его менять | помнить: защищены только позиции |
ждать кортеж от sorted() | вернётся список | tuple(sorted(data)) |
§18Проверьте себя
Что именно создаёт кортеж из одного элемента?
Запятая: (5,). Скобки только группируют выражение, поэтому (5) — обычное число.
Почему кортеж может быть ключом словаря, а список — нет?
Ключ должен быть хешируемым, а хеш имеет смысл только у неизменяемого объекта: у изменяемого он менялся бы вместе с содержимым, и найти значение стало бы невозможно.
Может ли кортеж содержать изменяемый список?
Да. Заменить позицию нельзя, но вложенный список сохраняет свою изменяемость — и из-за него такой кортеж перестаёт быть хешируемым.
Что возвращает функция с записью return x, y?
Один кортеж (x, y) — его собирает запятая. Дальше его обычно распаковывают.
Как «добавить» элемент в кортеж?
Никак — можно только создать новый: data = data + (x,). Если это нужно часто, подходящий тип — список.
Какой тип вернёт срез кортежа?
Кортеж. Срез сохраняет тип исходной последовательности.
§19Шпаргалка
point = (10, 20)single = (5,)values = 1, 2, 3point[0]point[1:]x, y = pointpoint = point + (30,)data[(city, time)]data.count(x)data.index(x)Итог главы
Кортеж сохраняет порядок, поддерживает индексы и срезы, но фиксирует набор позиций: заменить элемент нельзя, а методов изменения у него попросту нет. Создаёт кортеж запятая, а не скобки, — отсюда и return x, y, и обмен значениями a, b = b, a. Неизменяемость даёт хешируемость, и потому кортеж годится в ключи словаря и элементы множества — но только если внутри у него нет изменяемых объектов.