Hinzufügen von Informationen zu einer Ausnahme?

142

Ich möchte so etwas erreichen:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

Aber was ich bekomme ist:

Traceback..
  IOError('Stuff')

Irgendwelche Hinweise, wie dies erreicht werden kann? Wie geht das in Python 2 und 3?

Anijhaw
quelle
Bei der Suche nach Dokumentation für das Exception- messageAttribut habe ich diese SO-Frage gefunden: BaseException.message ist in Python 2.6 veraltet , was darauf hindeutet, dass die Verwendung jetzt nicht empfohlen wird (und warum sie nicht in den Dokumenten enthalten ist).
Martineau
Leider scheint dieser Link nicht mehr zu funktionieren.
Michael Scott Cuthbert
1
@ MichaelScottCuthbert hier ist eine gute Alternative: itmaybeahack.com/book/python-2.6/html/p02/…
Niels Keurentjes
Hier ist eine wirklich gute Erklärung für den Status des Nachrichtenattributs und seine Beziehung zum Argumentattribut und zu PEP 352 . Es ist aus dem kostenlosen Buch Building Skills in Python von Steven F. Lott.
Martineau

Antworten:

117

Ich würde es so machen, so dass das Ändern des Typs foo()nicht auch das Ändern des Typs erfordert bar().

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

Update 1

Hier ist eine geringfügige Änderung, die den ursprünglichen Traceback beibehält:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

Update 2

Für Python 3.x ist der Code in meinem ersten Update syntaktisch falsch und die Idee, ein messageAttribut zu haben, BaseExceptionwurde bei einer Änderung von PEP 352 am 16.05.2012 zurückgezogen (mein erstes Update wurde am 12.03.2012 veröffentlicht). . Derzeit müssen Sie in Python 3.5.2 ohnehin etwas in diese Richtung tun, um den Traceback beizubehalten und den Typ der Ausnahme in der Funktion nicht fest zu codieren bar(). Beachten Sie auch, dass es die Zeile geben wird:

During handling of the above exception, another exception occurred:

in den Traceback-Meldungen angezeigt.

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

Update 3

Ein Kommentator fragte, ob es einen Weg gäbe, der sowohl in Python 2 als auch in Python 3 funktionieren würde. Obwohl die Antwort aufgrund der Syntaxunterschiede "Nein" zu sein scheint, gibt es einen Weg, dies zu umgehen , indem eine Hilfsfunktion wie reraise()im sixAdd-In verwendet wird. auf Modul. Wenn Sie die Bibliothek aus irgendeinem Grund lieber nicht verwenden möchten, finden Sie unten eine vereinfachte Standalone-Version.

