Использование памяти назначения питона Python

Я прочитал здесь комментарий о переполнении стека, что при изменении списков более эффективно выполнять назначение slice. Например,

a[:] = [i + 6 for i in a]

должно быть больше памяти, чем

a = [i + 6 for i in a]

потому что первая заменяет элементы в существующем списке, в то время как последний создает новый список и переупорядочивает a в этот новый список, оставляя старый a в памяти до тех пор, пока он не будет собран в мусор. Бенчмаркинг двух для скорости, последний немного быстрее:

$ python -mtimeit -s 'a = [1, 2, 3]' 'a[:] = [i + 6 for i in a]'
1000000 loops, best of 3: 1.53 usec per loop
$ python -mtimeit -s 'a = [1, 2, 3]' 'a = [i + 6 for i in a]'
1000000 loops, best of 3: 1.37 usec per loop

Это то, чего я ожидал бы, поскольку переупорядочение переменной должно быть быстрее, чем замена элементов в списке. Однако я не могу найти официальную документацию, которая поддерживает заявку на использование памяти, и я не уверен, как ее оценивать.

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

Может ли кто-нибудь показать окончательно (с эталонным или официальным документом), какой из двух методов более эффективен с точки зрения памяти/который является предпочтительным?

Спасибо заранее.

Ответ 1

Линия

a[:] = [i + 6 for i in a]

не сохранит никакой памяти. Сначала Python оценивает правую сторону, как указано в документации по языку:

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

В данном случае единственным результирующим объектом будет новый список, а единственная цель в целевом списке будет a[:].

Мы могли бы заменить понимание списка выражением генератора:

a[:] = (i + 6 for i in a)

Теперь правая сторона вычисляет генератор вместо списка. Бенчмаркинг показывает, что это все еще медленнее, чем наивный

a = [i + 6 for i in a]

Так действительно ли выражение генератора сохраняет память? На первый взгляд, вы можете подумать, что это так. Но вникание в исходного кода функции list_ass_slice() показывает, что это не так. Строка

v_as_SF = PySequence_Fast(v, "can only assign an iterable");

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

Мораль, похоже, самая лучшая в любом отношении.