Как удалить элементы из списка во время итерации?

Я перебираю список кортежей в Python и пытаюсь удалить их, если они соответствуют определенным критериям.

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

что я должен использовать вместо code_to_remove_tup? Я не могу понять, как удалить элемент таким образом.

20 ответов


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

somelist = [x for x in somelist if not determine(x)]

или, назначив срез somelist[:], вы можете мутировать существующий список, чтобы содержать только элементы, которые вы хотите:

somelist[:] = [x for x in somelist if not determine(x)]

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

вместо понимания вы также можете использовать itertools. в Python 2:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

или в Python 3:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

ответы, предлагающие понимание списка, почти правильны-за исключением того, что они строят совершенно новый список, а затем дают ему то же имя, что и старый список, они не изменяют старый список на месте. Это отличается от того, что вы будете делать путем выборочного удаления, как в предложении @Lennart-это быстрее, но если ваш список доступен через несколько ссылок, тот факт, что вы просто повторно устанавливаете одну из ссылок и не изменяете сам объект списка, может привести к тонкому, ужасные жуки.

к счастью, очень легко получить как скорость понимания списка, так и требуемую семантику изменения на месте-просто код:

somelist[:] = [tup for tup in somelist if determine(tup)]

обратите внимание на тонкую разницу с другими ответами: этот не присваивает barename - он присваивает фрагменту списка, который просто оказывается всем списком, тем самым заменяя list содержание внутри того же объекта списка Python, а не просто установили один ссылка (от предыдущего объекта списка к новому объекту списка), как и другие ответы.


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

например (зависит от типа списка):

for tup in somelist[:]:
    etc....

пример:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]

for i in xrange(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

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


ваш лучший подход для такого примера будет понимание

somelist = [tup for tup in somelist if determine(tup)]

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

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

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

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)

для тех, кто любит функциональное программирование:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

или

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))

на официальный учебник Python 2 4.2. "за высказывания" говорит:

Если вам нужно изменить последовательность, которую вы повторяете, находясь внутри цикла (например, для дублирования выбранных элементов), рекомендуется сначала сделать копию. Итерация по последовательности не подразумевает создания копии. Нотация среза делает это особенно удобным:

>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

что было предложено на: https://stackoverflow.com/a/1207427/895245

на Python 2 документация 7.3. "The for statement" дает тот же совет:

Примечание: существует тонкость, когда последовательность изменяется циклом (это может произойти только для изменяемых последовательностей, т. е. списков). Внутренний счетчик используется для отслеживания того, какой элемент используется следующим, и это увеличивается на каждой итерации. Когда этот счетчик достигнет длины последовательности цикл завершается. Это означает, что если набор удаляет текущий (или предыдущий) элемент из последовательности, следующий элемент будет пропущен (так как он получает индекс текущего элемента, который уже обработан). Аналогично, если набор вставляет элемент в последовательность перед текущим элементом, текущий элемент будет обработан снова в следующий раз через цикл. Это может привести к неприятным ошибкам, которых можно избежать, сделав временную копию, используя срез всей последовательности, например,

for x in a[:]:
    if x < 0: a.remove(x)

может ли Python сделать это лучше?

похоже, что этот конкретный API Python может быть улучшен. Сравните его, например, с его Java-аналогом ListIterator, что делает кристально ясным, что вы не можете изменить список, который повторяется, кроме как с самим итератором, и дает вам эффективные способы сделать это без копирования списка. Давай, питон!


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

так:

for item in originalList:
   if (item != badValue):
        newList.append(item)

и чтобы избежать необходимости перекодировать весь проект с новым именем списков:

originalList[:] = newList

примечание из документации Python:

копировать.копировать(x) Верните пустую копию x.

копировать.deepcopy(x) Верните глубокую копию x.


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

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

чего я не знаю, так это насколько эффективна пара удалений по сравнению с копированием большого списка. Пожалуйста, прокомментируйте, если у вас есть понимание.


этот ответ был написан в ответ на вопрос, который был отмечен как дубликат: удаление координат из списка на python

в вашем коде есть две проблемы:

1) при использовании remove () вы пытаетесь удалить целые числа, тогда как вам нужно удалить кортеж.

2) цикл for пропустит элементы в вашем списке.

давайте рассмотрим, что происходит, когда мы выполняем код:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

первая проблема заключается в том, что вы передаете как "a", так и " b " для удаления (), но remove() принимает только один аргумент. Так как мы можем удалить() для правильной работы с вашим списком? Нам нужно выяснить, что представляет собой каждый элемент вашего списка. В этом случае каждый из них является кортежем. Чтобы увидеть это, давайте обратимся к одному элементу списка (индексирование начинается с 0):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

