Come eliminare un elemento in un elenco se esiste?


259

Ricevo new_tagda un campo di testo del modulo con self.response.get("new_tag")e selected_tagsda campi della casella di controllo con

self.response.get_all("selected_tags")

Li combino così:

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

( f1.striplistè una funzione che rimuove gli spazi bianchi all'interno delle stringhe nell'elenco.)

Ma nel caso che tag_listsia vuoto (non vengono inseriti nuovi tag) ma ce ne sono alcuni selected_tags, new_tag_listcontiene una stringa vuota " ".

Ad esempio, da logging.info:

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

Come posso liberarmi della stringa vuota?

Se è presente una stringa vuota nell'elenco:

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

Ma se non c'è una stringa vuota:

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

Ma questo 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

Perché ciò accade e come posso aggirare il problema?

Risposte:


718

1) stile quasi inglese:

Verificare la presenza utilizzando l' inoperatore, quindi applicare il removemetodo.

if thing in some_list: some_list.remove(thing)

Il removemetodo rimuoverà solo la prima occorrenza di thing, al fine di rimuovere tutte le occorrenze che è possibile utilizzare al whileposto di if.

while thing in some_list: some_list.remove(thing)    
  • Abbastanza semplice, probabilmente la mia scelta. Per piccoli elenchi (non posso resistere a una riga)

2) Duck-typed , stile EAFP :

Questo atteggiamento di sparare prima di porre domande è comune in Python. Invece di testare in anticipo se l'oggetto è adatto, basta eseguire l'operazione e catturare le Eccezioni rilevanti:

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

Ovviamente la seconda clausola tranne nell'esempio sopra non è solo di umorismo discutibile ma del tutto superflua (il punto era quello di illustrare la tipizzazione di anatre per le persone che non hanno familiarità con il concetto).

Se ti aspetti più ricorrenze di cose:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • un po 'prolisso per questo caso d'uso specifico, ma molto idiomatico in Python.
  • questo funziona meglio del n. 1
  • PEP 463 ha proposto una sintassi più breve per provare / tranne un semplice utilizzo che sarebbe utile qui, ma non è stato approvato.

Tuttavia, con il contestomanager suppress () di contextlib (introdotto in python 3.4) il codice sopra può essere semplificato a questo:

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

Ancora una volta, se ti aspetti più ricorrenze di cose:

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

3) Stile funzionale:

Intorno al 1993, Python ottenuto lambda, reduce(), filter()e map(), per gentile concessione di un Lisp hacker che li ha mancato e le patch di lavoro presentate *. È possibile utilizzare filterper rimuovere elementi dall'elenco:

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

C'è una scorciatoia che può essere utile per il tuo caso: se vuoi filtrare gli oggetti vuoti (in effetti elementi in cui bool(item) == False, come None, zero, stringhe vuote o altre raccolte vuote), puoi passare None come primo argomento:

cleaned_list = filter(None, some_list)
  • [aggiornamento] : in Python 2.x, filter(function, iterable)era equivalente a [item for item in iterable if function(item)](o [item for item in iterable if item]se il primo argomento è None); in Python 3.x, ora è equivalente a (item for item in iterable if function(item)). La sottile differenza è che il filtro ha usato per restituire un elenco, ora funziona come un'espressione del generatore - va bene se stai iterando sull'elenco pulito e scartandolo, ma se hai davvero bisogno di un elenco, devi racchiudere la filter()chiamata con il list()costruttore.
  • * Questi costrutti al gusto di Lispy sono considerati un po 'alieni in Python. Intorno al 2005, Guido stava addirittura parlando di caderefilter - insieme ai compagni mape reduce(non sono ancora andati ma sono reducestati spostati nel modulo functools , che vale la pena dare un'occhiata se ti piacciono le funzioni di alto ordine ).

4) Stile matematico:

La comprensione dell'elenco è diventata lo stile preferito per la manipolazione dell'elenco in Python da quando è stato introdotto nella versione 2.0 da PEP 202 . La logica alla base di ciò è che la comprensione degli elenchi fornisce un modo più conciso di creare elenchi in situazioni in cui map()e filter()e / o cicli annidati verrebbero attualmente utilizzati.

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

Le espressioni del generatore sono state introdotte nella versione 2.4 da PEP 289 . Un'espressione del generatore è migliore per le situazioni in cui non è davvero necessario (o desiderato) creare un elenco completo in memoria, ad esempio quando si desidera semplicemente scorrere gli elementi uno alla volta. Se stai solo ripetendo l'elenco, puoi pensare all'espressione di un generatore come a una comprensione dell'elenco valutato pigro :

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

Appunti

  1. potresti voler usare l'operatore di disuguaglianza !=invece di is not( la differenza è importante )
  2. per i critici dei metodi che implicano una copia dell'elenco: contrariamente alla credenza popolare, le espressioni del generatore non sono sempre più efficienti della comprensione dell'elenco - si prega di fare un profilo prima di lamentarsi

3
Posso suggerire di omettere la gestione AttributeError in (2)? È fonte di distrazione e non viene gestito nelle altre sezioni (o in altre parti della stessa sezione). Peggio ancora, qualcuno potrebbe copiare quel codice senza rendersi conto che stanno sopprimendo eccessivamente le eccezioni. La domanda originale presuppone un elenco, anche la risposta dovrebbe.
Jason R. Coombs,

