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

Кортеж (tuple) в Python 3.x часто описывают как «список, который нельзя менять». Это верно на уровне API, но скрывает главное: неизменяемость даёт кортежу свойства, которых у списка нет в принципе. Кортеж можно положить ключом в словарь, он занимает меньше памяти на элемент, и интерпретатор кеширует пустой кортеж и мелкие константные кортежи. Разберём, где эти отличия видны в коде и какую цену вы платите за каждое из них.
Речь про CPython 3.11 и 3.12 — там, где поведение зависит от реализации, это оговорено отдельно.
Где кортеж отличается от списка на самом деле
Синтаксически всё просто: список — [1, 2, 3], кортеж — (1, 2, 3). Но скобки у кортежа необязательны, а определяет его запятая:
# это кортеж, скобки только для читаемости
tochka = 10, 20
print(type(tochka)) # <class 'tuple'>
# кортеж из одного элемента — запятая обязательна
odin = (42,)
ne_kortezh = (42) # это просто int в скобках
print(type(odin), type(ne_kortezh)) # <class 'tuple'> <class 'int'>Самая частая ошибка новичка — забыть запятую в кортеже из одного элемента и получить не то, что ожидалось. Функция, которая должна вернуть кортеж, вернёт голое значение.
Неизменяемость поверхностная
Кортеж запрещает менять свои ссылки, но не запрещает менять объекты, на которые он ссылается:
dannye = (1, [10, 20], 3)
# dannye[1] = [99] # TypeError: 'tuple' object does not support item assignment
dannye[1].append(30) # а так можно — меняется сам список внутри
print(dannye) # (1, [10, 20, 30], 3)Отсюда прямое следствие: кортеж со вложенным списком нельзя использовать как ключ словаря, хотя сам он и кортеж. Причина — в разделе ниже.
Кортеж как ключ словаря
Список нельзя положить ключом в dict или элементом в set — он не хешируется. Кортеж хешируется, но только если хешируемы все его элементы:
koordinaty = {}
koordinaty[(55.75, 37.61)] = "Москва" # работает
koordinaty[(59.93, 30.31)] = "Петербург"
print(koordinaty[(55.75, 37.61)]) # Москва
# а вот это упадёт
plohoy_kluch = (1, [2, 3])
# {plohoy_kluch: "x"} # TypeError: unhashable type: 'list'Хеш кортежа вычисляется из хешей его элементов. Если внутри есть изменяемый объект (список, множество, словарь), кортеж перестаёт быть хешируемым целиком. Это не ограничение синтаксиса, а гарантия корректности: ключ словаря не должен менять свой хеш после вставки, иначе поиск сломается.
Правило простое: кортеж годится в ключи ровно тогда, когда всё, что в нём лежит, само годится в ключи. Одна вложенная коллекция — и вся конструкция теряет хешируемость.
Распаковка — то, ради чего кортежи используют чаще всего
Множественное присваивание в Python — это распаковка кортежа. Даже когда скобок не видно:
a, b = 1, 2 # справа кортеж (1, 2)
a, b = b, a # обмен без временной переменной
# распаковка с «звёздочкой» — остаток в список
pervyy, *ostalnye = (10, 20, 30, 40)
print(pervyy) # 10
print(ostalnye) # [20, 30, 40] — обратите внимание: это list, не tupleФункции, которые «возвращают несколько значений», на деле возвращают один кортеж, а вызывающий код его распаковывает:
def podelit(delimoe, delitel):
chastnoe = delimoe // delitel
ostatok = delimoe % delitel
return chastnoe, ostatok # возвращается кортеж (chastnoe, ostatok)
ch, ost = podelit(17, 5)
print(ch, ost) # 3 2Расход памяти: замер вместо утверждений
Кортеж компактнее списка при том же содержимом, потому что список хранит запас под будущий рост, а кортежу расти некуда. Проверить это можно самостоятельно через sys.getsizeof — он показывает размер самого объекта без учёта элементов:
import sys
spisok = [1, 2, 3, 4, 5]
kortezh = (1, 2, 3, 4, 5)
print(sys.getsizeof(spisok)) # больше
print(sys.getsizeof(kortezh)) # меньшеТочные числа зависят от версии CPython и разрядности платформы, поэтому запускайте на своей сборке, а не полагайтесь на цифры из чужой статьи. Смысл в направлении разницы, а не в конкретных байтах: список несёт накладные расходы на динамический массив, кортеж — нет.
Когда разница в памяти реально важна
- Миллионы мелких неизменяемых записей — например, координаты или пары «ключ-значение» в кеше. Здесь экономия на элемент умножается на количество.
- Данные, которые точно не меняются после создания и живут долго.
Для десятка временных значений разница в памяти неощутима — выбирайте кортеж не ради байтов, а ради того, что он честно говорит читателю кода: «это менять нельзя».
Именованные кортежи и сравнение с dataclass
Голый кортеж читается плохо: tochka[0] ничего не говорит о смысле. collections.namedtuple добавляет имена полей, сохраняя неизменяемость и хешируемость:
from collections import namedtuple
Tochka = namedtuple("Tochka", ["x", "y"])
p = Tochka(55.75, 37.61)
print(p.x, p.y) # 55.75 37.61
print(p[0]) # 55.75 — индекс тоже работает
# это по-прежнему кортеж, годится в ключи
karta = {p: "Москва"}| Что нужно | Подходит | Цена |
|---|---|---|
| Пара значений на месте, распаковка | обычный кортеж | непонятно, что значат индексы |
| Неизменяемая запись с именами полей, ключ словаря | namedtuple | нельзя менять после создания |
| Запись с методами, которую нужно менять | dataclass | по умолчанию изменяемый, не хешируется |
Если запись должна меняться и не нужна как ключ — берите dataclass. Если запись неизменяема и должна работать как ключ или элемент множества — namedtuple или frozen-датакласс. Обычный кортеж оставьте для коротких временных группировок и возврата нескольких значений.
Что ломается на практике
- Забытая запятая в кортеже из одного элемента.
return (x)вернёт значение, а не кортеж. Пишитеreturn (x,). - Попытка положить кортеж со списком внутри в ключи словаря. Ошибка
TypeError: unhashable type: 'list'появится только в момент вставки, не при создании кортежа. - Ожидание, что кортеж защитит вложенные данные. Неизменяемость поверхностна: список внутри кортежа меняется свободно.
- Скобки-ловушка.
(1)— этоint, а(1,)— кортеж. Пустой кортеж —(), без запятой.
Что смотреть дальше
Разберитесь с frozenset — неизменяемым множеством, которое так же хешируется и годится в ключи, но убирает дубликаты и не сохраняет порядок. Для типизированных неизменяемых записей в новых проектах посмотрите typing.NamedTuple — тот же namedtuple, но с аннотациями типов. Если данные объёмные и однотипные, сравните кортежи с массивами array или numpy: там экономия памяти на порядок больше, но теряется гибкость по типам элементов.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Python docs — Data Structures: Tuples and Sequences, Python docs — collections.namedtuple
Частые вопросы
А если у меня Python 2 — работает ли распаковка со звёздочкой?
Почему у меня getsizeof показал другие числа, чем ожидалось?
Кортеж быстрее списка при обходе?
Можно ли добавить элемент в кортеж?
Почему namedtuple предпочтительнее обычного кортежа?
Кортеж со вложенным кортежем можно класть в ключи словаря?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.