Использование памяти Python Slice Assignment - PullRequest
21 голосов
/ 09 февраля 2011

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

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 Ответ

45 голосов
/ 09 февраля 2011

Строка

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

Мораль, по-видимому, заключается в том, что самый простой подход является лучшим в любом отношении.

...