tuple‘а в list‘е.Все мы знаем, что в Python есть тип данных
list:
a = [] a.append(2)
list — это просто массив. Он позволяет добавлять, удалять и изменять элементы. Также он поддерживает много разных интересных операторов. Например, оператор += для добавления элементов в list. += меняет текущий список, а не создает новый. Это хорошо видно тут:
>>> a = [1,2] >>> id(a) 4543025032 >>> a += [3,4] >>> id(a) 4543025032
В Python есть еще один замечательный тип данных: tuple — неизменяемая коллекция. Она не позволяет добавлять, удалять или менять элементы:
>>> a = (1,2) >>> a[1] = 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
При использовании оператора += создается новый tuple:
>>> a = (1,2) >>> id(a) 4536192840 >>> a += (3,4) >>> id(a) 4542883144
Внимание, вопрос: что сделает следующий код?
a = (1,2,[3,4]) a[2] += [4,5]
Варианты:
- Добавятся элементы в список.
- Вылетит исключение о неизменяемости tuple.
- И то, и другое.
- Ни то, ни другое.
Запишите свой ответ на бумажке и давайте сделаем небольшую проверку:
>>> a = (1,2,[3,4]) >>> a[2] += [4,5] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
Ну что же! Вот мы и разобрались! Правильный ответ — 2. Хотя, подождите минутку:
>>> a (1, 2, [3, 4, 4, 5])
На самом деле правильный ответ — 3. То есть и элементы добавились, и исключение вылетело — wat?!

Давайте разберемся, почему так происходит. И поможет нам в этом замечательный модуль dis:
import dis def foo(): a = (1,2,[3,4]) a[2] += [4,5] dis.dis(foo) 2 0 LOAD_CONST 1 (1) 3 LOAD_CONST 2 (2) 6 LOAD_CONST 3 (3) 9 LOAD_CONST 4 (4) 12 BUILD_LIST 2 15 BUILD_TUPLE 3 18 STORE_FAST 0 (a) 3 21 LOAD_FAST 0 (a) 24 LOAD_CONST 2 (2) 27 DUP_TOP_TWO 28 BINARY_SUBSCR 29 LOAD_CONST 4 (4) 32 LOAD_CONST 5 (5) 35 BUILD_LIST 2 38 INPLACE_ADD 39 ROT_THREE 40 STORE_SUBSCR 41 LOAD_CONST 0 (None) 44 RETURN_VALUE
Первый блок отвечает за построение tuple‘а и его сохранение в переменной a. Дальше начинается самое интересное:
21 LOAD_FAST 0 (a) 24 LOAD_CONST 2 (2)
Загружаем в стек указатель на переменную a и константу 2.
27 DUP_TOP_TWO
Дублируем их и кладем в стек в том же порядке.
28 BINARY_SUBSCR
Этот оператор берет верхний элемент стека (TOS) и следующий за ним (TOS1). И записывает на вершину стека новый элемент TOS = TOS1[TOS]. Так мы убираем из стека два верхних значения и кладем в него ссылку на второй элемент tuple‘а (наш массив).
29 LOAD_CONST 4 (4) 32 LOAD_CONST 5 (5) 35 BUILD_LIST 2
Строим список из элементов 4 и 5 и кладем его на вершину стека:
38 INPLACE_ADD
Применяем += к двум верхним элементам стека (Важно! Это два списка! Один состоит из 4 и 5, а другой взяты из tuple). Тут всё нормально, инструкция выполняется без ошибок. Поскольку += изменяет оригинальный список, то список в tuple‘е уже поменялся (именно в этот момент).
39 ROT_THREE 40 STORE_SUBSCR
Тут мы меняем местами три верхних элемента стека (там живет tuple, в нём индекс массива и новый массив) и записываем новый массив в tuple по индексу. Тут-то и происходит исключение!
Ну что же, вот и разобрались! На самом деле список менять можно, а падает всё на операторе =.
Давайте напоследок разберемся, как переписать этот код без исключений. Как мы уже поняли, надо просто убрать запись в tuple. Вот парочка вариантов:
>>> a = (1,2,[3,4]) >>> b = a[2] >>> b += [4,5] >>> a (1, 2, [3, 4, 4, 5])
>>> a = (1,2,[3,4]) >>> a[2].extend([4,5]) >>> a (1, 2, [3, 4, 4, 5])
Спасибо всем, кто дочитал до конца. Надеюсь, было интересно =)
ссылка на оригинал статьи https://habr.com/ru/company/domclick/blog/506138/
Добавить комментарий