Аха! Каждый элемент L1 на самом деле является кортежем. Вот к чему нам нужно перейти. снимать.)( Кортежи в python очень просты, они просто заключают значения в круглые скобки. "а, б" - не Кортеж, а "(а, б)" - кортеж. Поэтому мы модифицируем ваш код и запускаем его снова:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

этот код работает без каких-либо ошибок, но давайте посмотрим на список, который он выводит:

L1 is now: [(1, 2), (5, 6), (1, -2)]

почему (1,-2) еще в вашем списке? Оказывается, изменение списка при использовании цикла для итерации по нему-очень плохая идея без особой заботы. Причина, по которой (1, -2) остается в список заключается в том, что расположения каждого элемента в списке изменялись между итерациями цикла for. Давайте посмотрим, что произойдет, если мы подадим вышеуказанный код в более длинный список:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

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

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

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

однако выход будет идентичен предыдущему:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

это потому, что когда мы создали L2, python фактически не создал новый объект. Вместо этого он просто ссылался на L2 на тот же объект, что и L1. Мы можем проверить это с помощью "Есть", которое отличается от просто "равно" (==).

>>> L2=L1
>>> L1 is L2
True

мы можем сделать true copy с помощью копирования.копия.)( Тогда все работает так, как ожидалось:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

наконец, есть одно более чистое решение, чем сделать совершенно новую копию L1. Функция reversed ():

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

к сожалению, я не могу адекватно описать, как работает reversed (). Он возвращает объект' listreverseiterator ' при передаче ему списка. Для практических целей вы можете думать об этом как о создании обратной копии своего аргумента. Это решение я рекомендовать.


Если вы хотите сделать что-нибудь еще во время итерации, может быть неплохо получить как индекс (который гарантирует, что вы сможете ссылаться на него, например, если у вас есть список диктов), так и фактическое содержимое элемента списка.

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumerate дает вам доступ к элементу, а индекс одновременно. reversed Так что показатели, которые вы собираетесь удалить, не изменить тебя.


вы можете использовать filter() доступный как встроенный.

для более подробной информации регистрация здесь


вы можете попробовать-цикл в обратном направлении, поэтому для some_list вы сделаете что-то вроде:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

таким образом, индекс выровнен и не страдает от обновлений списка (независимо от того, поп-элемент cur или нет).


одно возможное решение, полезное, если вы хотите не только удалить некоторые вещи, но и сделать что-то со всеми элементами в одном цикле:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1

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

``

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

``


TLDR:

я написал библиотеку, которая позволяет вам сделать это:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

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

должен работать на всех изменяемых последовательностей не только списки.


ответ:

Edit: последний пример кода в этом ответе дает вариант использования для почему иногда может потребоваться изменить список на месте, а не использовать понимание списка. Первая часть ответов служит учебником как массив может быть изменен на месте.

решение следует из этой ответ (на вопрос) от сендерле. Это объясняет, как индекс массива обновляется при итерации по измененному списку. Решение ниже предназначено для правильного отслеживания индекса массива, даже если список изменен.

скачать fluidIter.py С здесь https://github.com/alanbacon/FluidIterator, это всего лишь один файл, поэтому нет необходимости устанавливать git. Нет установщика, поэтому вам нужно будет убедиться, что файл находится в пути python самостоятельно. Код был написан для python 3 и не тестировался на в Python 2.

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

это приведет к следующему результату:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

выше мы использовали pop метод в объекте fluid list. Другие общие итерационные методы также реализованы, такие как del fluidL[i], .remove, .insert, .append, .extend. Список также можно изменить с помощью slices (sort и reverse методы не реализованы).

единственное условие-вы должны только изменить список на месте, если в любой момент fluidL или l были переназначены на другой объект списка, код не будет работать. Оригинал fluidL объект по-прежнему будет использоваться циклом for, но станет недоступным для изменения.

то есть

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

если мы хотим получить доступ к текущему значению индекса из списка, мы не можем перечислить, так как это только подсчитывает, сколько раз за цикл запуска. Вместо этого мы будем использовать объект iterator напрямую.

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

это выведет наружу следующий:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

на FluidIterable class просто предоставляет оболочку для исходного объекта списка. Исходный объект может быть доступен как свойство объекта fluid, например:

originalList = fluidArr.fixedIterable

дополнительные примеры / тесты можно найти в if __name__ is "__main__": раздел в нижней части fluidIter.py. На них стоит посмотреть, потому что они объясняют, что происходит в различных ситуациях. Например: замена больших разделов списка с помощью среза. Или использовать (и изменять) то же самое метод вложенных циклов.

как я уже говорил Для начала: это сложное решение, которое повредит читаемости вашего кода и затруднит его отладку. Поэтому другие решения, такие как понимание списка, упомянутые в David Raznick's ответ следует рассмотреть в первую очередь. Тем не менее, я нашел времена, когда этот класс был полезен для меня и был проще в использовании, чем отслеживать индексы элементов, которые нуждаются удаление.


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

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

то есть

newList = [i for i in oldList if testFunc(i)]

но что, если результат testFunc зависит от элементы, которые были добавлены в newList уже? Или элементы все еще в oldList что может быть рядом? Возможно, все еще есть способ использовать понимание списка, но он начнет терять свою элегантность, и для меня легче изменить список на месте.

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

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

выход и окончательный сокращенный список показан ниже

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]

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

однако есть один случай, когда безопасно удалять элементы из последовательности, которую вы повторяете: если вы удаляете только один элемент во время итерации. Это можно обеспечить с помощью return или break. Например:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

это часто легче понять, чем понимание списка, когда вы выполняете некоторые операции с побочными эффектами на первом элементе в списке, который соответствует некоторому условию, а затем удаляете этот элемент из списка сразу после этого.


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

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

Это должно быть значительно быстрее, чем все остальное.


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

вот пример, когда копирование списка заранее неверно, обратная итерация невозможна, и понимание списка также не является опцией.

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p

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

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

Как отметил Дэвид, я рекомендую понимание списка, чтобы сохранить элементы, которые вы не хотите удалять.

somelist = [x for x in somelist if not determine(x)]