Кортеж с изменяемым элементом и hash() в Python

Кортеж с изменяемым элементом и hash() в Python

@python_quiz

Разберем этот квиз

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

Исходный код и ожидаемое поведение

Вот код из квиза:

python

Построчный разбор:

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

На типичной реализации CPython пример выведет примерно:

python

(для малых целых чисел в 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] — неверны, поскольку хэширование целых чисел поддерживается. Ошибка возникла бы, если бы пытались хэшировать сам список или сам кортеж с не хэшируемыми элементами.

Демонстрация возможной ошибки (для наглядности)

Если попытаться взять хэш от списка или от кортежа, содержащего список, получите ошибку:

python

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

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

python

Выводы и рекомендации

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

Report Page