Como excluir um item de uma lista, se existir?


259

Estou obtendo new_tagde um campo de texto de formulário com self.response.get("new_tag")e selected_tagsde campos de caixa de seleção com

self.response.get_all("selected_tags")

Eu os combino assim:

tag_string = new_tag
new_tag_list = f1.striplist(tag_string.split(",") + selected_tags)

( f1.striplisté uma função que remove os espaços em branco nas cadeias da lista.)

Mas, no caso em que tag_listestá vazio (nenhuma nova tag é inserida), mas há algumas selected_tags, new_tag_listcontém uma string vazia " ".

Por exemplo, de logging.info:

new_tag
selected_tags[u'Hello', u'Cool', u'Glam']
new_tag_list[u'', u'Hello', u'Cool', u'Glam']

Como faço para me livrar da string vazia?

Se houver uma sequência vazia na lista:

>>> s = [u'', u'Hello', u'Cool', u'Glam']
>>> i = s.index("")
>>> del s[i]
>>> s
[u'Hello', u'Cool', u'Glam']

Mas se não houver uma string vazia:

>>> s = [u'Hello', u'Cool', u'Glam']
>>> if s.index(""):
        i = s.index("")
        del s[i]
    else:
        print "new_tag_list has no empty string"

Mas isso dá:

Traceback (most recent call last):
  File "<pyshell#30>", line 1, in <module>
    if new_tag_list.index(""):
        ValueError: list.index(x): x not in list

Por que isso acontece e como faço para solucionar isso?

Respostas:


718

1) Estilo quase inglês:

Teste a presença usando o inoperador e aplique o removemétodo

if thing in some_list: some_list.remove(thing)

O removemétodo removerá apenas a primeira ocorrência de thing, para remover todas as ocorrências que você pode usar em whilevez de if.

while thing in some_list: some_list.remove(thing)    
  • Simples o suficiente, provavelmente a minha escolha. Para listas pequenas (não resiste a frases simples)

2) Tipo de pato , estilo EAFP :

Essa atitude de atirar primeiro-fazer-perguntas-última é comum em Python. Em vez de testar antecipadamente se o objeto é adequado, basta executar a operação e capturar exceções relevantes:

try:
    some_list.remove(thing)
except ValueError:
    pass # or scream: thing not in some_list!
except AttributeError:
    call_security("some_list not quacking like a list!")

É claro que a segunda cláusula exceto no exemplo acima não é apenas de humor questionável, mas é totalmente desnecessária (o objetivo era ilustrar a digitação de patos para pessoas não familiarizadas com o conceito).

Se você espera várias ocorrências:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • um pouco detalhado para este caso de uso específico, mas muito idiomático em Python.
  • isso tem um desempenho melhor que o nº 1
  • PEP 463 propôs uma sintaxe mais curta para try / exceto o uso simples que seria útil aqui, mas não foi aprovado.

No entanto, com o contextmanager suppress () contextpress (introduzido no python 3.4), o código acima pode ser simplificado para isso:

with suppress(ValueError, AttributeError):
    some_list.remove(thing)

Novamente, se você espera várias ocorrências de algo:

with suppress(ValueError):
    while True:
        some_list.remove(thing)

3) estilo funcional:

Por volta de 1993, Python tem lambda, reduce(), filter()e map(), graças a um Lisp hacker que lhes falta e patches de trabalho apresentados *. Você pode usar filterpara remover elementos da lista:

is_not_thing = lambda x: x is not thing
cleaned_list = filter(is_not_thing, some_list)

Existe um atalho que pode ser útil para o seu caso: se você deseja filtrar itens vazios (na verdade, itens onde bool(item) == False, como Nonezero, cadeias vazias ou outras coleções vazias), pode passar None como o primeiro argumento:

