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()

Спасибо-

Jonathan

14
задан Jonathan 5 May 2010 в 01:26
поделиться

7 ответов

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 для повышения производительности во времени. Существуют различные сторонние профилировщики памяти, которые могут быть полезны, но работать с ними не так приятно.

  • Мера. Время или перепрофилирование памяти, когда вы вносите изменение, чтобы увидеть, дает ли изменение улучшение, и если да, то что это за улучшение.

  • Чтобы сделать ваш код более чувствительным к памяти, вам часто требуется смена парадигмы в том, как вы храните свои данные, а не микрооптимизации, такие как отказ от создания второго списка для фильтрации. (На самом деле то же самое верно и для времени: переход на лучший алгоритм почти всегда дает лучшее ускорение. Однако сложнее сделать обобщения об оптимизации скорости).

    Некоторые общие изменения парадигмы для оптимизации потребления памяти в Python включают

    1. Использование генераторов.Генераторы - это ленивые итерации: они не загружают в память сразу весь список, они на лету выясняют, что им делать дальше. Для использования генераторов приведенные выше фрагменты будут выглядеть так:

       foo = xrange (100000) # Как и генераторы, xrange ленив 
      def filter_divisible_by_three (iterable): 
      для элемента в foo: {{1 }} если элемент% 3! = 0: 
      yield item 
       
      new_foo = filter_divisible_by_three (foo) 
       

      или, используя синтаксис выражения генератора,

       new_foo = (item for item in foo if item% 3! = 0) 
       
    2. Использование numpy для однородных последовательностей, особенно тех, которые являются числовой математикой. Это также может ускорить код, выполняющий множество векторных операций.

    3. Хранение данных на диске, например в базе данных.

29
ответ дан 1 December 2019 в 06:53
поделиться

изменять структуру данных во время итерации по ней - все равно что выстрелить себе в ногу... итерация не работает. с таким же успехом можно последовать совету других и просто создать новый список:

myList = [element for element in listOfElements if not element.meetsCriteria()]

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

myList = (element for element in listOfElements if not element.meetsCriteria())

весь доступ к объектам в Python осуществляется по ссылке. Объекты создаются, а переменные являются просто ссылками на эти объекты. Однако, если кто-то захочет задать пуристский вопрос: "Какой тип семантики вызова использует Python, вызов по ссылке или вызов по значению?", то ответ будет: "Ни то, ни другое... и то, и другое". Причина в том, что соглашения о вызове менее важны для Python, чем тип объекта.

если объект является изменяемым, его можно изменить независимо от того, в какой области видимости вы находитесь... пока у вас есть действительная ссылка на объект, объект можно изменить. если объект является неизменяемым, то этот объект нельзя изменить независимо от того, где вы находитесь и какая у вас ссылка.

1
ответ дан 1 December 2019 в 06:53
поделиться

Удаление элементов списка на месте возможно, но не при переходе вперед по списку. Ваш код просто не работает - поскольку список уменьшается, вы можете пропустить исследуемые элементы. Вам нужно идти назад, чтобы сокращающаяся часть была позади вас, с довольно ужасным кодом. Прежде чем я покажу вам это, есть несколько предварительных соображений:

Во-первых, как этот мусор попал в список? Профилактика лучше, чем лечение.

Во-вторых, сколько элементов в списке, и какой процент из них, вероятно, нуждается в удалении? Чем выше процент, тем больше вероятность того, что лучше создать новый список.

Хорошо, если вы все еще хотите сделать это на месте, подумайте об этом:

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]
1
ответ дан 1 December 2019 в 06:53
поделиться

Для ясности:

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)

?

0
ответ дан 1 December 2019 в 06:53
поделиться

В Python списки всегда передаются по ссылке.

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

Как написано, listCleanup - это наихудший вариант O (n ** 2), поскольку у вас есть операция O (n) del внутри цикла, которая потенциально сама является O (n).

Если порядок элементов не имеет значения, вы можете использовать встроенный набор типов вместо списка. Набор имеет O (1) удалений и вставок. Однако вам нужно будет убедиться, что ваши объекты неизменяемы и хешируемы.

В противном случае вам лучше воссоздать список. Это O (n), и ваш алгоритм должен быть как минимум O (n), поскольку вам нужно проверить каждый элемент. Вы можете отфильтровать список в одну строку следующим образом:

listOfElements[:] = [el for el in listOfElements if el.MeetsCriteria()]
6
ответ дан 1 December 2019 в 06:53
поделиться

Похоже на преждевременную оптимизацию. Вам следует попытаться лучше понять, как работает python, прежде чем пытаться оптимизировать.

В данном конкретном случае вам не нужно беспокоиться о размере объекта. Копирование списка с использованием list comprehension или slice будет выполнять только поверхностное копирование (копирование ссылок на объекты, даже если этот термин не очень хорошо применим к python). Но количество элементов в списке может иметь значение, потому что del - это O(n). Могут быть и другие решения, например, заменить элемент на None или обычный объект Null, или использовать другую структуру данных, например, набор или словарь, где стоимость удаления элемента намного ниже.

2
ответ дан 1 December 2019 в 06:53
поделиться

Я не думаю, что кто-то упоминал о реальном использовании фильтра. Поскольку многие ответы исходили от уважаемых людей, я уверен, что я единственный, кто что-то упускает. Не мог бы кто-нибудь объяснить, что не так с этим:

new_list = filter(lambda o: o.meetsCriteria(), myList)

2
ответ дан 1 December 2019 в 06:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: