Так ли неизменяем кортеж?

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

Так если мы имеем список

>>> a = [1, 2]

то можем изменить его

>>> a[0] = 2 
>>> a 
[2, 2]

Если же мы имеем кортеж (определяется круглыми скобками)

>>> a = (1, 2)

то попытка его изменения приведет к ошибке:

>>> a[0] = 2 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
TypeError: 'tuple' object does not support item assignment

Мы можем только получать значение элементов кортежа:

>>> a[0] 
1

Однако, если элементом кортежа является изменяемый объект. Например, список или словарь, то этот элемент можно изменять:

>>> a = (1, [2, 3], {'a': 10, 'b': 20}) 
>>> a[1][0] = 5 
>>> a[2]['c'] = 30 
>>> a 
(1, [5, 3], {'c': 30, 'b': 20, 'a': 10})

В данном примере кортеж содержит три элемента: число, список и словарь. Первый элемент мы изменить не можем, т. к. числа — неизменяемые объекты. А вот второй и третий элемент изменить можно. В примере первый индекс указывает на элемент кортежа, второй — на элемент списка или ключ словаря.

Почему же такое изменение кортежа возможно? Дело в том, что в кортеже сохраняются не сами значения элементов списка (или словаря и т. д.), а ссылка на список. Поэтому хотя в кортеже ссылку изменить нельзя (то есть нельзя поменять список на другой), но вот что там делается с объектом по этой ссылке кортеж «не волнует».

Рассмотрим пример с переменными. Попытка заменить одну переменную, ссылающуюся на один изменяемый объект, на другую переменную, тоже ссылающуюся на изменяемый объект, приводит к неудаче. Потому что в памяти мы пытаемся перезаписать ячейку памяти кортежа, что невозможно.

>>> a = [1, 2] 
>>> b = {1: 'i', 2: 'j'} 
>>> c = (a, b) 
>>> c 
([1, 2], {1: 'i', 2: 'j'}) 
>>> d = [3, 4, 5, 6] 
>>> c[0] = d 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
TypeError: 'tuple' object does not support item assignment

Казалось бы, выход в данной ситуации — это присвоить переменной a:

>>> a = d
>>> c
([1, 2], {1: 'i', 2: 'j'})

Но не тут то было! Переменная a стала указывать на другой объект, в то время как в кортеже сохранилась ссылка на первоначальный список. Теперь его изменить через переменную a уже нельзя. Только непосредственно обращаясь к кортежу:

>>> c[0].append(4)
>>> c
([1, 2, 4], {1: 'i', 2: 'j'})

Если бы переменная a не была «перезаписана», то есть не стала ссылаться на другой список, то кортеж можно было бы менять через нее:

>>> a = [1, 2]
>>> b = {1: 'i', 2: 'j'}
>>> c = (a, b)
>>> a[0] = 3
>>> c
([3, 2], {1: 'i', 2: 'j'})

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