Wie gehe ich mit Ausnahmen in einem Listenverständnis um?

119

Ich habe ein Listenverständnis in Python, in dem jede Iteration eine Ausnahme auslösen kann.

Zum Beispiel , wenn ich habe:

eggs = (1,3,0,3,2)

[1/egg for egg in eggs]

Ich werde eine ZeroDivisionErrorAusnahme im 3. Element bekommen.

Wie kann ich mit dieser Ausnahme umgehen und die Ausführung des Listenverständnisses fortsetzen?

Ich kann mir nur vorstellen, eine Hilfsfunktion zu verwenden:

def spam(egg):
    try:
        return 1/egg
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Aber das sieht für mich etwas umständlich aus.

Gibt es eine bessere Möglichkeit, dies in Python zu tun?

Hinweis: Dies ist ein einfaches Beispiel (siehe " zum Beispiel " oben), das ich erfunden habe, weil mein reales Beispiel einen Kontext erfordert. Ich bin nicht daran interessiert, Fehler durch Teilen durch Null zu vermeiden, sondern Ausnahmen in einem Listenverständnis zu behandeln.

Nathan Fellman
quelle
4
Es gibt einen PEP 463 zum Hinzufügen eines Ausdrucks zum Behandeln von Ausnahmen. In Ihrem Beispiel wäre es [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Aber es ist immer noch im Entwurfsmodus. Mein Bauchgefühl ist, dass es nicht akzeptiert wird. Imho-Ausdrücke können zu chaotisch werden (Überprüfung mehrerer Ausnahmen, komplexere Kombinationen (mehrere logische Operatoren, komplexe Verständnisse usw.)
siehe
1
Beachten Sie, dass Sie für dieses spezielle Beispiel einen Numpy ndarraymit den entsprechenden Einstellungen in verwenden können np.seterr. Das würde dazu führen 1/0 = nan. Mir ist jedoch klar, dass sich dies nicht auf andere Situationen verallgemeinert, in denen dieses Bedürfnis entsteht.
Gerrit

Antworten:

96

In Python gibt es keinen integrierten Ausdruck, mit dem Sie eine Ausnahme ignorieren können (oder bei Ausnahmen alternative Werte & c zurückgeben können). Daher ist es buchstäblich unmöglich, "Ausnahmen in einem Listenverständnis zu behandeln", da ein Listenverständnis ein Ausdruck ist enthält keinen anderen Ausdruck, nichts weiter (dh keine Anweisungen und nur Anweisungen können Ausnahmen abfangen / ignorieren / behandeln).

Funktionsaufrufe sind Ausdrücke, und die Funktionskörper können alle gewünschten Anweisungen enthalten. Daher ist das Delegieren der Auswertung des ausnahmegefährdeten Unterausdrucks an eine Funktion, wie Sie bemerkt haben, eine mögliche Problemumgehung (andere sind, wenn möglich, möglich prüft Werte, die Ausnahmen hervorrufen können, wie auch in anderen Antworten vorgeschlagen).

Die richtigen Antworten auf die Frage "wie man mit Ausnahmen in einem Listenverständnis umgeht" drücken alle einen Teil all dieser Wahrheit aus: 1) wörtlich, dh lexikalisch im Verständnis selbst, kann man nicht; 2) In der Praxis delegieren Sie den Job an eine Funktion oder suchen nach fehleranfälligen Werten, wenn dies möglich ist. Ihre wiederholte Behauptung, dies sei keine Antwort, ist daher unbegründet.

Alex Martelli
quelle
14
Aha. Eine vollständige Antwort wäre also, dass ich entweder: 1. eine Funktion verwenden 2. kein Listenverständnis verwenden 3. versuchen, die Ausnahme zu verhindern, anstatt sie zu behandeln.
Nathan Fellman
9
Ich sehe "keine Verwendung von Listenverständnissen" nicht als Teil der Antwort auf "Umgang mit Ausnahmen in Listenverständnissen", aber ich denke, Sie könnten es vernünftigerweise als mögliche Folge von " lexikalisch im LC, das ist nicht möglich" sehen Ausnahmen behandeln ", was in der Tat der erste Teil der wörtlichen Antwort ist.
Alex Martelli
Können Sie einen Fehler in einem Generatorausdruck oder im Generatorverständnis feststellen?
1
@AlexMartelli, wäre eine Ausnahmeklausel so schwierig, in zukünftige Python-Versionen zu arbeiten? [x[1] for x in list except IndexError pass]. Konnte der Interpreter keine temporäre Funktion zum Ausprobieren erstellen x[1]?
Alancalvitti
@ Nathan, 1,2,3 oben, verursachen enorme Kopfschmerzen in funktionalen Datenflüssen, in denen 1. man normalerweise Funktionen über Lambdas einbinden möchte; 2. Alternative ist die Verwendung vieler verschachtelter for-Schleifen, die gegen das Funktionsparadigma verstoßen und zu fehleranfälligem Code führen. 3. Oft handelt es sich bei den Fehlern um Ad-hoc- und latent komplexe Datensätze, die, wie das lateinische Wort für Daten bedeutet, gegeben sind und daher nicht einfach verhindert werden können.
Alancalvitti
118

Mir ist klar, dass diese Frage ziemlich alt ist, aber Sie können auch eine allgemeine Funktion erstellen, um dies zu vereinfachen:

