Wie lösche ich ein Element in einer Liste, wenn es existiert?

259

Ich erhalte new_tagvon einem Formulartextfeld mit self.response.get("new_tag")und selected_tagsvon Kontrollkästchenfeldern mit

self.response.get_all("selected_tags")

Ich kombiniere sie so:

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

( f1.striplistist eine Funktion, die Leerzeichen innerhalb der Zeichenfolgen in der Liste entfernt.)

Aber in dem Fall, dass tag_listleer ist (es werden keine neuen Tags eingegeben), aber es gibt einige selected_tags, new_tag_listenthält eine leere Zeichenfolge " ".

Zum Beispiel von logging.info:

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

Wie werde ich die leere Zeichenfolge los?

Wenn die Liste eine leere Zeichenfolge enthält:

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

Aber wenn es keine leere Zeichenfolge gibt:

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

Aber das gibt:

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

Warum passiert das und wie arbeite ich daran?

Zeynel
quelle

Antworten:

718

1) Fast englischer Stil:

Testen Sie die Anwesenheit mit dem inOperator und wenden Sie dann die removeMethode an.

if thing in some_list: some_list.remove(thing)

Die removeMethode entfernt nur das erste Vorkommen von thing, um alle Vorkommen zu entfernen, die Sie whileanstelle von verwenden können if.

while thing in some_list: some_list.remove(thing)    
  • Einfach genug, wahrscheinlich meine Wahl. Für kleine Listen (kann Einzeiler nicht widerstehen)

2) Ententyp , EAFP- Stil:

Diese Einstellung, bei der zuerst geschossen, Fragen gestellt und zuletzt geschossen wird, ist in Python üblich. Anstatt im Voraus zu testen, ob das Objekt geeignet ist, führen Sie einfach die Operation aus und fangen Sie relevante Ausnahmen ab:

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

Natürlich ist die zweite Ausnahmeklausel im obigen Beispiel nicht nur von fragwürdigem Humor, sondern völlig unnötig (der Punkt war, die Ententypisierung für Leute zu veranschaulichen, die mit dem Konzept nicht vertraut sind).

Wenn Sie mehrere Vorkommen von Dingen erwarten:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • ein wenig ausführlich für diesen speziellen Anwendungsfall, aber in Python sehr idiomatisch.
  • Dies ist besser als # 1
  • PEP 463 schlug eine kürzere Syntax für try / mit Ausnahme der einfachen Verwendung vor, die hier nützlich wäre, wurde jedoch nicht genehmigt.

Mit dem in Python 3.4 eingeführten Context () - Kontextmanager von contextlib kann der obige Code jedoch folgendermaßen vereinfacht werden:

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

Nochmals, wenn Sie mehrere Vorkommen von Dingen erwarten:

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

3) Funktionsstil:

Um 1993, Python bekam lambda, reduce(), filter()und map(), mit freundlicher Genehmigung von einem Lisp - Hacker , die sie verpasst und legte Arbeits Patches *. Sie können filterElemente aus der Liste entfernen:

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

Es ist eine Abkürzung , die für Ihren Fall nützlich sein kann: Wenn Sie leere Elemente herausfiltern möchten (in der Tat Artikel , wo bool(item) == False, wie None, null, leere Zeichenfolge oder andere leere Sammlungen) Sie None als erstes Argument übergeben können:

cleaned_list = filter(None, some_list)
  • [Update] : in Python 2.x, filter(function, iterable)früher äquivalent zu [item for item in iterable if function(item)](oder [item for item in iterable if item]wenn das erste Argument ist None); in Python 3.x entspricht es jetzt (item for item in iterable if function(item)). Der subtile Unterschied besteht darin, dass der Filter zum Zurückgeben einer Liste verwendet wird. Jetzt funktioniert er wie ein Generatorausdruck. Dies ist in Ordnung, wenn Sie nur die bereinigte Liste durchlaufen und verwerfen. Wenn Sie jedoch wirklich eine Liste benötigen, müssen Sie den filter()Aufruf einschließen mit dem list()Konstruktor.
  • * Diese Konstrukte mit Lispy-Geschmack gelten in Python als etwas fremd. Um 2005 sprach Guido sogar überfilter das Löschen - zusammen mit Gefährten mapund reduce(sie sind noch nicht verschwunden, reducewurden aber in das functools- Modul verschoben , das einen Blick wert ist, wenn Sie Funktionen höherer Ordnung mögen ).

