Проверить поведение изменения списка внутри кортежа

28. Проверить поведение изменения списка внутри кортежа

Условие задачи Проверить поведение кортежа, внутри которого находится изменяемый список: сначала список изменяется через append(), затем выполняется попытка изменить элемент кортежа через оператор +=. Что вывежет код?

data = (['1'], '2')
data[0].append('3')
data[0] += ['4']

print(data)
Спойлеры к решению
Подсказки
  • Кортеж tuple сам по себе неизменяемый.
  • Но внутри кортежа может лежать изменяемый объект, например список.
  • data[0].append('3') изменяет список внутри кортежа, это разрешено.
  • data[0] += ['4'] сначала изменяет список через __iadd__, а потом пытается записать результат обратно в элемент кортежа.
  • Запись обратно в кортеж запрещена, поэтому будет TypeError.
  • До ошибки список уже успеет измениться.
Решение
data = (['1'], '2')
data[0].append('3')
data[0] += ['4']

print(data)

Код завершится ошибкой:

TypeError: 'tuple' object does not support item assignment

Строка:

print(data)

не выполнится, потому что программа остановится на строке:

data[0] += ['4']

Разбор по шагам:

data = (['1'], '2')

Создаётся кортеж, внутри которого первым элементом лежит список:

(['1'], '2')

Дальше:

data[0].append('3')

Это разрешено, потому что меняется не сам кортеж, а список внутри него. После этой строки состояние будет таким:

(['1', '3'], '2')

Дальше:

data[0] += ['4']

Для списка операция += работает как изменение списка на месте. Список успеет стать таким:

['1', '3', '4']

Но после этого Python пытается присвоить обновлённый список обратно в data[0]. А кортеж нельзя изменять по индексу, поэтому возникает ошибка:

TypeError: 'tuple' object does not support item assignment

Если бы ошибку перехватили через try/except, то можно было бы увидеть, что список внутри кортежа уже изменился:

data = (['1'], '2')

try:
    data[0].append('3')
    data[0] += ['4']
except TypeError:
    print(data)

Результат:

(['1', '3', '4'], '2')

Итог: исходный код ничего не выведет через print(data), потому что до него выполнение не дойдёт. Вместо этого будет ошибка TypeError, но внутренний список уже будет изменён.