"Innere Ausnahme" (mit Traceback) in Python?

145

Mein Hintergrund ist C # und ich habe erst kürzlich angefangen, in Python zu programmieren. Wenn eine Ausnahme ausgelöst wird, möchte ich sie normalerweise in eine andere Ausnahme einschließen, die weitere Informationen hinzufügt und gleichzeitig die vollständige Stapelverfolgung anzeigt. In C # ist das ganz einfach, aber wie mache ich das in Python?

Z.B. in C # würde ich so etwas machen:

try
{
  ProcessFile(filePath);
}
catch (Exception ex)
{
  throw new ApplicationException("Failed to process file " + filePath, ex);
}

In Python kann ich etwas Ähnliches tun:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file ' + filePath, e)

... aber das verliert den Traceback der inneren Ausnahme!

Bearbeiten: Ich möchte beide Ausnahmemeldungen und beide Stapelspuren sehen und die beiden korrelieren. Das heißt, ich möchte in der Ausgabe sehen, dass hier die Ausnahme X und dort die Ausnahme Y aufgetreten ist - genau wie in C #. Ist das in Python 2.6 möglich? Das Beste, was ich bisher tun kann (basierend auf Glenn Maynards Antwort), ist:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

Dies umfasst sowohl die Nachrichten als auch die Tracebacks, zeigt jedoch nicht an, welche Ausnahme wo im Traceback aufgetreten ist.

EMP
quelle
3
Die akzeptierte Antwort ist veraltet. Vielleicht sollten Sie in Betracht ziehen, eine andere zu akzeptieren.
Aaron Hall
1
@ AaronHall OP wurde leider seit 2015 nicht mehr gesehen.
Antti Haapala

Antworten:

136

Python 2

Es ist einfach; Übergeben Sie den Traceback als drittes Argument.

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

Tun Sie dies immer, wenn Sie eine Ausnahme abfangen und eine andere erneut auslösen.

Glenn Maynard
quelle
4
Vielen Dank. Dadurch bleibt der Traceback erhalten, aber die Fehlermeldung der ursprünglichen Ausnahme geht verloren. Wie sehe ich beide Nachrichten und beide Rückverfolgungen?
EMP
6
raise MyException(str(e)), ...usw.
Glenn Maynard
23
Python 3 fügt hinzu raise E() from tbund.with_traceback(...)
Dima Tisnek
3
@GlennMaynard ist eine ziemlich alte Frage, aber das mittlere Argument von raiseist der Wert, der an die Ausnahme übergeben werden soll (falls das erste Argument eine Ausnahmeklasse und keine Instanz ist). Wenn Sie also Ausnahmen austauschen möchten, anstatt dies zu tun raise MyException(str(e)), None, sys.exc_info()[2], ist es besser, Folgendes zu verwenden : raise MyException, e.args, sys.exc_info()[2].
Bgusach
8
Eine Python2- und 3-kompatible Methode ist mit dem zukünftigen Paket möglich: python-future.org/compatible_idioms.html#raising-exceptions ZB from future.utils import raise_und raise_(ValueError, None, sys.exc_info()[2]).
Jtpereyda
239

Python 3

In Python 3 können Sie Folgendes tun:

try:
    raise MyExceptionToBeWrapped("I have twisted my ankle")

except MyExceptionToBeWrapped as e:

    raise MyWrapperException("I'm not in a good shape") from e

Dies wird ungefähr so ​​etwas hervorbringen:

   Traceback (most recent call last):
   ...
   MyExceptionToBeWrapped: ("I have twisted my ankle")

The above exception was the direct cause of the following exception:

   Traceback (most recent call last):
   ...
   MyWrapperException: ("I'm not in a good shape")
Alexei Tenitski
quelle
17
raise ... from ...ist in der Tat der richtige Weg, dies in Python 3 zu tun. Dies erfordert mehr Upvotes.
Nakedible
NakedibleIch denke, das liegt daran, dass die meisten Leute Python 3 leider immer noch nicht verwenden.
Tim Ludwinski
Dies scheint sogar bei Verwendung von 'from' in Python 3 zu passieren
Steve Vermeulen
Könnte nach Python 2 zurückportiert werden. Ich hoffe, es wird eines Tages.
Marcin Wojnarski
4
@ogrisel Sie können das futurePaket verwenden, um dies zu erreichen: python-future.org/compatible_idioms.html#raising-exceptions ZB from future.utils import raise_und raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda
19

Python 3 hat die Klausel raise... from, um Ausnahmen zu verketten. Glenns Antwort ist großartig für Python 2.7, verwendet jedoch nur den Traceback der ursprünglichen Ausnahme und wirft die Fehlermeldung und andere Details weg. Hier sind einige Beispiele in Python 2.7, die Kontextinformationen aus dem aktuellen Bereich in die Fehlermeldung der ursprünglichen Ausnahme einfügen, andere Details jedoch beibehalten.

Bekannter Ausnahmetyp

try:
    sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
    self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
    _, ex, traceback = sys.exc_info()
    message = "Connecting to '%s': %s." % (config['connection'],
                                           ex.strerror)
    raise IOError, (ex.errno, message), traceback

