Модуль 4 · Словари, множества и кортежи

Справочник по Python · модуль 4 · словари, множества и кортежи

Глава 4.1

4.1

Кортежи
в Python

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

Категория
Структуры данных
Уровень
Базовый → продвинутый
Связанные темы
Списки, срезы, распаковка, словари
Зачем это знать
Хранить фиксированные записи и возвращать несколько результатов

§1Зачем нужен неизменяемый список

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

  1. Защита от случайной правки. Координаты точки, дата, размер экрана — данные, которые меняться не должны. Кортеж делает это невозможным, а не «нежелательным».
  2. Сигнал читателю. Круглые скобки сообщают: здесь фиксированная запись из нескольких полей, а не набор однотипных элементов.
  3. Возможность быть ключом. Из неизменяемости следует хешируемость — только благодаря ей кортеж можно положить в словарь или множество. Это §8, и это главная причина, по которой кортеж вообще существует.

§2Список и кортеж

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

Свойствоlisttuple
порядок элементовсохраняетсясохраняется
доступ по индексуестьесть
срезыестьесть
изменение элементаестьнет
методы append(), pop()естьнет
может быть ключом словарянетда
запись[10, 20](10, 20)

Всё, что в главах 3.1–3.8 говорилось про чтение списка — индексы с нуля, отрицательные индексы, срезы, len(), перебор циклом, — работает и для кортежа без изменений. Отличается только вторая половина: всё, что связано с изменением.

§3Создание кортежа

main.pyтри способа
point = (10, 20)
empty = ()
letters = tuple("abc")

print(point)
print(empty)
print(letters)
Выводтри кортежа
(10, 20)
()
('a', 'b', 'c')

tuple() принимает любой итерируемый объект — список, строку, множество — и строит из его элементов новый кортеж.

§4Кортеж создаёт запятая

Здесь скрыта деталь, на которой спотыкаются почти все: кортеж создают не скобки, а запятая. Скобки лишь группируют выражение.

main.pyскобок недостаточно
number = (5)
singleton = (5,)

print(type(number))
print(type(singleton))
Выводпервое — обычное число
<class 'int'>
<class 'tuple'>

Обратное тоже верно: без скобок кортеж прекрасно создаётся.

main.pyскобок нет вовсе
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Индексы и срезы

Чтение кортежа ничем не отличается от чтения списка.

main.pyэлемент, последний и срез
colors = ("red", "green", "blue", "white")

print(colors[0])
print(colors[-1])
print(colors[1:3])
Выводсрез кортежа — тоже кортеж
red
white
('green', 'blue')

Обратите внимание на последнюю строку: срез списка даёт список, срез кортежа — кортеж. Тип сохраняется.

§6Неизменяемость

Попытка заменить элемент завершается ошибкой.

main.pyприсваивание по индексу
point = (10, 20)
point[0] = 99
Ошибкапозиции зафиксированы
TypeError: 'tuple' object does not support item assignment

Методов, меняющих коллекцию, у кортежа нет вовсе — обращение к ним даёт другую ошибку.

main.pyметода append() не существует
(1, 2).append(3)
Ошибкатакого метода у типа нет
AttributeError: 'tuple' object has no attribute 'append'

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

§7Что именно неизменяемо

Неизменяемость кортежа относится к его позициям, а не к содержимому этих позиций. Если внутри лежит список, он сохраняет собственную изменяемость.

main.pyизменяемый объект внутри кортежа
record = ("Max", ["Python"])
record[1].append("SQL")

print(record)
Выводпозиции те же, список изменился
('Max', ['Python', 'SQL'])
Схемаграница неизменяемости
record ──→ ( "Max" , • )
                     │
                     └──→ ["Python", "SQL"]   этот объект менять можно

менять нельзя: какие объекты лежат в позициях кортежа
менять можно:  внутреннее состояние этих объектов

Это ровно та же граница, что у поверхностной копии в главе 3.12: внешний уровень защищён, внутренний — нет.

