Ausnahme erneut mit einem anderen Typ und einer anderen Nachricht auslösen, wobei vorhandene Informationen erhalten bleiben

139

Ich schreibe ein Modul und möchte eine einheitliche Ausnahmehierarchie für die Ausnahmen haben, die es auslösen kann (z. B. Erben von einer FooErrorabstrakten Klasse für alle foospezifischen Ausnahmen des Moduls). Auf diese Weise können Benutzer des Moduls diese bestimmten Ausnahmen abfangen und bei Bedarf eindeutig behandeln. Viele der vom Modul ausgelösten Ausnahmen werden jedoch aufgrund einer anderen Ausnahme ausgelöst. z. B. Fehler bei einer Aufgabe aufgrund eines OSError in einer Datei.

Was ich brauche, ist, die gefangene Ausnahme so zu "verpacken", dass sie einen anderen Typ und eine andere Nachricht hat , so dass Informationen weiter oben in der Propagierungshierarchie verfügbar sind, je nachdem, was die Ausnahme abfängt. Aber ich möchte den vorhandenen Typ, die Nachricht und den Stack-Trace nicht verlieren. Das sind alles nützliche Informationen für jemanden, der versucht, das Problem zu beheben. Ein Ausnahmebehandler der obersten Ebene ist nicht gut, da ich versuche, die Ausnahme zu dekorieren, bevor sie den Propagierungsstapel weiter nach oben führt, und der Handler der obersten Ebene zu spät ist.

Dies wird teilweise dadurch gelöst, dass die foospezifischen Ausnahmetypen meines Moduls vom vorhandenen Typ abgeleitet werden (z. B. class FooPermissionError(OSError, FooError)). Dies macht es jedoch nicht einfacher, die vorhandene Ausnahmeinstanz in einen neuen Typ zu packen oder die Nachricht zu ändern.

In Pythons PEP 3134 „Ausnahmekettung und eingebettete Tracebacks“ wird eine in Python 3.0 akzeptierte Änderung für die Verkettung von Ausnahmeobjekten erläutert, um anzuzeigen, dass während der Behandlung einer vorhandenen Ausnahme eine neue Ausnahme ausgelöst wurde.

Was ich versuche zu tun, ist verwandt: Ich brauche es auch in früheren Python-Versionen, und ich brauche es nicht zum Verketten, sondern nur für Polymorphismus. Was ist der richtige Weg, um dies zu tun?

große Nase
quelle
Ausnahmen sind bereits vollständig polymorph - sie sind alle Unterklassen von Ausnahmen. Was versuchst du zu machen? "Andere Nachricht" ist mit einem Ausnahmebehandler der obersten Ebene ziemlich trivial. Warum wechselst du die Klasse?
S.Lott
Wie in der Frage erklärt (jetzt danke für Ihren Kommentar): Ich versuche, eine Ausnahme, die ich gefangen habe, zu dekorieren, damit sie sich mit mehr Informationen weiter ausbreitet, aber keine verliert. Ein Top-Level-Handler ist zu spät.
Bignose
Bitte werfen Sie einen Blick auf meine CausedException-Klasse, die in Python 2.x das tun kann, was Sie wollen. Auch in Python 3 kann es hilfreich sein, wenn Sie mehr als eine ursprüngliche Ausnahme als Ursache für Ihre Ausnahme angeben möchten. Vielleicht passt es Ihren Bedürfnissen.
Alfe
Für Python-2 mache ich etwas Ähnliches wie @DevinJeanpierre, aber ich füge nur eine neue String-Nachricht hinzu: except Exception as e-> raise type(e), type(e)(e.message + custom_message), sys.exc_info()[2]-> Diese Lösung stammt aus einer anderen SO-Frage . Das ist nicht schön, aber funktional.
Trevor Boyd Smith

Antworten:

197

Python 3 führte eine Ausnahmeverkettung ein (wie in PEP 3134 beschrieben ). Auf diese Weise können Sie beim Auslösen einer Ausnahme eine vorhandene Ausnahme als „Ursache“ angeben:

try:
    frobnicate()
except KeyError as exc:
    raise ValueError("Bad grape") from exc