4) Mathematischer Stil:

Das Listenverständnis wurde zum bevorzugten Stil für die Listenmanipulation in Python, seit es in Version 2.0 von PEP 202 eingeführt wurde . Das Grundprinzip dahinter ist, dass Listenverständnisse eine präzisere Möglichkeit bieten, Listen in Situationen zu erstellen, in denen derzeit map()und filter()und oder verschachtelte Schleifen verwendet werden.

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

Generatorausdrücke wurden in Version 2.4 von PEP 289 eingeführt . Ein Generatorausdruck ist besser für Situationen geeignet, in denen Sie nicht wirklich eine vollständige Liste im Speicher erstellen müssen (oder möchten) - beispielsweise, wenn Sie nur die Elemente einzeln durchlaufen möchten. Wenn Sie nur die Liste durchlaufen, können Sie sich einen Generatorausdruck als ein faul ausgewertetes Listenverständnis vorstellen:

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

Anmerkungen

  1. Möglicherweise möchten Sie !=stattdessen den Ungleichungsoperator verwenden is not( der Unterschied ist wichtig ).
  2. Für Kritiker von Methoden, die eine Listenkopie implizieren: Entgegen der landläufigen Meinung sind Generatorausdrücke nicht immer effizienter als Listenverständnisse - bitte profilieren Sie sich, bevor Sie sich beschweren
Paulo Scardine
quelle
3
Darf ich vorschlagen, die AttributeError-Behandlung in (2) wegzulassen? Es lenkt ab und wird in den anderen Abschnitten (oder anderen Teilen desselben Abschnitts) nicht behandelt. Schlimmer noch, jemand könnte diesen Code kopieren, ohne zu bemerken, dass er Ausnahmen übermäßig aggressiv unterdrückt. Die ursprüngliche Frage setzt eine Liste voraus, die Antwort sollte es auch sein.
Jason R. Coombs
1
Super umfassende Antwort! Schön, dass es von "Style" in verschiedene Abschnitte unterteilt wurde. Vielen Dank!
Halloleo
Welches ist das schnellste?
Sheshank S.
12
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

Beachten Sie, dass dadurch nur eine Instanz der leeren Zeichenfolge aus Ihrer Liste entfernt wird (wie es auch Ihr Code getan hätte). Kann Ihre Liste mehr als eine enthalten?

Tim Pietzcker
quelle
5

Wenn indexdie gesuchte Zeichenfolge nicht gefunden wird, wird die angezeigte Zeichenfolge ValueErrorausgegeben. Fangen Sie entweder den ValueError ab:

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

oder verwenden find, was in diesem Fall -1 zurückgibt.

i = s.find("")
if i >= 0:
    del s[i]
else:
    print "new_tag_list has no empty string"
Phihag
quelle
Ist find () ein Listenattribut? Ich bekomme:>>> 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
Der remove()Ansatz von Time Pietscker ist viel direkter: Er zeigt direkt, was der Code tun soll (es ist in der Tat kein Zwischenindex erforderlich i).
Eric O Lebigot
1
@Zeynel nein, es sollte in jedem Python sein, siehe docs.python.org/library/string.html#string.find . Aber wie EOL betonte, ist es besser, einfach entfernen zu verwenden.
Phihag
4

Der Vollständigkeit halber wird diese Antwort hinzugefügt, obwohl sie nur unter bestimmten Bedingungen verwendet werden kann.

Wenn Sie sehr große Listen haben, müssen Sie die CPython-Interna nicht mehr am Ende der Liste entfernen memmove, wenn Sie die Liste neu anordnen können. Es gibt einen Leistungsgewinn, den Sie am Ende der Liste entfernen können, da nicht memmove jedes Element nach dem Element, das Sie entfernen, benötigt wird - einen Schritt zurück (1) .
Bei einmaligen Entfernungen kann der Leistungsunterschied akzeptabel sein. Wenn Sie jedoch eine große Liste haben und viele Elemente entfernen müssen, werden Sie wahrscheinlich einen Leistungseinbruch bemerken.

