Sind verschachtelte Try / Except-Blöcke in Python eine gute Programmierpraxis?

200

Ich schreibe meinen eigenen Container, der durch Attributaufrufe Zugriff auf ein Wörterbuch im Inneren gewähren muss. Die typische Verwendung des Behälters wäre wie folgt:

dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo

Ich weiß, dass es vielleicht dumm ist, so etwas zu schreiben, aber das ist die Funktionalität, die ich bereitstellen muss. Ich habe darüber nachgedacht, dies folgendermaßen umzusetzen:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        try:
            return self.dict[item]
        except KeyError:
            print "The object doesn't have such attribute"

Ich bin nicht sicher , ob verschachtelter try / except Blöcke sind eine gute Praxis , so eine andere Art und Weise zu verwenden wäre hasattr()und has_key():

def __getattribute__(self, item):
        if hasattr(self, item):
            return object.__getattribute__(item)
        else:
            if self.dict.has_key(item):
                return self.dict[item]
            else:
                raise AttributeError("some customised error")

Oder um einen von ihnen zu verwenden und einen Catch-Block wie folgt zu versuchen:

def __getattribute__(self, item):
    if hasattr(self, item):
        return object.__getattribute__(item)
    else:
        try:
            return self.dict[item]
        except KeyError:
            raise AttributeError("some customised error")

Welche Option ist am pythonischsten und elegantesten?

Michal
quelle
Würde mich über die Bereitstellung der Python-Götter freuen if 'foo' in dict_container:. Amen.
Gseattle

Antworten:

180

Ihr erstes Beispiel ist vollkommen in Ordnung. Sogar die offiziellen Python-Dokumente empfehlen diesen als EAFP bekannten Stil .

Persönlich vermeide ich lieber das Verschachteln, wenn es nicht notwendig ist:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass  # fallback to dict
    try:
        return self.dict[item]
    except KeyError:
        raise AttributeError("The object doesn't have such attribute") from None

PS. has_key()ist in Python 2 schon lange veraltet. Verwenden Sie item in self.dictstattdessen.

lqc
quelle
2
return object.__getattribute__(item)ist falsch und erzeugt ein, TypeErrorweil die falsche Anzahl von Argumenten übergeben wird. Es sollte stattdessen sein return object.__getattribute__(self, item).
Martineau
13
PEP 20: flach ist besser als verschachtelt.
Ioannis Filippidis
7
Was bedeutet das from Nonein der letzten Zeile?
Niklas
2
@niklas Es unterdrückt im Wesentlichen den Ausnahmekontext ("während der Behandlung dieser Ausnahme ist eine andere Ausnahme aufgetreten" - ähnliche Nachrichten). Siehe hier
Kade
Die Tatsache, dass die Python-Dokumente das Verschachteln von Versuchen empfehlen, ist irgendwie verrückt. Es ist offensichtlich ein schrecklicher Stil. Die richtige Art, eine Kette von Operationen zu handhaben, bei denen solche Dinge fehlschlagen können, wäre die Verwendung eines monadischen Konstrukts, das Python nicht unterstützt.
Henry Henrinson
19

Während es in Java in der Tat eine schlechte Praxis ist, Ausnahmen für die Flusskontrolle zu verwenden (hauptsächlich, weil Ausnahmen das JVM zwingen, Ressourcen zu sammeln ( mehr hier )), gibt es in Python zwei wichtige Prinzipien: Duck Typing und EAFP . Dies bedeutet im Grunde, dass Sie aufgefordert werden, ein Objekt so zu verwenden, wie Sie es sich vorstellen, und damit umzugehen, wenn die Dinge nicht so sind.

Zusammenfassend wäre das einzige Problem, dass Ihr Code zu stark eingerückt wird. Wenn Sie Lust dazu haben, versuchen Sie, einige der Verschachtelungen zu vereinfachen, wie von lqc vorgeschlagen

Bruno Penteado
quelle
10

Seien Sie vorsichtig - in diesem Fall wird zuerst finallyberührt, ABER auch übersprungen.

def a(z):
    try:
        100/z
    except ZeroDivisionError:
        try:
            print('x')
        finally:
            return 42
    finally:
        return 1


In [1]: a(0)
x
Out[1]: 1
Sławomir Lenart
quelle
Wow, es macht mich wahnsinnig ... Könnten Sie mich auf ein Dokumentationsfragment verweisen, das dieses Verhalten erklärt?
Michal
1
@Michal: fyi: Beide finallyBlöcke werden ausgeführt a(0), aber nur übergeordnete Blöcke werden finally-returnzurückgegeben.
Sławomir Lenart
9

