Wie kann ich dieselbe Ausnahme mit einer benutzerdefinierten Nachricht in Python auslösen?

145

Ich habe diesen tryBlock in meinem Code:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

Genau genommen ziehe ich tatsächlich einen anderen auf ValueError , nicht den ValueErrorvon do_something...(), der errin diesem Fall als geworfen bezeichnet wird. Wie hänge ich eine benutzerdefinierte Nachricht an err? Ich versuche den folgenden Code, aber er schlägt fehl, weil erreine ValueError Instanz nicht aufrufbar ist:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)
Kit
quelle
13
@Hamish, das Anhängen zusätzlicher Informationen und das erneute Auslösen von Ausnahmen kann beim Debuggen sehr hilfreich sein.
Johan Lundberg
@ Joan Absolut - und dafür ist ein Stacktrace gedacht. Ich kann nicht ganz verstehen, warum Sie die vorhandene Fehlermeldung bearbeiten würden, anstatt einen neuen Fehler auszulösen.
Hamish
@ Hamish. Sicher, aber Sie können andere Sachen hinzufügen. Schauen Sie sich für Ihre Frage meine Antwort und das Beispiel von UnicodeDecodeError an. Wenn Sie Kommentare dazu haben, kommentieren Sie vielleicht stattdessen meine Antwort.
Johan Lundberg
Mögliches Duplikat Hinzufügen von Informationen zu einer Ausnahme?
Trevor Boyd Smith
1
@ Kit es ist 2020 und Python 3 ist überall. Warum ändern Sie nicht die akzeptierte Antwort in Bens Antwort :-)
mit

Antworten:

88

Update: Überprüfen Sie für Python 3 Bens Antwort


So hängen Sie eine Nachricht an die aktuelle Ausnahme an und lösen sie erneut aus: (Der äußere Versuch / die Ausnahme dient nur dazu, den Effekt anzuzeigen.)

Für Python 2.x mit x> = 6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

Dies wird auch das Richtige tun , wenn errsich ableiten aus ValueError. Zum Beispiel UnicodeDecodeError.

Beachten Sie, dass Sie hinzufügen können, was Sie möchten err. Zum Beispiel err.problematic_array=[1,2,3].


Bearbeiten: @Ducan zeigt in einem Kommentar, dass das oben Gesagte nicht mit Python 3 funktioniert, da .messagees kein Mitglied von ist ValueError. Stattdessen können Sie dies verwenden (gültiges Python 2.6 oder höher oder 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Edit2:

Je nach Zweck können Sie die zusätzlichen Informationen auch unter Ihrem eigenen Variablennamen hinzufügen. Für Python2 und Python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info
Johan Lundberg
quelle
9
Da Sie sich die Mühe gemacht haben, die Ausnahmebehandlung im Python 3-Stil zu verwenden print, sollten Sie wahrscheinlich beachten, dass Ihr Code in Python 3.x nicht funktioniert, da messageAusnahmen kein Attribut enthalten. err.args = (err.args[0] + " hello",) + err.args[1:]funktioniert möglicherweise zuverlässiger (und konvertiert dann einfach in eine Zeichenfolge, um die Nachricht zu erhalten).
Duncan
1
Leider gibt es keine Garantie dafür, dass args [0] ein Zeichenfolgentyp ist, der eine Fehlermeldung darstellt - "Das Tupel der Argumente, die dem Ausnahmekonstruktor übergeben werden. Einige integrierte Ausnahmen (wie IOError) erwarten eine bestimmte Anzahl von Argumenten und weisen ihnen eine besondere Bedeutung zu die Elemente dieses Tupels, während andere normalerweise nur mit einer einzelnen Zeichenfolge aufgerufen werden, die eine Fehlermeldung ausgibt. " Der Code funktioniert also nicht. Arg [0] ist keine Fehlermeldung (es kann sich um ein int oder eine Zeichenfolge handeln, die einen Dateinamen darstellt).
Trent
1
@ Taras, interessant. Haben Sie eine Referenz dazu? Dann würde ich einem völlig neuen Mitglied hinzufügen: err.my_own_extra_info. Oder kapseln Sie alles in meiner eigenen Ausnahme und behalten Sie die neuen und die ursprünglichen Informationen bei.
Johan Lundberg
2
Ein echtes Beispiel dafür, wann args [0] keine Fehlermeldung ist - docs.python.org/2/library/exceptions.html - "Ausnahme EnvironmentError Die Basisklasse für Ausnahmen, die außerhalb des Python-Systems auftreten können: IOError, OSError. Wenn Ausnahmen dieses Typs mit einem 2-Tupel erstellt werden, ist das erste Element im Attribut errno der Instanz verfügbar (es wird als Fehlernummer angenommen) und das zweite Element im Attribut strerror (normalerweise das zugehörige Attribut) Fehlermeldung). Das Tupel selbst ist auch für das Attribut args verfügbar. "
Trent
2
Ich verstehe das überhaupt nicht. Der einzige Grund, warum das Setzen des .messageAttributs hier irgendetwas bewirkt, ist, dass dieses Attribut explizit gedruckt wird. Wenn Sie die Ausnahme , ohne zu kontrollieren und den Druck zu erhöhen, würden Sie nicht das sehen .messageAttribut tun etwas Sinnvolles.
DanielSank
170

Wenn Sie das Glück haben, nur Python 3.x zu unterstützen, wird dies wirklich zu einer Sache der Schönheit :)

