TLDR: Вы не можете изменить список на месте в Python, не выполняя какой-либо цикл самостоятельно или не используя внешнюю библиотеку, но в любом случае, вероятно, не стоит пытаться по соображениям экономии памяти (преждевременная оптимизация).Возможно, стоит попробовать использовать функцию Python map
и iterables , которые вообще не сохраняют результаты, а вычисляют их по требованию.
Существует несколькоспособы применения модифицирующей функции по списку (т.е. выполнение map ) в Python, каждый из которых имеет различные значения для производительности и побочных эффектов:
Новый список
Это то, что фактически делают обе опции в вопросе.
[some_function(x) for x in _list]
Это создает новый список со значениями, заполняемыми по порядку, путем запуска some_function
для соответствующего значения в _list
.Затем он может быть назначен в качестве замены для старого списка (_list = ...
) или его значения заменяют старые значения, сохраняя при этом ссылку на объект (_list[:] = ...
).Первое назначение происходит в постоянном времени и памяти (в конце концов, это просто замена ссылки), где второе должно выполнять итерацию по списку для выполнения назначения, которое является линейным по времени.Тем не менее, время и память, необходимые для создания списка, в первую очередь линейны, поэтому _list = ...
строго быстрее, чем _list[:] = ...
, но все равно линейно по времени и памяти, поэтому на самом деле это не имеет значения.
С функциональной точки зрения два варианта этого варианта имеют потенциально опасные последствия из-за побочных эффектов._list = ...
оставляет старый список висящим вокруг, что не опасно, но означает, что память не может быть освобождена.Любой другой код, ссылающийся на _list
, сразу же после изменения получит новый список, что, вероятно, снова хорошо, но может вызвать незначительные ошибки, если вы не обращаете на это внимания.list[:] = ...
изменяет существующий список, так что любой, у кого есть ссылка на него, будет менять значения у себя под ногами.Имейте в виду, что если список когда-либо возвращается из метода или выходит за пределы области действия, в которой вы работаете, вы можете не знать, кто еще его использует.
Суть в том, что оба эти методалинейны как по времени, так и по памяти, потому что они копируют список, и имеют побочные эффекты, которые необходимо учитывать.
Замена на месте
Другая возможность, на которую намекают вВопрос об изменении ценностей на месте.Это позволит сэкономить на памяти копию списка.К сожалению, в Python нет встроенной функции для этого, но это не сложно сделать вручную (как предлагается в различных ответах на этот вопрос ).
for i in range(len(_list)):
_list[i] = some_function(_list[i])
в отношении сложностиэто все еще имеет линейную временную стоимость выполнения вызовов до some_function
, но экономит дополнительную память для хранения двух списков.Если на него не ссылаются в другом месте, каждый элемент в старом списке можно собирать мусором, как только он будет заменен.
Функционально это, возможно, самый опасный вариант, поскольку список находится в несогласованном состоянии.во время звонков на some_function
.Пока some_function
не ссылается на список (который в любом случае был бы довольно ужасным дизайном), он должен быть таким же безопасным, как и разнообразные решения new list .Он также имеет те же опасности, что и решение _list[:] = ...
, приведенное выше, поскольку исходный список изменяется.
Iterables
Функция Python 3 map
действует на итерации, а не насписки.Списки являются итерируемыми, но итерируемые не всегда являются списками, и когда вы вызываете map(some_function, _list)
, он сразу не запускает some_function
.Это происходит только тогда, когда вы пытаетесь потреблять итерируемого каким-либо образом.
list(map(some_other_function, map(some_function, _list)))
Приведенный выше код применяет some_function
, затем some_other_function
к элементам _list
и помещает результаты в новый список, но, что важно, он вообще не сохраняет промежуточное значение.Если вам нужно только выполнить итерацию результатов или вычислить максимум из них или какую-либо другую функцию уменьшение , вам не нужно ничего сохранять по пути.
Этот подход подходитс функциональной парадигмой программирования , которая препятствует побочным эффектам (часто источником хитрых ошибок).Поскольку исходный список никогда не изменяется, даже если some_function
ссылался на него, кроме того, который он рассматривал в то время (что, кстати, все еще не является хорошей практикой), текущее на него не повлияло бы.map .
В стандартной библиотеке Python есть множество функций для работы с итераторами и генераторами itertools
.
Замечание по распараллеливанию
Очень заманчиво подумать о том, как можно распараллелить выполнение карты в списке, чтобы уменьшить линейную временную стоимость вызовов до some_function
, поделив ее между несколькими процессорами.В принципе, все эти методы могут быть распараллелены, но Python делает это довольно сложным.Один из способов сделать это - использовать библиотеку multiprocessing
, которая имеет функцию map
. Этот ответ описывает, как его использовать.