Страницы

Поиск по вопросам

понедельник, 4 февраля 2019 г.

TypeError при изменении списка оператором += внутри кортежа

Известно, что оператор += для списков примерно эквивалентен методу extend в том смысле, что при его использовании не создаётся копия списка, а расширяется текущий.
В связи с этим возникает вопрос: почему следующая конструкция бросает исключение TypeError
t = ([1], ) # кортеж из одного элемента, являющегося списком try: t[0] += [2] except TypeError as e: print(e) # 'tuple' object does not support item assignment print(t) # ([1, 2],) -- и сам список изменился!
Ведь мы не изменяем ссылки, сохранённые в кортеже и следующий код работает без проблем:
t = ([1], ) t[0].extend([2]) print(t) # ([1, 2],)
Почему такое поведение?


Ответ

Дело в том, что операция += всё же содержит в себе присваивание, хоть и выполняет изменение "на месте". Оператор += реализуется через метод __iadd__, и код
t[0] += [2]
эквивалентен следующей записи
res = t[0].__iadd__([1]) t[0] = res # TypeError: 'tuple' object does not support item assignment
Хоть ссылка на самом деле и не меняется:
print(res is t[0]) # True
но присваивание есть, и это мешает выполниться подобному оператору без ошибок.
Способ с использованием метода, очевидно, такой проблемы иметь не может.

Можно удостовериться, что присваивание действительно есть. Для этого рассмотрим более простой случай:
a = [1] a += [2]
Посмотрим, какой код генерируется в Python 3 для выполнения второй строки (операции могут отличаться, но суть будет примерно та же):
import dis dis.dis('a += [2]') Расшифровка действия со стеком 1 0 LOAD_NAME 0 (a) push [1] (переменная "a") 3 LOAD_CONST 0 (2) push 2 6 BUILD_LIST 1 pop (2), push [2] 9 INPLACE_ADD pop ([2]), pop ([1]), push [1, 2] 10 STORE_NAME 0 (a) pop ([1, 2] -> a) ...
Именно попытка выполнить операцию STORE_NAME (более точно, STORE_SUBSCR, для кортежа) вызывает ошибку в случае, описанном в вопросе. Важно понимать, что операция INPLACE_ADD выполняет добавление элементов "на месте", т.е. на стеке в итоге окажется тот же объект.

Комментариев нет:

Отправить комментарий