erhöhen von

Wir können die Ausnahmen mit Raise from verketten .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

In diesem Fall hat die Ausnahme, die Ihr Anrufer abfangen würde, die Zeilennummer des Ortes, an dem wir unsere Ausnahme auslösen.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

Beachten Sie, dass die unterste Ausnahme nur den Stacktrace enthält, von dem aus wir unsere Ausnahme ausgelöst haben. Ihr Anrufer kann weiterhin die ursprüngliche Ausnahme erhalten, indem er auf das __cause__Attribut der Ausnahme zugreift, die er abfängt.

with_traceback

Oder Sie können with_traceback verwenden .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

Bei Verwendung dieses Formulars hat die Ausnahme, die Ihr Anrufer abfangen würde, den Traceback, von dem der ursprüngliche Fehler aufgetreten ist.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

Beachten Sie, dass die unterste Ausnahme die Zeile enthält, in der wir die ungültige Division durchgeführt haben, sowie die Zeile, in der wir die Ausnahme erneut auslösen.

Ben
quelle
1
Ist es möglich, einer Ausnahme eine benutzerdefinierte Nachricht ohne den zusätzlichen Traceback hinzuzufügen? Kann raise Exception('Smelly socks') from ebeispielsweise so geändert werden, dass nur "Smelly Socks" als Kommentar zum ursprünglichen Traceback hinzugefügt werden, anstatt einen eigenen neuen Traceback einzuführen.
Joelostblom
Das ist das Verhalten, das Sie aus Johan Lundbergs Antwort erhalten
Ben
3
das ist wirklich schön. Danke dir.
Allanberry
3
Das erneute Auslösen einer neuen Ausnahme oder das Auslösen von Kettenausnahmen mit neuen Nachrichten führt in vielen Fällen zu mehr Verwirrung als erforderlich. Ausnahmen sind an sich komplex zu handhaben. Eine bessere Strategie besteht darin, Ihre Nachricht nach Möglichkeit wie in err.args + = ("message",) an das Argument der ursprünglichen Ausnahme anzuhängen und die Ausnahmemeldung erneut auszulösen. Der Traceback führt Sie möglicherweise nicht zu den Zeilennummern, in denen die Ausnahme abgefangen wurde, führt Sie jedoch sicher dorthin, wo die Ausnahme aufgetreten ist.
User-Asterix
2
Sie können die Anzeige der Ausnahmekette auch explizit unterdrücken, indem Sie in der from-Klausel None raise RuntimeError("Something bad happened") from None
angeben
10
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

Drucke:

There is a problem: invalid literal for int() with base 10: 'a'
Eumiro
quelle
1
Ich habe mich gefragt, ob es eine Python-Sprache für das gibt, was ich versuche, außer eine andere Instanz auszulösen.
Kit
@ Kit - Ich würde es "eine Ausnahme erneut auslösen
eumiro
1
@eumiro, Nein, du machst eine neue Ausnahme. Siehe meine Antwort. Aus Ihrem Link: "... aber Erhöhen ohne Ausdrücke sollte bevorzugt werden, wenn die Ausnahme, die erneut ausgelöst werden soll, die zuletzt aktive Ausnahme im aktuellen Bereich war."
Johan Lundberg
3
@ JohanLundberg - raiseohne Parameter wird neu angehoben. Wenn OP eine Nachricht hinzufügen möchte, muss er eine neue Ausnahme auslösen und kann die Nachricht / den Typ der ursprünglichen Ausnahme wiederverwenden.
Eumiro
2
Wenn Sie eine Nachricht hinzufügen möchten, können Sie keine neue Nachricht von Grund auf neu erstellen, indem Sie "ValueError" auslösen. Auf diese Weise zerstören Sie die zugrunde liegenden Informationen darüber, um welche Art von ValueError es sich handelt (ähnlich wie beim Schneiden in C ++). Durch die Wieder werfen die gleiche Ausnahme mit raise ohne Argument, übergeben Sie das ursprüngliche Objekt mit dem richtigen spezifischen Typ (abgeleitet von Valueerror).
Johan Lundberg
9

