Python: создать функцию для изменения списка по ссылке не значение
Я выполняю критическую работу с Python и хочу создать функцию, которая удаляет несколько элементов из списка, если они отвечают определенным критериям. Я бы предпочел не создавать копии списка, потому что он заполнен множеством действительно больших объектов.
Функциональность, которую я хочу реализовать:
def listCleanup(listOfElements):
i = 0
for element in listOfElements:
if(element.meetsCriteria()):
del(listOfElements[i])
i += 1
return listOfElements
myList = range(10000)
myList = listCleanup(listOfElements)
Я не знаком с низкоуровневыми работами Python. Передано ли myList по значению или по ссылке?
Как я могу сделать это быстрее?
Возможно ли каким-либо образом расширить класс списка и реализовать listCleanup() в пределах этого?
myList = range(10000)
myList.listCleanup()
Спасибо -
Джонатан
Ответы
Ответ 1
Python передает все одинаково, но называть его "по значению" или "по ссылке" не очистят все, так как семантика Python отличается от языков, для которых эти термины обычно применяются. Если бы я описал это, я бы сказал, что все прохождение было по значению и что значение было ссылкой на объект. (Вот почему я не хотел этого говорить!)
Если вы хотите отфильтровать некоторые материалы из списка, вы создадите новый список
foo = range(100000)
new_foo = []
for item in foo:
if item % 3 != 0: # Things divisble by 3 don't get through
new_foo.append(item)
или, используя синтаксис понимания списка
new_foo = [item for item in foo if item % 3 != 0]
Python не будет копировать объекты в списке, но оба foo
и new_foo
будут ссылаться на одни и те же объекты. (Python никогда не копирует какие-либо объекты).
Вы предложили, чтобы у вас были проблемы с производительностью в отношении этой операции. Использование повторяющихся операторов del
из старого списка приведет к тому, что код, который будет менее идиоматичным и более запутанным, не будет иметь дело с ним, но он будет вводить квадратичную производительность, потому что весь список должен быть перетасован каждый раз.
Чтобы определить производительность:
-
Загрузите и запустите. Вы не можете понять, какова ваша производительность, если у вас нет кода. Это также скажет вам, нужна ли вам скорость или пространство, для которых вы должны оптимизировать; вы упоминаете о проблемах как в своем коде, но зачастую оптимизация включает в себя получение одного за счет другого.
-
Профиль. Вы можете использовать инструменты stdlib для производительности во времени. Существуют различные сторонние профили памяти, которые могут быть несколько полезными, но с ними не так приятно работать.
-
Измерьте. Time или память репродукции, когда вы внесете изменения, чтобы увидеть, если изменение делает улучшение, и если да, то какое это улучшение.
-
Чтобы сделать ваш код более чувствительным к памяти, вам часто понадобится изменение парадигмы в том, как вы храните свои данные, а не микрооптимизации, например, не создавая второй список для фильтрации. (То же самое верно и для времени, на самом деле: переход на лучший алгоритм почти всегда даст лучшее ускорение. Однако сложнее обобщать оптимизацию скорости).
Некоторые общие сдвиги парадигмы для оптимизации потребления памяти в Python включают
-
Использование генераторов. Генераторы - это ленивые итерации: они не загружают весь список в память сразу, они выясняют, что их следующие предметы находятся на ходу. Чтобы использовать генераторы, приведенные выше фрагменты выглядят как
foo = xrange(100000) # Like generators, xrange is lazy
def filter_divisible_by_three(iterable):
for item in foo:
if item % 3 != 0:
yield item
new_foo = filter_divisible_by_three(foo)
или, используя синтаксис выражения генератора,
new_foo = (item for item in foo if item % 3 != 0)
-
Используя numpy
для однородных последовательностей, особенно тех, которые численно-математически. Это также может ускорить код, который выполняет много векторных операций.
-
Хранение данных на диск, например, в базе данных.
Ответ 2
В Python списки всегда передаются по ссылке.
Размер объектов в списке не влияет на производительность списков, поскольку в списках хранятся только ссылки на объекты. Однако количество элементов в списке влияет на производительность некоторых операций - например, удаление элемента, который является O (n).
Как указано, listCleanup является наихудшим O (n ** 2), так как у вас есть операция O (n) del в цикле, который потенциально является O (n).
Если порядок элементов не имеет значения, вы можете использовать встроенный set
тип вместо списка. set
имеет O (1) удаления и вставки. Тем не менее, вам нужно будет гарантировать, что ваши объекты будут неизменными и хешируемыми.
В противном случае вам лучше воссоздать список. Это O (n), и ваш алгоритм должен быть как минимум O (n), так как вам нужно изучить каждый элемент. Вы можете отфильтровать список в одной строке следующим образом:
listOfElements[:] = [el for el in listOfElements if el.MeetsCriteria()]
Ответ 3
Похоже на преждевременную оптимизацию. Вы должны попытаться лучше понять, как работает python, прежде чем пытаться оптимизировать.
В этом конкретном случае вам не нужно беспокоиться о размере объекта. Копирование списка с использованием понимания списка или среза будет выполнять только поверхностную копию (копировать ссылки на объекты, даже если этот термин не очень хорошо подходит для python). Но количество элементов в списке может иметь значение, поскольку del - O (n). Могут быть другие решения, такие как замена элемента None или обычного объекта Null или использование другой структуры данных, такой как набор или словарь, где стоимость удаления элемента намного ниже.
Ответ 4
Я не думаю, что кто-то упоминал об использовании фильтра. Поскольку ответы были получены от уважаемых людей, я уверен, что я тот, кто чего-то не хватает. Может кто-нибудь объяснить, что было бы не так:
new_list = filter(lambda o: o.meetsCriteria(), myList)
Ответ 5
Изменение структуры данных по мере того, как вы выполняете итерацию, это похоже на то, как стрелять в ногу... Итерация терпит неудачу. вы можете также взять советы других и просто создать новый список:
myList = [element for element in listOfElements if not element.meetsCriteria()]
старый список - если нет других ссылок на него - будет освобожден и память будет исправлена. еще лучше, даже не делайте копию списка. измените приведенное выше выражение на генератор для более удобной для памяти версии:
myList = (element for element in listOfElements if not element.meetsCriteria())
весь доступ к объекту Python осуществляется по ссылке. объекты создаются, а переменные - только ссылки на эти объекты. однако, если кто-то хотел задать вопрос пуриста, "какой тип семантики вызова использует Python, вызов по ссылке или позывной?" ответ должен быть: "Ни то, ни другое". причина в том, что вызывающие соглашения менее важны для Python, чем тип объекта.
если объект изменен, он может быть изменен независимо от того, в какой области вы находитесь... до тех пор, пока у вас есть действительная ссылка на объект, объект может быть изменен. если объект является неизменным, то этот объект не может быть изменен независимо от того, где вы находитесь или какая у вас ссылка.
Ответ 6
Удаление элементов списка in-situ возможно, но не путем перехода вперед по списку. Ваш код просто не работает - поскольку список сжимается, вы можете пропустить изучение элементов. Вам нужно идти назад, так что сокращающаяся часть позади вас, с довольно ужасным кодом. Прежде чем я покажу вам это, есть некоторые предварительные соображения:
Во-первых, как этот мусор попал в список? Профилактика лучше, чем лечение.
Во-вторых, сколько элементов в списке и какой процент, вероятно, потребуется удалить? Чем выше процент, тем больше вероятность того, что лучше создать новый список.
Хорошо, если вы все еще хотите сделать это in-situ, созерцайте это:
def list_cleanup_fail(alist, is_bad):
i = 0
for element in alist:
print "i=%d alist=%r alist[i]=%d element=%d" % (i, alist, alist[i], element)
if is_bad(element):
del alist[i]
i += 1
def list_cleanup_ok(alist, is_bad):
for i in xrange(len(alist) - 1, -1, -1):
print "i=%d alist=%r alist[i]=%d" % (i, alist, alist[i])
if is_bad(alist[i]):
del alist[i]
def is_not_mult_of_3(x):
return x % 3 != 0
for func in (list_cleanup_fail, list_cleanup_ok):
print
print func.__name__
mylist = range(11)
func(mylist, is_not_mult_of_3)
print "result", mylist
и вот результат:
list_cleanup_fail
i=0 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=0 element=0
i=1 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=1 element=1
i=2 alist=[0, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=3 element=3
i=3 alist=[0, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=4 element=4
i=4 alist=[0, 2, 3, 5, 6, 7, 8, 9, 10] alist[i]=6 element=6
i=5 alist=[0, 2, 3, 5, 6, 7, 8, 9, 10] alist[i]=7 element=7
i=6 alist=[0, 2, 3, 5, 6, 8, 9, 10] alist[i]=9 element=9
i=7 alist=[0, 2, 3, 5, 6, 8, 9, 10] alist[i]=10 element=10
result [0, 2, 3, 5, 6, 8, 9]
list_cleanup_ok
i=10 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=10
i=9 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alist[i]=9
i=8 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alist[i]=8
i=7 alist=[0, 1, 2, 3, 4, 5, 6, 7, 9] alist[i]=7
i=6 alist=[0, 1, 2, 3, 4, 5, 6, 9] alist[i]=6
i=5 alist=[0, 1, 2, 3, 4, 5, 6, 9] alist[i]=5
i=4 alist=[0, 1, 2, 3, 4, 6, 9] alist[i]=4
i=3 alist=[0, 1, 2, 3, 6, 9] alist[i]=3
i=2 alist=[0, 1, 2, 3, 6, 9] alist[i]=2
i=1 alist=[0, 1, 3, 6, 9] alist[i]=1
i=0 alist=[0, 3, 6, 9] alist[i]=0
result [0, 3, 6, 9]
Ответ 7
Просто, чтобы быть ясным:
def listCleanup(listOfElements):
i = 0
for element in listOfElements:
if(element.meetsCriteria()):
del(listOfElements[i])
i += 1
return listOfElements
myList = range(10000)
myList = listCleanup(listOfElements)
совпадает с
def listCleanup(listOfElements):
i = 0
for element in listOfElements:
if(element.meetsCriteria()):
del(listOfElements[i])
i += 1
myList = range(10000)
listCleanup(listOfElements)
?