Diese raiseAnweisung verwendet den Ausnahmetyp als ersten Ausdruck, die Argumente des Ausnahmeklassenkonstruktors in einem Tupel als zweiten Ausdruck und den Traceback als dritten Ausdruck. Wenn Sie früher als Python 2.2 ausgeführt werden, lesen Sie die Warnungen unter sys.exc_info().

Beliebiger Ausnahmetyp

Hier ist ein weiteres Beispiel, das allgemeiner ist, wenn Sie nicht wissen, welche Ausnahmen Ihr Code möglicherweise abfangen muss. Der Nachteil ist, dass es den Ausnahmetyp verliert und nur einen RuntimeError auslöst. Sie müssen das tracebackModul importieren .

except Exception:
    extype, ex, tb = sys.exc_info()
    formatted = traceback.format_exception_only(extype, ex)[-1]
    message = "Importing row %d, %s" % (rownum, formatted)
    raise RuntimeError, message, tb

Ändern Sie die Nachricht

Hier ist eine weitere Option, wenn Sie mit dem Ausnahmetyp Kontext hinzufügen können. Sie können die Nachricht der Ausnahme ändern und dann erneut erhöhen.

import subprocess

try:
    final_args = ['lsx', '/home']
    s = subprocess.check_output(final_args)
except OSError as ex:
    ex.strerror += ' for command {}'.format(final_args)
    raise

Dadurch wird der folgende Stack-Trace generiert:

Traceback (most recent call last):
  File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
    s = subprocess.check_output(final_args)
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']

Sie können sehen, dass die Zeile angezeigt wird, check_output()in der aufgerufen wurde, aber die Ausnahmemeldung enthält jetzt die Befehlszeile.

Don Kirkby
quelle
1
Woher kommt das ex.strerror? Ich kann keinen relevanten Treffer dafür in den Python-Dokumenten finden. Sollte es nicht sein str(ex)?
Henrik Heimbuerger
1
IOErrorwird abgeleitet von EnvironmentError@hheimbuerger, das die Attribute errornound bereitstellt strerror.
Don Kirkby
Wie würde ich einen beliebigen Error, z. B. ValueError, RuntimeErrordurch Fangen in einen einwickeln Exception? Wenn ich Ihre Antwort für diesen Fall wiedergebe, geht die Stapelverfolgung verloren.
Karl Richter
Ich bin mir nicht sicher, was du fragst, @karl. Können Sie ein Beispiel in einer neuen Frage posten und dann von hier aus darauf verlinken?
Don Kirkby
Ich habe mein Duplikat der Frage des OP unter stackoverflow.com/questions/23157766/… mit einer Klarstellung bearbeitet, die Ihre Antwort direkt berücksichtigt. Wir sollten dort diskutieren :)
Karl Richter
12

In Python 3.x :

raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)

oder einfach

except Exception:
    raise MyException()

Dies wird sich verbreiten, MyExceptionaber beide Ausnahmen drucken, wenn es nicht behandelt wird.

In Python 2.x :

raise Exception, 'Failed to process file ' + filePath, e