Es scheint, dass alle Antworten e.args [0] Informationen hinzufügen und dadurch die vorhandene Fehlermeldung ändern. Gibt es einen Nachteil beim Erweitern des Args-Tupels? Ich denke, der mögliche Vorteil ist, dass Sie die ursprüngliche Fehlermeldung für Fälle, in denen das Parsen dieser Zeichenfolge erforderlich ist, in Ruhe lassen können. Sie können dem Tupel mehrere Elemente hinzufügen, wenn Ihre benutzerdefinierte Fehlerbehandlung mehrere Meldungen oder Fehlercodes erzeugt, wenn der Traceback programmgesteuert analysiert wird (z. B. über ein Systemüberwachungstool).

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

oder

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

Können Sie einen Nachteil dieses Ansatzes erkennen?

Chris Johnson
quelle
Meine ältere Antwort ändert e.args [0] nicht.
Johan Lundberg
4

Mit dieser Codevorlage sollten Sie eine Ausnahme mit einer benutzerdefinierten Nachricht auslösen können.

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")
Ruggero Turra
quelle
3
Dadurch wird die Stapelverfolgung nicht beibehalten.
Plok
Der Fragesteller gibt nicht an, dass die Stapelverfolgung beibehalten werden soll.
Spitzmaus
4

Lösen Sie entweder die neue Ausnahme mit Ihrer Fehlermeldung mit aus

raise Exception('your error message')

oder

raise ValueError('your error message')

an der Stelle, an der Sie es auslösen möchten ODER die Fehlermeldung mit 'from' an die aktuelle Ausnahme anhängen (ersetzen) (nur Python 3.x wird unterstützt):

except ValueError as e:
  raise ValueError('your message') from e
Alexey Antonenko
quelle
Vielen Dank, @gberger, "von e" Ansatz wird eigentlich nicht von Python 2.x unterstützt
Alexey Antonenko
3

Dies ist die Funktion, mit der ich die Ausnahmemeldung in Python 2.7 und 3.x unter Beibehaltung des ursprünglichen Tracebacks ändere. Es benötigtsix

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)
Bryce Guinta
quelle
3

In Python 3 integrierte Ausnahmen haben das strerrorFeld:

except ValueError as err:
  err.strerror = "New error message"
  raise err
user3761308
quelle
Das scheint nicht zu funktionieren. Vermissen Sie etwas?
MasayoMusic
2

Die aktuelle Antwort hat bei mir nicht gut funktioniert. Wenn die Ausnahme nicht erneut abgefangen wird, wird die angehängte Nachricht nicht angezeigt.

Wenn Sie jedoch wie unten beschrieben vorgehen, wird die Ablaufverfolgung beibehalten und die angehängte Nachricht angezeigt, unabhängig davon, ob die Ausnahme erneut abgefangen wird oder nicht.

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

(Ich habe Python 2.7 verwendet, habe es in Python 3 nicht ausprobiert.)

Zitrax
quelle
1

Keine der oben genannten Lösungen hat genau das getan, was ich wollte, nämlich dem ersten Teil der Fehlermeldung einige Informationen hinzuzufügen, dh ich wollte, dass meine Benutzer zuerst meine benutzerdefinierte Nachricht sehen.

Das hat bei mir funktioniert:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)
RobinL
quelle
0

Dies funktioniert nur mit Python 3 . Sie können die ursprünglichen Argumente der Ausnahme ändern und Ihre eigenen Argumente hinzufügen.

Eine Ausnahme merkt sich die Argumente, mit denen sie erstellt wurde. Ich gehe davon aus, dass dies so ist, dass Sie die Ausnahme ändern können.

In der Funktion stellen reraisewir den ursprünglichen Argumenten der Ausnahme alle neuen Argumente voran, die wir möchten (wie eine Nachricht). Schließlich lösen wir die Ausnahme erneut aus, während der Rückverfolgungsverlauf beibehalten wird.

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the expection and preserve the traceback info so thta we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

Ausgabe

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')
Spitzmaus
quelle
-3

Wenn Sie den Fehlertyp anpassen möchten, können Sie einfach eine Fehlerklasse definieren, die auf ValueError basiert.

igni
quelle
Wie würde das in diesem Fall helfen?
Johan Lundberg