uma - substituir item lista python




Como remover itens de uma lista durante a iteração? (16)

Estou interagindo com uma lista de tuplas no Python e estou tentando removê-las se elas atenderem a determinados critérios.

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

O que devo usar no lugar de code_to_remove_tup ? Eu não consigo descobrir como remover o item dessa maneira.


As respostas que sugerem as compreensões da lista são ALMOST corretas - exceto que elas constroem uma lista completamente nova e, em seguida, dão o mesmo nome à lista antiga, pois elas NÃO modificam a lista antiga no lugar. Isso é diferente do que você estaria fazendo por remoção seletiva, como na sugestão do @ Lennart - é mais rápido, mas se sua lista é acessada através de múltiplas referências, o fato é que você está apenas recolocando uma das referências e NÃO alterando o objeto da lista. em si pode levar a bugs sutis e desastrosos.

Felizmente, é extremamente fácil obter a velocidade das compreensões da lista E a semântica necessária da alteração no local - apenas código:

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

Observe a diferença sutil com outras respostas: essa não está atribuindo a um nome de barra - está atribuindo a uma lista que por acaso é a lista inteira, substituindo o conteúdo da lista no mesmo objeto de lista Python , em vez de apenas recolocar uma referência (do objeto de lista anterior para o novo objeto de lista) como as outras respostas.


Esta resposta foi originalmente escrita em resposta a uma pergunta que já foi marcada como duplicada: Removendo coordenadas da lista em python

Existem dois problemas no seu código:

1) Ao usar remove (), você tenta remover números inteiros enquanto você precisa remover uma tupla.

2) O loop for irá pular itens na sua lista.

Vamos percorrer o que acontece quando executamos seu código:

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

O primeiro problema é que você está passando 'a' e 'b' para remove (), mas remove () aceita apenas um único argumento. Então, como podemos obter remover () para funcionar corretamente com sua lista? Precisamos descobrir o que cada elemento da sua lista é. Neste caso, cada um é uma tupla. Para ver isso, vamos acessar um elemento da lista (a indexação começa em 0):

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

Aha! Cada elemento de L1 é na verdade uma tupla. Então é isso que precisamos passar para remover (). Tuplas em python são muito fáceis, elas são simplesmente feitas colocando os valores entre parênteses. "a, b" não é uma tupla, mas "(a, b)" é uma tupla. Então, modificamos seu código e o executamos novamente:

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

Este código é executado sem nenhum erro, mas vamos ver a lista que ele gera:

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

Por que (1, -2) ainda está na sua lista? Acontece que modificar a lista usando um loop para iterar é uma idéia muito ruim sem um cuidado especial. A razão que (1, -2) permanece na lista é que os locais de cada item dentro da lista mudaram entre as iterações do loop for. Vejamos o que acontece se alimentarmos o código acima com uma lista maior:

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

Como você pode inferir desse resultado, toda vez que a instrução condicional for avaliada como true e um item de lista for removido, a próxima iteração do loop ignorará a avaliação do próximo item na lista, porque seus valores agora estão localizados em índices diferentes.

A solução mais intuitiva é copiar a lista, depois iterar sobre a lista original e modificar apenas a cópia. Você pode tentar fazer assim:

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)

No entanto, a saída será idêntica a antes:

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

Isso porque, quando criamos o L2, o Python na verdade não criou um novo objeto. Em vez disso, ele meramente referenciava L2 ao mesmo objeto que L1. Podemos verificar isso com 'is' que é diferente de meramente "igual a" (==).

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

Nós podemos fazer uma cópia verdadeira usando copy.copy (). Então tudo funciona como esperado:

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

Finalmente, há uma solução mais limpa do que ter que fazer uma cópia inteiramente nova de L1. A função invertida ():

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

Infelizmente, não consigo descrever adequadamente como o reverso () funciona. Ele retorna um objeto 'tira-se-identificador' quando uma lista é passada para ele. Para fins práticos, você pode pensar nisso como criar uma cópia invertida de seu argumento. Essa é a solução que recomendo.


Eu precisava fazer isso com uma lista enorme, e duplicar a lista parecia caro, especialmente porque no meu caso o número de exclusões seria poucos em comparação com os itens que permanecem. Eu tomei essa abordagem de baixo nível.

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

O que eu não sei é quão eficiente algumas exclusões são comparadas a copiar uma lista grande. Por favor, comente se você tiver alguma idéia.


Para aqueles que gostam de programação funcional:

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