Für Ihr spezielles Beispiel müssen Sie sie nicht verschachteln. Wenn der Ausdruck im tryBlock erfolgreich ist, wird die Funktion zurückgegeben, sodass jeder Code nach dem gesamten Versuch / Ausnahme-Block nur ausgeführt wird, wenn der erste Versuch fehlschlägt. Sie können also einfach Folgendes tun:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass
    # execution only reaches here when try block raised AttributeError
    try:
        return self.dict[item]
    except KeyError:
        print "The object doesn't have such attribute"

Sie zu verschachteln ist nicht schlecht, aber ich habe das Gefühl, es flach zu lassen, um die Struktur klarer zu machen: Sie probieren nacheinander eine Reihe von Dingen aus und geben die erste zurück, die funktioniert.

Im Übrigen möchten Sie vielleicht darüber nachdenken, ob Sie wirklich __getattribute__statt __getattr__hier verwenden möchten . Die Verwendung __getattr__vereinfacht die Arbeit, da Sie wissen, dass der normale Attributsuchprozess bereits fehlgeschlagen ist.

BrenBarn
quelle
7

Meiner Meinung nach wäre dies die pythonischste Art, damit umzugehen, obwohl und weil es Ihre Frage zur Diskussion stellt. Beachten Sie, dass dies definiert wird, __getattr__()anstatt __getattribute__()dass dies bedeutet, dass nur die "speziellen" Attribute behandelt werden müssen, die im internen Wörterbuch gespeichert sind.

def __getattr__(self, name):
    """only called when an attribute lookup in the usual places has failed"""
    try:
        return self.my_dict[name]
    except KeyError:
        raise AttributeError("some customized error message")
Martineau
quelle
2
Beachten Sie, dass das Auslösen einer Ausnahme in einem exceptBlock in Python 3 zu einer verwirrenden Ausgabe führen kann. Dies liegt daran, dass Python 3 (gemäß PEP 3134) die erste Ausnahme (the KeyError) als "Kontext" der zweiten Ausnahme (the AttributeError) verfolgt und wenn sie die erreicht In der obersten Ebene wird ein Traceback gedruckt, der beide Ausnahmen enthält. Dies kann hilfreich sein, wenn eine zweite Ausnahme nicht erwartet wurde. Wenn Sie jedoch die zweite Ausnahme absichtlich auslösen, ist dies unerwünscht. Für Python 3.3 hat PEP 415 die Möglichkeit hinzugefügt, den Kontext mithilfe von zu unterdrücken raise AttributeError("whatever") from None.
Blckknght
3
@Blckknght: In diesem Fall wäre es in Ordnung, einen Traceback zu drucken, der beide Ausnahmen enthält. Mit anderen Worten, ich denke nicht, dass Ihre pauschale Aussage, dass es immer unerwünscht ist, wahr ist. In der Verwendung hier wird es KeyErrorzu einem AttributeErrorund es ist nützlich und angemessen zu zeigen, was in einem Traceback passiert ist.
Martineau
In komplizierteren Situationen haben Sie vielleicht Recht, aber ich denke, dass Sie beim Konvertieren zwischen Ausnahmetypen häufig wissen, dass die Details der ersten Ausnahme für den externen Benutzer keine Rolle spielen. Das heißt, wenn __getattr__eine Ausnahme ausgelöst wird, ist der Fehler wahrscheinlich ein Tippfehler im Attributzugriff und kein Implementierungsfehler im Code der aktuellen Klasse. Das Anzeigen der früheren Ausnahme als Kontext kann dies durcheinander bringen. Und selbst wenn Sie den Kontext mit unterdrücken raise Whatever from None, können Sie bei Bedarf über auf die vorherige Ausnahme zugreifen ex.__context__.
Blckknght
1
Ich wollte Ihre Antwort akzeptieren, aber in der Frage war ich neugieriger, ob die Verwendung eines verschachtelten Try / Catch-Blocks eine gute Praxis ist oder nicht. Auf der anderen Seite ist es die eleganteste Lösung und ich werde sie in meinem Code verwenden. Vielen Dank Martin.
Michal
Michal: Gern geschehen. Es ist auch schneller als mit __getattribute__().
Martineau
4

In Python ist es einfacher, um Vergebung zu bitten als um Erlaubnis. Schwitzen Sie nicht bei der Behandlung verschachtelter Ausnahmen.

(Außerdem werden has*sowieso fast immer Ausnahmen unter der Abdeckung verwendet.)

Ignacio Vazquez-Abrams
quelle
4

