Кортеж с изменяемым элементом и hash() в Python
@python_quizРазберем этот квиз
Кратко: в Python кортеж сам по себе неизменяем, но может содержать изменяемые объекты (например, список). Хэширование применяется к объектам, а не к их контейнерам — хэш от целого числа работает, хэш от списка — нет.
Исходный код и ожидаемое поведение
Вот код из квиза:

Построчный разбор:
- a = (1, 2, [3, 4]) — создаём кортеж, третий элемент которого — список.
- a[2].append(5) — добавляем 5 в список, который лежит внутри кортежа. Кортеж остаётся тем же объектом, но содержимое вложенного списка меняется.
- print(a, hash(a[0]), hash(a[2][2])) — печатаем сам кортеж и хэши двух чисел: первого элемента (1) и третьего элемента списка после изменения (5).
На типичной реализации CPython пример выведет примерно:

(для малых целых чисел в CPython хэш совпадает со значением числа, поэтому hash(1) == 1, hash(5) == 5).
Почему так — ключевые правила
- Неизменяемость контейнера (tuple) не означает, что его содержимое неизменно. Кортеж не позволяет заменить целиком свой элемент (например, a[2] = ... вызовет ошибку), но если внутри него лежит изменяемый объект (list, dict и т.п.), то этот внутренний объект можно менять.
- hash(x) работает только для хэшируемых объектов. Простые неизменяемые типы (int, str, tuple из хэшируемых элементов и т.д.) — хэшируемы. Списки — не хэшируемы, попытка hash(list) приведёт к
TypeError: unhashable type: 'list'. - В коде вызываются hash(a[0]) и hash(a[2][2]). Оба этих выражения хэшируют целые числа (1 и 5), которые хэшируемы, поэтому ошибок не возникает. Мы не пытаемся хэшировать сам кортеж или сам список.
Разбор вариантов ответов (логика)
- Вариант, где вывод (1, 2, [3, 4, 5]) и оба hash работают без ошибок — соответствует поведению кода: список внутри кортежа изменился, и хэши для целых чисел вычисляются без проблем.
- Варианты, предполагающие ошибку при append — неверны, потому что метод append изменяет сам список, и наличие списка внутри кортежа этому не мешает.
- Варианты, где падает hash для a[0] или hash для a[2][2] — неверны, поскольку хэширование целых чисел поддерживается. Ошибка возникла бы, если бы пытались хэшировать сам список или сам кортеж с не хэшируемыми элементами.
Демонстрация возможной ошибки (для наглядности)
Если попытаться взять хэш от списка или от кортежа, содержащего список, получите ошибку:

Вывод будет примерно:

А если попытаться хэшировать сам кортеж, содержащий список, тоже будет ошибка:

Выводы и рекомендации
- Кортежы — неизменяемы по структуре, но могут хранить изменяемые объекты. Это часто удобно, но требует аккуратности: изменение вложенных объектов изменяет «логическое состояние» кортежа.
- Хэшируемы только те объекты, у которых есть стабильный, неизменяемый хэш — например, int, str, tuple из хэшируемых элементов. Список не хэшируем.
- Если вам нужен хэш от составной структуры, убедитесь, что все её элементы хэшируемы, или преобразуйте изменяемые части в неизменяемые (например, list -> tuple) перед хэшированием.