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?

Zeynel
fonte

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
Paulo Scardine
fonte
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?

Tim Pietzcker
fonte
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"
phihag
fonte
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.
ideasman42
fonte
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.

dfichter
fonte
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.

Dane White
fonte
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")
SollyBunny
fonte
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