Zugegebenermaßen ist in diesen Fällen die Durchführung einer vollständigen Listensuche wahrscheinlich auch ein Leistungsengpass, es sei denn, Elemente stehen meistens ganz oben auf der Liste.

Diese Methode kann zum effizienteren Entfernen verwendet werden,
sofern eine Neuordnung der Liste zulässig ist. (2)

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

Möglicherweise möchten Sie vermeiden, einen Fehler auszulösen, wenn der itemnicht in der Liste enthalten ist.

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. Während ich dies mit CPython getestet habe, verwenden die meisten / alle anderen Python-Implementierungen wahrscheinlich ein Array, um Listen intern zu speichern. Wenn sie also keine ausgefeilte Datenstruktur verwenden, die für eine effiziente Größenänderung von Listen ausgelegt ist, weisen sie wahrscheinlich dieselben Leistungsmerkmale auf.

Vergleichen Sie auf einfache Weise den Geschwindigkeitsunterschied zwischen dem Entfernen vom Anfang der Liste und dem Entfernen des letzten Elements:

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

Mit:

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

(gibt eine Geschwindigkeitsdifferenz um eine Größenordnung an, wobei das zweite Beispiel mit CPython und PyPy schneller ist).

  1. In diesem Fall könnten Sie die Verwendung von a in Betracht ziehen set, insbesondere wenn die Liste keine Duplikate speichern soll.
    In der Praxis müssen Sie jedoch möglicherweise veränderbare Daten speichern, die nicht zu a hinzugefügt werden können set. Überprüfen Sie auch auf btree's, ob die Daten bestellt werden können.
ideasman42
quelle
3

Eek, mach nichts so kompliziertes :)

Nur filter()deine Tags. bool()Gibt Falsefür leere Zeichenfolgen zurück, also anstelle von

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

du solltest schreiben

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

oder noch besser, setzen Sie diese Logik striplist()so ein, dass sie überhaupt keine leeren Zeichenfolgen zurückgibt.

dfichter
quelle
Vielen Dank! Alles gute Antworten, aber ich denke, ich werde dies nutzen. Dies ist meine striplistFunktion, wie kann ich Ihre Lösung einbinden: def striplist (l): "" entfernt Leerzeichen von Zeichenfolgen in einer Liste l "" "return ([x.strip () für x in l])
Zeynel
1
@ Zeynel: sicher. Sie könnten entweder einen Test in Ihrem Liste Verständnis so: [x.strip() for x in l if x.strip()]oder verwenden Python-internen mapund filterFunktionen wie folgt aus : filter(bool, map(str.strip, l)). Wenn Sie es testen möchten, bewerten Sie dies im interaktiven Interpreter : filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
dfichter
Filter hat eine Verknüpfung für diesen Fall (Auswertung des Elements im Booleschen Kontext): Es reicht aus, Noneanstelle des boolersten Arguments zu verwenden.
Paulo Scardine
2

Hier ist ein weiterer Einzeiler-Ansatz, den Sie dort rauswerfen sollten:

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

Es wird keine Listenkopie erstellt, es werden keine mehrfachen Durchläufe durch die Liste durchgeführt, es wird keine zusätzliche Ausnahmebehandlung erforderlich, und es wird das übereinstimmende Objekt oder Keine zurückgegeben, wenn keine Übereinstimmung vorliegt. Das einzige Problem ist, dass es für eine lange Aussage sorgt.

Wenn Sie nach einer Einzeilerlösung suchen, die keine Ausnahmen auslöst, ist next () der richtige Weg, da es eine der wenigen Python-Funktionen ist, die ein Standardargument unterstützen.

Däne Weiß
quelle
1

Alles was Sie tun müssen, ist dies

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

Aber diese Methode hat ein Problem. Sie müssen etwas an der Ausnahme platzieren, damit ich Folgendes gefunden habe:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")
SollyBunny
quelle
3
Sie sollten die integrierte Liste nicht überschreiben . Und die Konvertierung in einen String wird im 2. Snippet nicht benötigt.
Robert Caspary