ou

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

Se você quiser fazer qualquer outra coisa durante a iteração, pode ser bom obter o índice (que garante que você possa referenciá-lo, por exemplo, se você tiver uma lista de programas) e o conteúdo real do item da lista.

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 lhe dá acesso ao item e ao índice de uma só vez. reversed é para que os índices que você vai excluir mais tarde não mudem em você.


Sua melhor abordagem para esse exemplo seria uma compreensão da lista

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

Nos casos em que você está fazendo algo mais complexo do que chamar uma função determine , prefiro construir uma nova lista e simplesmente anexá-la à medida que eu for. Por exemplo

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

Copiar a lista usando a remove pode fazer com que seu código pareça um pouco mais limpo, conforme descrito em uma das respostas abaixo. Você definitivamente não deve fazer isso para listas extremamente grandes, já que isso envolve copiar primeiro toda a lista, e também executar uma operação de remove O(n) para cada elemento sendo removido, tornando este um algoritmo O(n^2) .

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

Uma solução possível, útil se você quiser não apenas remover algumas coisas, mas também fazer algo com todos os elementos em um único loop:

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

Você pode querer usar filter() disponível como o built-in.

Para mais detalhes, confira aqui


Você pode usar uma compreensão de lista para criar uma nova lista contendo apenas os elementos que você não deseja remover:

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

Ou, atribuindo à fatia somelist[:] , você pode alterar a lista existente para conter apenas os itens desejados:

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

Essa abordagem pode ser útil se houver outras referências à somelist que precisem refletir as alterações.

Em vez de uma compreensão, você também pode usar itertools . No Python 2:

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

Ou no Python 3:

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

Você precisa fazer uma cópia da lista e iterá-la primeiro ou a iteração falhará com resultados inesperados.

Por exemplo (depende do tipo de lista):

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

Um exemplo:

>>> 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
[]

Em algumas situações, onde você está fazendo mais do que simplesmente filtrar um item da lista no momento, você quer que sua iteração seja alterada durante a iteração.

Aqui está um exemplo em que copiar a lista de antemão é incorreto, a iteração reversa é impossível e uma compreensão de lista também não é uma opção.

""" 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

Imediatamente, você deseja criar uma cópia da lista para poder tê-la como referência quando estiver percorrendo e excluindo as tuplas dessa lista que atendam a um determinado critério.

Então, depende do tipo de lista que você deseja para a saída, seja uma lista das tuplas removidas ou uma lista das tuplas que não são removidas.

Como David salientou, recomendo a compreensão da lista para manter os elementos que você não deseja remover.

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

A maioria das respostas aqui quer que você crie uma cópia da lista. Eu tinha um caso de uso em que a lista era bastante longa (110 mil itens) e era mais inteligente continuar reduzindo a lista.

Primeiro de tudo você precisa substituir o loop foreach com while loop ,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

O valor de inão é alterado no bloco if porque você deseja obter o valor do novo item FROM THE SAME INDEX, quando o item antigo for excluído.


As outras respostas estão corretas e geralmente é uma má idéia excluir de uma lista que você está interagindo. A iteração reversa evita as armadilhas, mas é muito mais difícil seguir o código que faz isso, então, normalmente, é melhor usar uma compreensão de lista ou filter.

Há, no entanto, um caso em que é seguro remover elementos de uma sequência que você está iterando: se você estiver removendo apenas um item enquanto estiver interagindo. Isso pode ser garantido usando um returnou um break. Por exemplo:

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

Isso geralmente é mais fácil de entender do que uma compreensão de lista quando você está realizando algumas operações com efeitos colaterais no primeiro item de uma lista que atende a alguma condição e, em seguida, remove esse item da lista imediatamente após.


O método mais eficaz é a compreensão da lista, muitas pessoas mostram o seu caso, é claro, também é uma boa maneira de obter um iteratormeio filter.

Filterrecebe uma função e uma sequência. Filteraplica a função passada a cada elemento e, em seguida, decide se deseja reter ou descartar o elemento, dependendo se o valor de retorno da função é Trueou False.

Há um exemplo (obtenha as chances na tupla):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

Atenção: Você também não pode manipular iteradores. Os iteradores às vezes são melhores que sequências.


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

Você precisa ir para trás, caso contrário, é um pouco como serrar o galho de árvore em que você está sentado :-)

Usuários do Python 2: substitua o range por xrange para evitar a criação de uma lista codificada





iteration