cleaned_list = filter(None, some_list)
  • [update] : no Python 2.x, filter(function, iterable)costumava ser equivalente a [item for item in iterable if function(item)](ou [item for item in iterable if item]se o primeiro argumento for None); no Python 3.x, agora é equivalente a (item for item in iterable if function(item)). A diferença sutil é que o filtro é usado para retornar uma lista, agora funciona como uma expressão geradora - tudo bem se você estiver apenas repetindo a lista limpa e descartando-a, mas se realmente precisar de uma lista, precisará encerrar a filter()chamada com o list()construtor.
  • * Essas construções com sabor Lispy são consideradas um pouco alienígenas em Python. Por volta de 2005, Guido chegou a falar em desistirfilter - junto com companheiros mape reduce(eles ainda não foram embora, mas reduceforam transferidos para o módulo functools , que vale a pena dar uma olhada se você gosta de funções de alta ordem ).

4) estilo matemático:

A compreensão de lista tornou-se o estilo preferido para manipulação de lista em Python desde que foi introduzido na versão 2.0 pelo PEP 202 . A lógica por trás disso é que compreensões lista fornecer uma maneira mais concisa para criar listas em situações em que map()e filter()teria atualmente ser usado e / loops ou aninhadas.

cleaned_list = [ x for x in some_list if x is not thing ]

As expressões de gerador foram introduzidas na versão 2.4 pelo PEP 289 . Uma expressão de gerador é melhor para situações em que você realmente não precisa (ou deseja) de ter uma lista completa criada na memória - como quando você deseja iterar os elementos, um de cada vez. Se você estiver apenas repetindo a lista, considere uma expressão geradora como uma compreensão preguiçosa da lista avaliada :

for item in (x for x in some_list if x is not thing):
    do_your_thing_with(item)

Notas

  1. convém usar o operador de desigualdade em !=vez de is not( a diferença é importante )
  2. para críticos de métodos que implicam uma cópia da lista: ao contrário da crença popular, as expressões geradoras nem sempre são mais eficientes do que as compreensões da lista - faça um perfil antes de reclamar

3
Posso sugerir a omissão da manipulação de AttributeError em (2)? É perturbador e não é tratado nas outras seções (ou em outras partes da mesma seção). Pior, alguém pode copiar esse código sem perceber que está suprimindo excessivamente agressivamente as exceções. A pergunta original assume uma lista, a resposta também.
Jason R. Coombs

1
Resposta super abrangente! É ótimo dividir em seções diferentes por "Estilo". Obrigado!
halloleo 4/09/19

Qual é o mais rápido?
Sheshank S. 12/01

12
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

Observe que isso removerá apenas uma instância da string vazia da sua lista (como seu código também teria). Sua lista pode conter mais de um?


5

Se indexnão encontrar a sequência pesquisada, ela lança o que ValueErrorvocê está vendo. Pegue o ValueError:

try:
    i = s.index("")
    del s[i]
except ValueError:
    print "new_tag_list has no empty string"

ou use find, que retorna -1 nesse caso.

i = s.find("")
if i >= 0:
    del s[i]
else:
    print "new_tag_list has no empty string"

Find () é um atributo de lista? Estou recebendo:>>> s [u'Hello', u'Cool', u'Glam'] >>> i = s.find("") Traceback (most recent call last): File "<pyshell#42>", line 1, in <module> i = s.find("") AttributeError: 'list' object has no attribute 'find'
Zeynel

2
A remove()abordagem do Time Pietscker é muito mais direta: mostra diretamente o que o código deve fazer (de fato não há necessidade de um índice intermediário i).
Eric O Lebigot

1
@ Zeynel não, deve estar em todos os Python, consulte docs.python.org/library/string.html#string.find . Mas, como a EOL apontou, o simples uso de remover é melhor.
Phihag #

4

Adicionar esta resposta para que fique completo, embora seja utilizável apenas sob certas condições.

Se você tiver listas muito grandes, a remoção do final da lista evita a necessidade de componentes internos do CPython memmove, para situações em que você pode reordenar a lista. Ele fornece um ganho de desempenho para remover do final da lista, pois não precisará de memmove todos os itens após a remoção - um passo atrás (1) .
Para remoções pontuais, a diferença de desempenho pode ser aceitável, mas se você tiver uma lista grande e precisar remover muitos itens - provavelmente notará um impacto no desempenho.

