Использование памяти назначения питона 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() для преобразования итеративного (в данном случае генератора) сначала в кортеж, который затем копируется в старый список, Кортеж использует тот же объем памяти, что и список, поэтому использование выражения генератора в основном такое же, как использование понимания списка в этом случае. Во время последней копии элементы исходного списка повторно используются.
Мораль, похоже, самая лучшая в любом отношении.