Die abgefangene Ausnahme wird dadurch Teil der neuen Ausnahme (ist die „Ursache“ dafür) und steht für jeden Code zur Verfügung, der die neue Ausnahme abfängt.

Mit dieser Funktion wird das __cause__Attribut festgelegt. Der integrierte Ausnahmebehandler weiß auch , wie die "Ursache" und der "Kontext" der Ausnahme zusammen mit dem Traceback gemeldet werden.


In Python 2 scheint dieser Anwendungsfall keine gute Antwort zu haben (wie von Ian Bicking und Ned Batchelder beschrieben ). Schade.

große Nase
quelle
4
Beschreibt Ian Bicking nicht meine Lösung? Ich bedauere, dass ich eine so gottesfürchtige Antwort gegeben habe, aber es ist seltsam, dass diese akzeptiert wurde.
Devin Jeanpierre
1
@bignose Du hast meinen Standpunkt nicht nur dadurch verstanden, dass ich Recht hatte, sondern auch für die Verwendung von "frobnicate" :)
David M.
5
Die Verkettung von Ausnahmen ist derzeit das Standardverhalten. Tatsächlich ist es das Gegenteil des Problems, die erste Ausnahme zu unterdrücken, die Arbeit erfordert (siehe PEP 409 python.org/dev/peps/pep-0409
Chris_Rands,
1
Wie würden Sie dies in Python 2 erreichen?
Selotape
1
Es scheint gut zu funktionieren (Python 2.7)try: return 2 / 0 except ZeroDivisionError as e: raise ValueError(e)
Alex
37

Sie können sys.exc_info () verwenden, um den Traceback abzurufen, und Ihre neue Ausnahme mit diesem Traceback auslösen (wie im PEP erwähnt). Wenn Sie den alten Typ und die alte Nachricht beibehalten möchten, können Sie dies für die Ausnahme tun. Dies ist jedoch nur dann nützlich, wenn das, was Ihre Ausnahme abfängt, danach sucht.

Beispielsweise

import sys

def failure():
    try: 1/0
    except ZeroDivisionError, e:
        type, value, traceback = sys.exc_info()
        raise ValueError, ("You did something wrong!", type, value), traceback

Das ist natürlich nicht so nützlich. Wenn es so wäre, würden wir diesen PEP nicht brauchen. Ich würde es nicht empfehlen.

Devin Jeanpierre
quelle
Devin, Sie speichern dort einen Verweis auf den Traceback. Sollten Sie diesen Verweis nicht explizit löschen?
Arafangion
2
Ich habe nichts gespeichert, sondern Traceback als lokale Variable hinterlassen, die vermutlich außerhalb des Gültigkeitsbereichs liegt. Ja, es ist denkbar, dass dies nicht der Fall ist. Wenn Sie jedoch solche Ausnahmen im globalen Bereich und nicht innerhalb von Funktionen auslösen, treten größere Probleme auf. Wenn Ihre Beschwerde nur darin besteht, dass sie in einem globalen Bereich ausgeführt werden könnte, besteht die richtige Lösung nicht darin, irrelevante Kesselplatten hinzuzufügen, die erklärt werden müssen und für 99% der Anwendungen nicht relevant sind, sondern die Lösung so umzuschreiben, dass dies nicht der Fall ist ist notwendig, während es so aussieht, als ob nichts anders ist - wie ich es jetzt getan habe.
Devin Jeanpierre
4
Arafangion verweist möglicherweise auf eine Warnung in der Python-Dokumentation fürsys.exc_info() @Devin. Es heißt: "Das Zuweisen des Traceback-Rückgabewerts zu einer lokalen Variablen in einer Funktion, die eine Ausnahme behandelt, führt zu einem Zirkelverweis." In der folgenden Anmerkung heißt es jedoch, dass der Zyklus seit Python 2.2 bereinigt werden kann, es jedoch effizienter ist, ihn nur zu vermeiden.
Don Kirkby
5
Weitere Details zu verschiedenen Möglichkeiten, Ausnahmen in Python von zwei aufgeklärten Pythonisten erneut auszulösen: Ian Bicking und Ned Batchelder
Rodrigue
11

Sie können Ihren eigenen Ausnahmetyp erstellen, der die von Ihnen abgefangene Ausnahme erweitert .

class NewException(CaughtException):
    def __init__(self, caught):
        self.caught = caught

try:
    ...
except CaughtException as e:
    ...
    raise NewException(e)

Aber die meiste Zeit denke ich, dass es einfacher wäre, die Ausnahme abzufangen, damit raiseumzugehen und entweder die ursprüngliche Ausnahme (und den Traceback beizubehalten) oder raise NewException(). Wenn ich Ihren Code aufrufen würde und eine Ihrer benutzerdefinierten Ausnahmen erhalten würde, würde ich erwarten, dass Ihr Code bereits alle Ausnahmen behandelt hat, die Sie abfangen mussten. Daher muss ich nicht selbst darauf zugreifen.

Bearbeiten: Ich habe diese Analyse der Möglichkeiten gefunden, Ihre eigene Ausnahme auszulösen und die ursprüngliche Ausnahme beizubehalten. Keine schönen Lösungen.

Nikhil Chelliah
quelle
1
Der von mir beschriebene Anwendungsfall dient nicht zur Behandlung der Ausnahme. Es geht speziell darum, nicht damit umzugehen, sondern einige zusätzliche Informationen (eine zusätzliche Klasse und eine neue Nachricht) hinzuzufügen, damit sie weiter oben im Aufrufstapel verarbeitet werden können.
Bignose
2

Ich fand auch, dass ich oft etwas "Wrapping" für Fehler brauche.

Dies umfasste sowohl einen Funktionsumfang als auch manchmal nur einige Zeilen innerhalb einer Funktion.

Erstellt einen Wrapper zur Verwendung von a decoratorund context manager:


Implementierung

import inspect
from contextlib import contextmanager, ContextDecorator
import functools    

class wrap_exceptions(ContextDecorator):
    def __init__(self, wrapper_exc, *wrapped_exc):
        self.wrapper_exc = wrapper_exc
        self.wrapped_exc = wrapped_exc

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not exc_type:
            return
        try:
            raise exc_val
        except self.wrapped_exc:
            raise self.wrapper_exc from exc_val

    def __gen_wrapper(self, f, *args, **kwargs):
        with self:
            for res in f(*args, **kwargs):
                yield res

    def __call__(self, f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            with self:
                if inspect.isgeneratorfunction(f):
                    return self.__gen_wrapper(f, *args, **kw)
                else:
                    return f(*args, **kw)
        return wrapper

Anwendungsbeispiele

Dekorateur

@wrap_exceptions(MyError, IndexError)
def do():
   pass

beim Aufruf doMethode, keine Sorge über IndexError, nurMyError

try:
   do()
except MyError as my_err:
   pass # handle error 

Kontextmanager

def do2():
   print('do2')
   with wrap_exceptions(MyError, IndexError):
       do()

innen do2, in der context manager, wenn IndexErrores angehoben wird, wird es eingewickelt und angehobenMyError

Aaron_ab
quelle
1
Bitte erläutern Sie, wie sich "Wrapping" auf die ursprüngliche Ausnahme auswirkt. Was ist der Zweck Ihres Codes und welches Verhalten ermöglicht er?
Alexis
@alexis - fügte einige Beispiele hinzu, hoffe es hilft
Aaron_ab
-2

Die einfachste Lösung für Ihre Anforderungen sollte folgende sein:

try:
     upload(file_id)
except Exception as upload_error:
     error_msg = "Your upload failed! File: " + file_id
     raise RuntimeError(error_msg, upload_error)

Auf diese Weise können Sie später Ihre Nachricht und den spezifischen Fehler drucken, der von der Upload-Funktion ausgelöst wird

lustige_dev
quelle
1
Das fängt das Ausnahmeobjekt ab und wirft es dann weg. Nein, es entspricht nicht den Anforderungen der Frage. Die Frage stellt , wie man halten die bestehende Ausnahme und lassen die gleiche Ausnahme, mit allen nützlichen Informationen enthält, um den Stapel ausbreitet, um fortzufahren.
Bignose