1
Risposta super completa! Fantastico averlo diviso in diverse sezioni in "Stile". Grazie!
halloleo

Qual è il più veloce però?
Sheshank S.

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

Nota che questo rimuoverà solo una istanza della stringa vuota dal tuo elenco (come avrebbe fatto anche il tuo codice). La tua lista può contenere più di una?


5

Se indexnon trova la stringa cercata, genera ciò ValueErrorche stai vedendo. Prendi il ValueError:

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

o use find, che restituisce -1 in quel caso.

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

Find () è un attributo di lista? Ricevo:>>> 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
L' remove()approccio di Time Pietscker è molto più diretto: mostra direttamente cosa deve fare il codice (in effetti non è necessario un indice intermedio i).
Eric O Lebigot,

1
@Zeynel no, dovrebbe essere in ogni Python, vedi docs.python.org/library/string.html#string.find . Ma come sottolineato da EOL, semplicemente usando remove è meglio.
phihag,

4

Aggiungendo questa risposta per completezza, sebbene sia utilizzabile solo a determinate condizioni.

Se si dispone di elenchi molto grandi, la rimozione dalla fine dell'elenco evita che gli interni di CPython debbano farlo memmove, per situazioni in cui è possibile riordinare l'elenco. Dà un guadagno in termini di prestazioni da rimuovere dalla fine dell'elenco, poiché non sarà necessario per memmove ogni elemento dopo quello che stai rimuovendo - indietro di un passaggio (1) .
Per le rimozioni una tantum la differenza di prestazioni può essere accettabile, ma se si dispone di un elenco di grandi dimensioni e è necessario rimuovere molti elementi, è probabile che si verifichi un calo delle prestazioni.

Anche se è vero, in questi casi, eseguire una ricerca dell'elenco completo è probabilmente anche un collo di bottiglia delle prestazioni, a meno che gli elementi non siano principalmente in cima all'elenco.

Questo metodo può essere utilizzato per una rimozione più efficiente,
a condizione che il riordino dell'elenco sia accettabile. (2)

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

Si consiglia di evitare di generare un errore quando itemnon è nell'elenco.

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. Mentre l'ho provato con CPython, è molto probabile che la maggior parte / tutte le altre implementazioni di Python utilizzino un array per memorizzare gli elenchi internamente. Pertanto, a meno che non utilizzino una struttura dati sofisticata progettata per ridimensionare gli elenchi in modo efficiente, probabilmente hanno le stesse caratteristiche prestazionali.

Un modo semplice per testarlo, confronta la differenza di velocità dalla rimozione dalla parte anteriore dell'elenco con la rimozione dell'ultimo elemento:

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

Con:

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

(fornisce un ordine di differenza di velocità di grandezza in cui il secondo esempio è più veloce con CPython e PyPy).

  1. In questo caso, potresti prendere in considerazione l'utilizzo di a set, soprattutto se l'elenco non è destinato a memorizzare duplicati.
    In pratica, tuttavia, potrebbe essere necessario archiviare dati mutabili che non possono essere aggiunti a set. Controlla anche su btree se i dati possono essere ordinati.

3

Eek, non fare nulla di così complicato:)

Solo i filter()tuoi tag. bool()ritorna Falseper stringhe vuote, quindi invece di

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

dovresti scrivere

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

o meglio ancora, inserisci questa logica in striplist()modo che non restituisca stringhe vuote in primo luogo.


Grazie! Tutte buone risposte ma penso che userò questo. Questa è la mia striplistfunzione, come posso incorporare la tua soluzione: def striplist (l): "" "elimina gli spazi bianchi dalle stringhe in un elenco l" "" return ([x.strip () for x in l])
Zeynel

1
@Zeynel: certo. Si potrebbe o mettere una prova all'interno lista la vostra comprensione in questo modo: [x.strip() for x in l if x.strip()]o utilizzare Python built-in mape filterle funzioni in questo modo: filter(bool, map(str.strip, l)). Se volete provarlo, valutare questa nell'interprete interattivo: filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
lunedì

Filter ha una scorciatoia per questo caso (valutazione dell'elemento nel contesto booleano): usare al Noneposto di boolper il primo argomento è sufficiente.
Paulo Scardine,

2

Ecco un altro approccio one-liner da lanciare lì:

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

Non crea una copia dell'elenco, non effettua più passaggi nell'elenco, non richiede una gestione aggiuntiva delle eccezioni e restituisce l'oggetto corrispondente o Nessuno se non esiste una corrispondenza. L'unico problema è che fa una lunga dichiarazione.

In generale, quando si cerca una soluzione one-liner che non generi eccezioni, next () è la strada da percorrere, poiché è una delle poche funzioni di Python che supporta un argomento predefinito.


1

Tutto quello che devi fare è questo

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

ma quel metodo ha un problema. Devi mettere qualcosa nel posto tranne quindi ho trovato questo:

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

3
Non dovresti sovrascrivere l' elenco integrato. E la conversione in una stringa non è necessaria nel secondo frammento.
Robert Caspary,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.