Beachten Sie auch, dass, da die Ausnahme innerhalb der reraise()Funktion erneut ausgelöst wird, diese in jedem Traceback angezeigt wird, aber das Endergebnis das ist, was Sie wollen.

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')
Martineau
quelle
3
Dadurch wird die Rückverfolgung verloren und der Punkt, an dem einer vorhandenen Ausnahme Informationen hinzugefügt werden, wird zunichte gemacht. Außerdem funktioniert es nicht mit Ausnahmen mit ctor, die> 1 Argumente annehmen (der Typ kann nicht von der Stelle aus gesteuert werden, an der Sie die Ausnahme abfangen).
Václav Slavík
1
@ Václav: Es ist ziemlich einfach zu verhindern, dass der Backtrace verloren geht - wie in dem Update gezeigt, das ich hinzugefügt habe. Dies behandelt zwar immer noch nicht alle denkbaren Ausnahmen, funktioniert jedoch für Fälle, die den in der Frage des OP gezeigten ähnlich sind.
Martineau
1
Das ist nicht ganz richtig. Wenn Typ (e) überschreibt __str__, erhalten Sie möglicherweise unerwünschte Ergebnisse. Beachten Sie auch, dass das zweite Argument an den Konstruktor übergeben wird, der durch das erste Argument angegeben wird, was zu einem etwas unsinnigen Ergebnis führt type(e)(type(e)(e.message). Drittens wird e.message zugunsten von e.args [0] abgelehnt.
Bukzor
1
Gibt es also keine tragbare Methode, die sowohl in Python 2 als auch in Python 3 funktioniert?
Elias Dorneles
1
@martineau Was ist der Zweck des Imports innerhalb des Ausnahmeblocks? Ist dies, um Speicher zu sparen, indem nur bei Bedarf importiert wird?
AllTradesJack
114

Falls Sie hierher gekommen sind, um nach einer Lösung für Python 3 zu suchen, heißt es im Handbuch :

Wenn Sie eine neue Ausnahme raiseauslösen (anstatt ein Bare zu verwenden, um die aktuell behandelte Ausnahme erneut auszulösen), kann der implizite Ausnahmekontext durch die Verwendung von from with raise durch eine explizite Ursache ergänzt werden:

raise new_exc from original_exc

Beispiel:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

Was am Ende so aussieht:

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

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

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

Verwandeln Sie eine völlig unscheinbare TypeErrorNachricht in eine nette Nachricht mit Hinweisen auf eine Lösung, ohne die ursprüngliche Ausnahme durcheinander zu bringen.

Chris
quelle
14
Dies ist die beste Lösung, da die resultierende Ausnahme auf die ursprüngliche Ursache verweist und detailliertere Informationen liefert.
JT
Gibt es eine Lösung, mit der wir eine Nachricht hinzufügen können, aber dennoch keine neue Ausnahme auslösen? Ich meine, erweitern Sie einfach die Nachricht der Ausnahmeinstanz.
edcSam
Yaa ~~ es funktioniert, aber es fühlt sich wie etwas an, das mir nicht angetan werden sollte. Die Nachricht wird in gespeichert e.args, aber das ist ein Tupel, daher kann sie nicht geändert werden. Kopieren Sie also zuerst argsin eine Liste, ändern Sie sie und kopieren Sie sie dann als Tupel zurück:args = list(e.args) args[0] = 'bar' e.args = tuple(args)
Chris
27

Angenommen, Sie möchten oder können foo () nicht ändern, können Sie Folgendes tun:

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

Dies ist in der Tat die einzige Lösung, die das Problem in Python 3 ohne eine hässliche und verwirrende Meldung "Während der Behandlung der obigen Ausnahme ist eine weitere Ausnahme aufgetreten" löst.

Falls die Zeile zum erneuten Erhöhen zur Stapelverfolgung hinzugefügt werden soll, reicht das Schreiben raise eanstelle von aus raise.

Steve Howard
quelle
aber in diesem Fall, wenn sich die Ausnahme in foo ändert, muss ich auch die Leiste ändern, oder?
Anijhaw
1
Wenn Sie Exception (oben bearbeitet) abfangen, können Sie jede Standardbibliotheksausnahme abfangen (sowie solche, die von Exception erben und Exception .__ init__ aufrufen).
Steve Howard
6
e.args = ('mynewstr' + e.args[0],) + e.args[1:]
Um
1
@ nmz787 Dies ist die beste Lösung für Python 3. Was genau ist dein Fehler?
Christian
1
@ Dublow und Martineau Ich habe Ihre Vorschläge in eine Bearbeitung aufgenommen.
Christian
9

Ich mag bisher nicht alle gegebenen Antworten. Sie sind imho immer noch zu ausführlich. In jeder Code- und Nachrichtenausgabe.

Alles, was ich haben möchte, ist der Stacktrace, der auf die Quellausnahme verweist, kein Ausnahmematerial dazwischen, also keine Erstellung neuer Ausnahmen, nur das Original mit allen relevanten Stack-Frame-Zuständen, die dorthin geführt haben , erneut zu erhöhen .

Steve Howard gab eine nette Antwort, die ich erweitern möchte, nein, reduzieren ... nur auf Python 3.

except Exception as e:
    e.args = ("Some failure state", *e.args)
    raise

Das einzig Neue ist das Erweitern / Auspacken der Parameter , wodurch es klein und einfach genug für mich ist, es zu verwenden.

Versuch es:

foo = None

try:
    try:
        state = "bar"
        foo.append(state)

    except Exception as e:
        e.args = ("Appending '"+state+"' failed", *e.args)
        raise

    print(foo[0]) # would raise too

except Exception as e:
    e.args = ("print(foo) failed: " + str(foo), *e.args)
    raise

Dies wird Ihnen geben:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")

Ein einfacher hübscher Druck könnte so etwas wie sein

print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))
6EQUJ5
quelle
5

