Известно, что оператор += для списков примерно эквивалентен методу 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 выполняет добавление элементов "на месте", т.е. на стеке в итоге окажется тот же объект.
Комментариев нет:
Отправить комментарий