Laut Dokumentation ist es besser, mehrere Ausnahmen durch Tupel oder ähnliches zu behandeln:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as e:
    print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise
Blairg23
quelle
2
Diese Antwort geht nicht wirklich auf die ursprüngliche Frage ein, aber für jeden, der sie liest, ist das "nackte" außer am Ende eine schreckliche Idee (normalerweise), da sie alles erfasst, einschließlich z. B. NameError & KeyboardInterrupt - was normalerweise nicht das ist, was Sie gemeint haben!
verlor am
Angesichts der Tatsache, dass der Code direkt nach der print-Anweisung dieselbe Ausnahme erneut auslöst, ist dies wirklich eine große Sache. In diesem Fall kann mehr Kontext für die Ausnahme bereitgestellt werden, ohne sie auszublenden. Wenn es nicht erneut erhöht würde, würde ich voll und ganz zustimmen, aber ich glaube nicht, dass das Risiko besteht, eine Ausnahme zu verbergen, die Sie nicht beabsichtigt haben.
NimbusScale
4

Ein gutes und einfaches Beispiel für verschachtelte Versuche / Ausnahmen könnte das Folgende sein:

import numpy as np

def divide(x, y):
    try:
        out = x/y
    except:
        try:
            out = np.inf * x / abs(x)
        except:
            out = np.nan
    finally:
        return out

Probieren Sie nun verschiedene Kombinationen aus und Sie erhalten das richtige Ergebnis:

divide(15, 3)
# 5.0

divide(15, 0)
# inf

divide(-15, 0)
# -inf

divide(0, 0)
# nan

[Natürlich haben wir Numpy, also müssen wir diese Funktion nicht erstellen]

Verwüstende Pflege
quelle
2

Eine Sache, die ich gerne vermeiden möchte, ist das Auslösen einer neuen Ausnahme beim Umgang mit einer alten. Das Lesen der Fehlermeldungen ist verwirrend.

Zum Beispiel habe ich in meinem Code ursprünglich geschrieben

try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    raise KeyError(key)

Und ich habe diese Nachricht bekommen.

>>> During handling of above exception, another exception occurred.

Was ich wollte war folgendes:

try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    pass
raise KeyError(key)

Es hat keinen Einfluss darauf, wie Ausnahmen behandelt werden. In jedem Codeblock wäre ein KeyError abgefangen worden. Dies ist lediglich eine Frage des Erhaltens von Stilpunkten.

Steve Zelaznik
quelle
Siehe die Verwendung Erhöhung der akzeptierten Antwort from Noneaber für noch mehr Style - Punkte. :)
Pianosaurus
1

Wenn try-exception-finally im finally-Block verschachtelt ist, bleibt das Ergebnis von "child" finally erhalten. Ich habe noch keine offizielle Erklärung gefunden, aber das folgende Code-Snippet zeigt dieses Verhalten in Python 3.6.

def f2():
    try:
        a = 4
        raise SyntaxError
    except SyntaxError as se:
        print('log SE')
        raise se from None
    finally:
        try:
            raise ValueError
        except ValueError as ve:
            a = 5
            print('log VE')
            raise ve from None
        finally:
            return 6       
        return a

In [1]: f2()
log SE
log VE
Out[2]: 6
Guanghua Shu
quelle
Dieses Verhalten unterscheidet sich von dem Beispiel von @ Sławomir Lenart, wenn es schließlich im Block außer Block verschachtelt ist.
Guanghua Shu
0

Ich denke nicht, dass es darum geht, pythonisch oder elegant zu sein. Es geht darum, Ausnahmen so weit wie möglich zu verhindern. Ausnahmen sollen Fehler behandeln, die in Code oder Ereignissen auftreten können, über die Sie keine Kontrolle haben. In diesem Fall haben Sie die volle Kontrolle, wenn Sie prüfen, ob ein Element ein Attribut oder ein Wörterbuch ist. Vermeiden Sie daher verschachtelte Ausnahmen und bleiben Sie bei Ihrem zweiten Versuch.

Owobeid
quelle
Aus den Dokumenten: In einer Umgebung mit mehreren Threads kann der LBYL-Ansatz (Look Before You Leap) das Risiko eingehen, eine Race-Bedingung zwischen „dem Schauen“ und „dem Springen“ einzuführen. Beispiel: Der Code, wenn Schlüssel in der Zuordnung: Rückgabezuordnung [Schlüssel] kann fehlschlagen, wenn ein anderer Thread den Schlüssel nach dem Test, aber vor der Suche aus der Zuordnung entfernt. Dieses Problem kann mit Sperren oder mithilfe des EAFP- Ansatzes
Nuno André