def catch(func, handle=lambda e : e, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        return handle(e)

Dann, nach Ihrem Verständnis:

eggs = (1,3,0,3,2)
[catch(lambda : 1/egg) for egg in eggs]
[1, 0, ('integer division or modulo by zero'), 0, 0]

Sie können die Standardhandle-Funktion natürlich beliebig gestalten (sagen Sie, Sie möchten standardmäßig lieber "Keine" zurückgeben).

Hoffe das hilft dir oder zukünftigen Zuschauern dieser Frage!

Hinweis: In Python 3 würde ich nur das Argument-Schlüsselwort 'handle' verwenden und es am Ende der Argumentliste einfügen. Dies würde das tatsächliche Weitergeben von Argumenten und dergleichen durch Fang viel natürlicher machen.

Bryan Head
quelle
2
extrem nützlich, danke. Obwohl ich den theoretischen Kommentaren zustimme, zeigt dies einen praktischen Ansatz zur Lösung eines Problems, das ich wiederholt hatte.
Paul
2
Antwort erstellen. Ein Mod, den ich vorschlagen würde, ist das Passieren argsund kwargsDurchlaufen. Auf diese Weise können Sie beispielsweise egganstelle eines fest codierten 0oder einer Ausnahme, wie Sie dies tun, zurückkehren.
Mad Physicist
3
Möglicherweise möchten Sie den Ausnahmetyp auch als optionales Argument (können Ausnahmetypen parametrisiert werden?), Damit unerwartete Ausnahmen nach oben ausgelöst werden, anstatt alle Ausnahmen zu ignorieren.
00prometheus
3
@Bryan, können Sie Code für "In Python 3 würde ich nur das Argument-Schlüsselwort 'handle' erstellen und es am Ende der Argumentliste einfügen." versuchte handlenach zu platzieren **kwargund bekam eine SyntaxError. Meinst du dereferenzieren als kwargs.get('handle',e)?
Alancalvitti
21

Sie können verwenden

[1/egg for egg in eggs if egg != 0]

Dadurch werden einfach Elemente übersprungen, die Null sind.

Peter
quelle
28
Dies beantwortet nicht die Frage, wie Ausnahmen in einem Listenverständnis behandelt werden sollen.
Nathan Fellman
8
ähm, ja, das tut es. Ausnahmen müssen nicht behandelt werden. Ja, es ist nicht immer die richtige Lösung, aber es ist eine übliche.
Peter
3
Ich verstehe. Ich nehme den Kommentar zurück (obwohl ich ihn nicht löschen werde, da diese kurze "Diskussion" die Antwort verbessert).
Nathan Fellman
11

Nein, es gibt keinen besseren Weg. In vielen Fällen können Sie Vermeidung anwenden, wie es Peter tut

Ihre andere Möglichkeit besteht darin, kein Verständnis zu verwenden

eggs = (1,3,0,3,2)

result=[]
for egg in eggs:
    try:
        result.append(egg/0)
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Es liegt an Ihnen, zu entscheiden, ob dies umständlicher ist oder nicht

John La Rooy
quelle
1
Wie würde ich hier Verständnis verwenden?
Nathan Fellman
@ Nathan: Das würdest du nicht. Gnibbler sagt: Nein, es gibt keinen besseren Weg
SilentGhost
Entschuldigung ... Ich habe das "Nicht" in seiner Antwort verpasst :-)
Nathan Fellman
4

Ich denke, eine Hilfsfunktion, wie sie von demjenigen vorgeschlagen wird, der die erste Frage stellt, und auch von Bryan Head, ist gut und überhaupt nicht umständlich. Eine einzige Zeile magischen Codes, die die ganze Arbeit erledigt, ist einfach nicht immer möglich, daher ist eine Hilfsfunktion eine perfekte Lösung, wenn man forSchleifen vermeiden möchte . Ich würde es jedoch zu diesem ändern:

# A modified version of the helper function by the Question starter 
def spam(egg):
    try:
        return 1/egg, None
    except ZeroDivisionError as err:
        # handle division by zero error        
        return None, err

Die Ausgabe wird dies sein [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. Mit dieser Antwort haben Sie die volle Kontrolle darüber, wie Sie möchten.

Elmex80s
quelle
Nett. Dies sieht dem EitherTyp in einigen funktionalen Programmiersprachen (wie Scala) sehr ähnlich, in denen ein EitherWert des einen oder anderen Typs enthalten kann, jedoch nicht beide. Der einzige Unterschied besteht darin, dass es in diesen Sprachen idiomatisch ist, den Fehler auf der linken Seite und den Wert auf der rechten Seite zu platzieren. Hier ist ein Artikel mit weiteren Informationen .
Alex Palmer
3

Ich habe keine Antwort gesehen, die dies erwähnt. Dieses Beispiel wäre jedoch eine Möglichkeit, zu verhindern, dass eine Ausnahme für bekannte Fehlerfälle ausgelöst wird.

eggs = (1,3,0,3,2)
[1/egg if egg > 0 else None for egg in eggs]


Output: [1, 0, None, 0, 0]
Slakker
quelle
Ist das nicht dasselbe wie diese Antwort? stackoverflow.com/a/1528244/1084
Nathan Fellman
Es gibt einen subtilen Unterschied. Die Filterung wird eher auf die Ausgabe als auf die Eingabeliste angewendet. Wie Sie im veröffentlichten Beispiel sehen können, habe ich für den Fall, der eine Ausnahme verursachen könnte, "Keine" angegeben.
Slakker