Ein praktischer Ansatz, den ich verwendet habe, besteht darin, das Klassenattribut als Speicher für Details zu verwenden, da auf das Klassenattribut sowohl vom Klassenobjekt als auch von der Klasseninstanz aus zugegriffen werden kann:

class CustomError(Exception):
    def __init__(self, details: Dict):
        self.details = details

Dann in Ihrem Code:

raise CustomError({'data': 5})

Und wenn Sie einen Fehler bemerken:

except CustomError as e:
    # Do whatever you want with the exception instance
    print(e.details)
Kee
quelle
Nicht wirklich nützlich, da das OP anfordert, dass die Details als Teil der Stapelverfolgung gedruckt werden, wenn die ursprüngliche Ausnahme ausgelöst und nicht abgefangen wird.
Cowbert
Ich finde die Lösung gut. Aber die Beschreibung ist nicht wahr. Klassenattribute werden in Instanzen kopiert, wenn Sie sie instanziieren. Wenn Sie also das Attribut "Details" der Instanz ändern, lautet das Klassenattribut weiterhin "Keine". Jedenfalls wollen wir dieses Verhalten hier.
Adam Wallner
2

Im Gegensatz zu früheren Antworten funktioniert dies trotz wirklich schlechter Ausnahmen __str__. Es tut die Art jedoch ändern, um Faktor aus nicht hilfreich __str__Implementierungen.

Ich würde immer noch gerne eine zusätzliche Verbesserung finden, die den Typ nicht ändert.

from contextlib import contextmanager
@contextmanager
def helpful_info():
    try:
        yield
    except Exception as e:
        class CloneException(Exception): pass
        CloneException.__name__ = type(e).__name__
        CloneException.__module___ = type(e).__module__
        helpful_message = '%s\n\nhelpful info!' % e
        import sys
        raise CloneException, helpful_message, sys.exc_traceback


class BadException(Exception):
    def __str__(self):
        return 'wat.'

with helpful_info():
    raise BadException('fooooo')

Der ursprüngliche Traceback und Typ (Name) bleiben erhalten.

Traceback (most recent call last):
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "re_raise.py", line 5, in helpful_info
    yield
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
__main__.BadException: wat.

helpful info!
Bukzor
quelle
2

Ich werde einen Codeausschnitt bereitstellen, den ich häufig verwende, wenn ich einer Ausnahme zusätzliche Informationen hinzufügen möchte. Ich arbeite sowohl in Python 2.7 als auch in 3.6.

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

Der obige Code führt zu folgender Ausgabe:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers


Ich weiß, dass dies ein wenig von dem in der Frage angegebenen Beispiel abweicht, aber ich hoffe trotzdem, dass jemand es nützlich findet.

Pedro M Duarte
quelle
1

Sie können Ihre eigene Ausnahme definieren, die von einer anderen erbt, und einen eigenen Konstruktor erstellen, um den Wert festzulegen.

Beispielsweise:

class MyError(Exception):
   def __init__(self, value):
     self.value = value
     Exception.__init__(self)

   def __str__(self):
     return repr(self.value)
Alexander Kiselev
quelle
2
Die Adresse muss nicht geändert / an die messageursprüngliche Ausnahme angehängt werden (könnte aber behoben werden, denke ich).
Martineau
-6

Vielleicht

except Exception as e:
    raise IOError(e.message + 'happens at %s'%arg1)
Malvolio
quelle