Sie können das Drucken beider Ausnahmen verhindern, indem Sie das __context__Attribut beenden. Hier schreibe ich einen Kontextmanager, der dies verwendet, um Ihre Ausnahme im laufenden Betrieb abzufangen und zu ändern: ( Weitere Informationen zur Funktionsweise finden Sie unter http://docs.python.org/3.1/library/stdtypes.html. )

try: # Wrap the whole program into the block that will kill __context__.

    class Catcher(Exception):
        '''This context manager reraises an exception under a different name.'''

        def __init__(self, name):
            super().__init__('Failed to process code in {!r}'.format(name))

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is not None:
                self.__traceback__ = exc_tb
                raise self

    ...


    with Catcher('class definition'):
        class a:
            def spam(self):
                # not really pass, but you get the idea
                pass

            lut = [1,
                   3,
                   17,
                   [12,34],
                   5,
                   _spam]


        assert a().lut[-1] == a.spam

    ...


except Catcher as e:
    e.__context__ = None
    raise
ilya n.
quelle
4
TypeError: Raise: Arg 3 muss ein Traceback sein oder None
Glenn Maynard
Entschuldigung, ich habe einen Fehler gemacht, irgendwie dachte ich, dass es auch Ausnahmen akzeptiert und deren Traceback-Attribut automatisch erhält. Wie pro docs.python.org/3.1/reference/... sollte diese e .__ traceback__ sein
Ilya n.
1
@ilyan.: Python 2 hat kein e.__traceback__Attribut!
Jan Hudec
5

Ich glaube nicht, dass Sie dies in Python 2.x tun können, aber etwas Ähnliches wie diese Funktionalität ist Teil von Python 3. Aus PEP 3134 :

In der heutigen Python-Implementierung bestehen Ausnahmen aus drei Teilen: dem Typ, dem Wert und dem Traceback. Das Modul 'sys' macht die aktuelle Ausnahme in drei parallelen Variablen, exc_type, exc_value und exc_traceback, verfügbar. Die Funktion sys.exc_info () gibt ein Tupel dieser drei Teile zurück, und die Anweisung 'raise' hat eine Form mit drei Argumenten diese drei Teile. Das Manipulieren von Ausnahmen erfordert häufig das parallele Übergeben dieser drei Dinge, was mühsam und fehleranfällig sein kann. Darüber hinaus kann die Anweisung "Except" nur Zugriff auf den Wert gewähren, nicht auf den Traceback. Durch Hinzufügen des Attributs ' traceback ' zu Ausnahmewerten können alle Ausnahmeinformationen von einem einzigen Ort aus aufgerufen werden.

Vergleich mit C #:

Ausnahmen in C # enthalten eine schreibgeschützte Eigenschaft 'InnerException', die möglicherweise auf eine andere Ausnahme verweist. In der Dokumentation [10] heißt es: "Wenn eine Ausnahme X als direktes Ergebnis einer vorherigen Ausnahme Y ausgelöst wird, sollte die InnerException-Eigenschaft von X einen Verweis auf Y enthalten." Diese Eigenschaft wird von der VM nicht automatisch festgelegt. Stattdessen verwenden alle Ausnahmekonstruktoren ein optionales Argument 'innerException', um es explizit festzulegen. Das Attribut ' Ursache ' erfüllt den gleichen Zweck wie InnerException, aber dieses PEP schlägt eine neue Form von 'Erhöhen' vor, anstatt die Konstruktoren aller Ausnahmen zu erweitern. C # bietet auch eine GetBaseException-Methode, die direkt zum Ende der InnerException-Kette springt.

Beachten Sie auch, dass Java, Ruby und Perl 5 diese Art von Dingen ebenfalls nicht unterstützen. Nochmals zitieren:

Wie bei anderen Sprachen verwerfen sowohl Java als auch Ruby die ursprüngliche Ausnahme, wenn eine andere Ausnahme in einer Klausel 'catch' / 'retten' oder 'endlich' / 'sicherstellen' auftritt. In Perl 5 fehlt die integrierte strukturierte Ausnahmebehandlung. Für Perl 6 schlägt RFC-Nummer 88 [9] einen Ausnahmemechanismus vor, der implizit verkettete Ausnahmen in einem Array mit dem Namen @@ beibehält.

ire_and_curses
quelle
Aber natürlich können Sie in Perl5 einfach "qq {OH NOES! $ @} Bekennen" sagen und nicht den Stack-Trace der anderen Ausnahme verlieren. Oder Sie können Ihren eigenen Typ implementieren, der die Ausnahme beibehält.
Jrockway
4

Für maximale Kompatibilität zwischen Python 2 und 3 können Sie raise_fromin der sixBibliothek verwenden. https://six.readthedocs.io/#six.raise_from . Hier ist Ihr Beispiel (aus Gründen der Übersichtlichkeit leicht modifiziert):

import six

try:
  ProcessFile(filePath)
except Exception as e:
  six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)
LexH
quelle
2

Vielleicht könnten Sie die relevanten Informationen greifen und weitergeben? Ich denke so etwas wie:

import traceback
import sys
import StringIO

class ApplicationError:
    def __init__(self, value, e):
        s = StringIO.StringIO()
        traceback.print_exc(file=s)
        self.value = (value, s.getvalue())

    def __str__(self):
        return repr(self.value)

try:
    try:
        a = 1/0
    except Exception, e:
        raise ApplicationError("Failed to process file", e)
except Exception, e:
    print e
brool
quelle
2

Angenommen:

  • Sie benötigen eine Lösung, die für Python 2 funktioniert (für reines Python 3 siehe raise ... fromLösung)
  • Ich möchte nur die Fehlermeldung anreichern, z. B. einen zusätzlichen Kontext bereitstellen
  • brauche die vollständige Stapelverfolgung

Sie können eine einfache Lösung aus den Dokumenten https://docs.python.org/3/tutorial/errors.html#raising-exceptions verwenden :

try:
    raise NameError('HiThere')
except NameError:
    print 'An exception flew by!' # print or log, provide details about context
    raise # reraise the original exception, keeping full stack trace

Die Ausgabe:

An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

Es sieht so aus, als wäre das Schlüsselstück das vereinfachte "Raise" -Schlüsselwort, das für sich allein steht. Dadurch wird die Ausnahme im Ausnahmeblock erneut ausgelöst.

eine Leckerei
quelle
Dies ist die Python 2 & 3-kompatible Lösung! Vielen Dank!
Andy Chase
Ich denke, die Idee war, eine andere Art von Ausnahme zu machen.
Tim Ludwinski
2
Dies ist keine Kette verschachtelter Ausnahmen, sondern nur eine Ausnahme
Karl Richter
Dies ist die beste Python 2-Lösung, wenn Sie nur die Ausnahmemeldung anreichern und den vollständigen Stack-Trace haben müssen!
GeekQ
Was ist der Unterschied zwischen Raise und Raise von
Variable