Embora seja certo que, nesses casos, fazer uma pesquisa na lista completa provavelmente também seja um gargalo de desempenho, a menos que os itens estejam principalmente na frente da lista.

Este método pode ser usado para uma remoção mais eficiente,
desde que a ordem da lista seja aceitável. 2)

def remove_unordered(ls, item):
    i = ls.index(item)
    ls[-1], ls[i] = ls[i], ls[-1]
    ls.pop()

Você pode evitar um erro quando este itemnão estiver na lista.

def remove_unordered_test(ls, item):
    try:
        i = ls.index(item)
    except ValueError:
        return False
    ls[-1], ls[i] = ls[i], ls[-1]
    ls.pop()
    return True

  1. Enquanto eu testei isso com o CPython, é bem provável que a maioria das implementações do Python use uma matriz para armazenar listas internamente. Portanto, a menos que eles usem uma estrutura de dados sofisticada projetada para o redimensionamento eficiente da lista, eles provavelmente terão a mesma característica de desempenho.

Uma maneira simples de testar isso, compare a diferença de velocidade entre remover da frente da lista e remover o último elemento:

python -m timeit 'a = [0] * 100000' 'while a: a.remove(0)'

Com:

python -m timeit 'a = [0] * 100000' 'while a: a.pop()'

(fornece uma diferença de velocidade de ordem de magnitude em que o segundo exemplo é mais rápido com CPython e PyPy).

  1. Nesse caso, você pode considerar usar a set, especialmente se a lista não tiver o objetivo de armazenar duplicatas.
    Na prática, talvez você precise armazenar dados mutáveis ​​que não podem ser adicionados a um set. Verifique também na btree se os dados podem ser pedidos.

3

Eek, não faça nada tão complicado :)

Apenas filter()suas tags. bool()retorna Falsepara cadeias vazias; portanto, em vez de

new_tag_list = f1.striplist(tag_string.split(",") + selected_tags)

você deveria escrever

new_tag_list = filter(bool, f1.striplist(tag_string.split(",") + selected_tags))

ou melhor ainda, coloque essa lógica dentro striplist()para que ela não retorne cadeias vazias em primeiro lugar.


Obrigado! Todas as boas respostas, mas acho que vou usar isso. Esta é a minha striplistfunção: como incorporar sua solução: def striplist (l): "" "retira os espaços em branco das strings de uma lista l" "" return ([x.strip () para x em l])
Zeynel

1
@ Zeynel: com certeza. Você poderia colocar um teste dentro de lista da sua compreensão como esta: [x.strip() for x in l if x.strip()]ou usar do Python embutido mape filterfunções como este: filter(bool, map(str.strip, l)). Se você quiser testá-lo, avaliar isso no interpretador interativo: filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
dfichter

O filtro possui um atalho para esse caso (avaliar o elemento no contexto booleano): usar em Nonevez de boolpara o primeiro argumento é suficiente.
Paulo Scardine

2

Aqui está outra abordagem de uma linha a ser lançada:

next((some_list.pop(i) for i, l in enumerate(some_list) if l == thing), None)

Ele não cria uma cópia da lista, não faz várias passagens pela lista, não requer tratamento adicional de exceções e retorna o objeto correspondente ou Nenhum se não houver uma correspondência. O único problema é que ele faz uma longa declaração.

Em geral, ao procurar uma solução de uma linha que não lança exceções, next () é o caminho a seguir, pois é uma das poucas funções do Python que suporta um argumento padrão.


1

Tudo o que você precisa fazer é isso

list = ["a", "b", "c"]
    try:
        list.remove("a")
    except:
        print("meow")

mas esse método tem um problema. Você tem que colocar algo no local de exceção, então eu encontrei isso:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")

3
Você não deve substituir a lista incorporada. E a conversão para uma string não é necessária no segundo snippet.
Robert Caspary
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.