§8Хешируемость

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

main.pyсписок хешировать нельзя
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.

main.pyкортеж со списком внутри
print(hash((1, [2])))
Ошибкавиноват вложенный список
TypeError: unhashable type: 'list'

§9Составной ключ словаря

Практическая польза хешируемости — ключ, состоящий из нескольких частей.

main.pyгород и время суток вместе
temperatures = {
    ("Москва", "утро"): -3,
    ("Москва", "вечер"): -7,
}

print(temperatures[("Москва", "вечер")])
Выводзначение по составному ключу
-7

Со списком та же запись невозможна.

main.pyсписок в роли ключа
data = {["Москва", "утро"]: -3}
Ошибкаключ должен быть неизменяемым
TypeError: unhashable type: 'list'

По той же причине кортежи можно класть в множество, а списки нельзя, — об этом глава 4.5.

§10Операции над кортежами

Кортежи поддерживают те же операторы, что списки, — и все они создают новый объект, не трогая исходный.

main.pyобъединение, повторение, проверка
left = (1, 2)
right = (3, 4)

print(left + right)
print(("A",) * 3)
print(2 in left)
Выводтри результата
(1, 2, 3, 4)
('A', 'A', 'A')
True

Отсюда способ «изменить» кортеж: собрать новый и связать с тем же именем.

main.pyдобавление элемента
point = (10, 20)
point = point + (30,)

print(point)
Выводэто другой объект с тем же именем
(10, 20, 30)

Старый кортеж при этом не изменился — он просто перестал быть нужен. Если такие «изменения» нужны часто, значит выбран не тот тип: возьмите список.

§11Преобразование коллекций

main.pyсписок в кортеж и обратно
numbers = [10, 20, 30]

fixed = tuple(numbers)
editable = list(fixed)

print(fixed)
print(editable)
Выводсодержимое то же, типы разные
(10, 20, 30)
[10, 20, 30]

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

При преобразовании множества порядок результата заранее неизвестен: у множества позиционного порядка нет.

§12Несколько результатов функции

Функция возвращает ровно одно значение. Когда результатов несколько, они автоматически собираются в кортеж — благодаря правилу из §4.

main.pyдва числа через запятую
def get_point():
    return 10, 20

result = get_point()

print(result)
print(type(result))
Вывододин объект tuple
(10, 20)
<class 'tuple'>

Скобки в return писать не обязательно — их роль играет запятая. Это самое частое место, где кортеж появляется в коде, даже если о нём не думали.

§13Распаковка

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

main.pyобычная и расширенная распаковка
name, age, role = ("Max", 20, "admin")
first, *middle, last = (10, 20, 30, 40)

print(name, role)
print(first, middle, last)
Выводmiddle всегда список
Max admin
10 [20, 30] 40

Число имён слева должно совпадать с числом элементов, иначе ValueError. Форма со звёздочкой снимает это требование для середины.

§14Пары словаря

Метод items() выдаёт пары «ключ и значение», и каждая такая пара — кортеж. Поэтому привычная запись цикла по словарю на самом деле распаковывает кортежи.

main.pyраспаковка прямо в заголовке цикла
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().

main.pysorted() возвращает список
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] = 7TypeError: присваивание запрещенособрать новый кортеж
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, 3
Прочитатьpoint[0]point[1:]
Распаковатьx, y = point
«Добавить»point = point + (30,)
Составной ключdata[(city, time)]
Методыdata.count(x)data.index(x)

Итог главы

Кортеж сохраняет порядок, поддерживает индексы и срезы, но фиксирует набор позиций: заменить элемент нельзя, а методов изменения у него попросту нет. Создаёт кортеж запятая, а не скобки, — отсюда и return x, y, и обмен значениями a, b = b, a. Неизменяемость даёт хешируемость, и потому кортеж годится в ключи словаря и элементы множества — но только если внутри у него нет изменяемых объектов.

Глава 4.1 · Кортежи · Справочник по Python Автор: Макаров Максим Николаевич