Frage Wie können benutzerdefinierte Ausnahmen im modernen Python deklariert werden?


Was ist der richtige Weg, benutzerdefinierte Ausnahmeklassen im modernen Python zu deklarieren? Mein primäres Ziel ist es, dem Standard zu folgen, den andere Ausnahmeklassen haben, so dass (zum Beispiel) jede zusätzliche Zeichenkette, die ich in die Ausnahme einbeziehe, von jedem Tool ausgedruckt wird, das die Ausnahme erfasst hat.

Mit "modernem Python" meine ich etwas, das in Python 2.5 läuft, aber für die Python 2.6- und Python-3-Methode "richtig" ist. Und mit "custom" meine ich ein Exception-Objekt, das zusätzliche Daten über die Fehlerursache enthalten kann: eine Zeichenfolge, vielleicht auch ein anderes beliebiges Objekt, das für die Ausnahme relevant ist.

Ich war über die folgende Verwarnungswarnung in Python 2.6.2 gestolpert:

>>> class MyError(Exception):
...     def __init__(self, message):
...         self.message = message
... 
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6

Es scheint verrückt, dass BaseException hat eine besondere Bedeutung für die genannten Attribute message. Ich nehme an PEP-352 Dieses Attribut hatte in 2.5 eine besondere Bedeutung. Sie versuchen, weg zu verwerfen, also denke ich, dass dieser Name (und dieser allein) jetzt verboten ist? Pfui.

Ich bin mir auch bewusst, dass Exception hat einen magischen Parameter args, aber ich habe nie gewusst, wie man es benutzt. Ich bin mir auch nicht sicher, dass es der richtige Weg ist, Dinge voranzutreiben. Viele der Diskussionen, die ich online fand, deuteten darauf hin, dass sie versuchten, in Python 3 die Argumente zu beseitigen.

Update: Zwei Antworten haben das Überschreiben vorgeschlagen __init__, und __str__/__unicode__/__repr__. Das scheint wie viel Tippen, ist es notwendig?


892
2017-08-23 21:29


Ursprung


Antworten:


Vielleicht habe ich die Frage verpasst, aber warum nicht:

class MyException(Exception):
    pass

Bearbeiten: etwas überschreiben (oder zusätzliche Argumente übergeben), tun Sie dies:

class ValidationError(Exception):
    def __init__(self, message, errors):

        # Call the base class constructor with the parameters it needs
        super(ValidationError, self).__init__(message)

        # Now for your custom code...
        self.errors = errors

Auf diese Weise könnten Sie das dict von Fehlermeldungen an den zweiten Parameter übergeben und später mit ihm weitermachen e.errors


Python 3 Update: In Python 3+ können Sie diesen etwas kompakteren Gebrauch von verwenden super():

class ValidationError(Exception):
    def __init__(self, message, errors):

        # Call the base class constructor with the parameters it needs
        super().__init__(message)

        # Now for your custom code...
        self.errors = errors

924
2017-08-23 21:55



Mit modernen Python-Ausnahmen müssen Sie nicht missbrauchen .messageoder überschreiben .__str__() oder .__repr__() oder irgendetwas davon. Wenn Sie nur eine informative Nachricht erhalten möchten, wenn Ihre Ausnahme ausgelöst wird, führen Sie Folgendes aus:

class MyException(Exception):
    pass

raise MyException("My hovercraft is full of eels")

Das wird eine Rückverfolgung geben, die mit endet MyException: My hovercraft is full of eels.

Wenn Sie von der Ausnahme mehr Flexibilität wünschen, können Sie ein Wörterbuch als Argument übergeben:

raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})

Um jedoch auf diese Details in einem except Block ist ein bisschen komplizierter. Die Details sind in der gespeichert args Attribut, das eine Liste ist. Sie müssten so etwas tun:

try:
    raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
    details = e.args[0]
    print(details["animal"])

Es ist immer noch möglich, mehrere Elemente an die Ausnahme zu übergeben und über Tupel-Indizes darauf zuzugreifen, aber dies ist sehr entmutigt (und war sogar vor einiger Zeit für die Zurückhaltung gedacht). Wenn Sie mehr als eine einzige Information benötigen und die obige Methode für Sie nicht ausreicht, sollten Sie eine Unterklasse erstellen Exception wie beschrieben in der Anleitung.

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message

359
2018-04-22 18:18



"Korrekte Möglichkeit benutzerdefinierte Ausnahmen im modernen Python zu deklarieren?"

Dies ist in Ordnung, es sei denn, Ihre Ausnahme ist eine Art spezifischer Ausnahme:

class MyException(Exception):
    pass

Oder besser (vielleicht perfekt), statt pass gib einen Docstring:

class MyException(Exception):
    """Raise for my specific kind of exception"""

Subclassing Exception Subclasses

Von dem Dokumente

Exception

Alle eingebauten, nicht systemauslösenden Ausnahmen werden von dieser Klasse abgeleitet.   Alle benutzerdefinierten Ausnahmen sollten auch daraus abgeleitet werden   Klasse.

Das bedeutet, dass ob Ihre Ausnahme ist ein Typ einer spezifischeren Ausnahme, die die Ausnahme von der allgemeinen Klasse ableitet Exception (und das Ergebnis wird sein, dass Sie immer noch von Exception wie die Dokumente empfehlen). Außerdem können Sie zumindest einen Docstring bereitstellen (und nicht gezwungen werden, den pass Stichwort):

class MyAppValueError(ValueError):
    '''Raise when my specific value is wrong'''

Legen Sie Attribute, die Sie selbst erstellen, mit einem benutzerdefinierten Wert fest __init__. Vermeiden Sie es, ein Diktat als Positionsargument zu übergeben. Die zukünftigen Benutzer Ihres Codes werden es Ihnen danken. Wenn Sie das veraltete Nachrichtenattribut verwenden, vermeiden Sie die Zuweisung selbst DeprecationWarning:

class MyAppValueError(ValueError):
    '''Raise when a specific subset of values in context of app is wrong'''
    def __init__(self, message, foo, *args):
        self.message = message # without this you may get DeprecationWarning
        # Special attribute you desire with your Error, 
        # perhaps the value that caused the error?:
        self.foo = foo         
        # allow users initialize misc. arguments as any other builtin Error
        super(MyAppValueError, self).__init__(message, foo, *args) 

Es gibt wirklich keine Notwendigkeit, Ihre eigenen zu schreiben __str__ oder __repr__. Die eingebauten sind sehr nett, und dein kooperative Vererbung stellt sicher, dass Sie es verwenden.

Kritik der Top-Antwort

Vielleicht habe ich die Frage verpasst, aber warum nicht:

class MyException(Exception):
    pass

Auch hier besteht das Problem darin, dass Sie es entweder spezifisch benennen müssen (wenn es an anderer Stelle erstellt wird) oder Exception abfangen (aber Sie sind wahrscheinlich nicht bereit, alle Arten von Exceptions zu behandeln, und Sie sollten nur Ausnahmen abfangen, die Sie bereit sind zu handhaben). Ähnliche Kritik an der unten, aber zusätzlich ist das nicht der Weg, über zu initialisieren superund du wirst einen bekommen DeprecationWarning wenn Sie auf das Nachrichtenattribut zugreifen:

Bearbeiten: Um etwas zu überschreiben (oder zusätzliche Argumente übergeben), tun Sie dies:

class ValidationError(Exception):
    def __init__(self, message, errors):

        # Call the base class constructor with the parameters it needs
        super(ValidationError, self).__init__(message)

        # Now for your custom code...
        self.errors = errors

Auf diese Weise können Sie dict von Fehlermeldungen an den zweiten Parameter übergeben und später mit e.errors dazu kommen

Es erfordert auch genau zwei Argumente (abgesehen von der self.) Nicht mehr und nicht weniger. Das ist eine interessante Einschränkung, die zukünftige Benutzer vielleicht nicht zu schätzen wissen.

Direkt sein - es verletzt Liskov Substituierbarkeit.

Ich werde beide Fehler demonstrieren:

>>> ValidationError('foo', 'bar', 'baz').message

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    ValidationError('foo', 'bar', 'baz').message
TypeError: __init__() takes exactly 3 arguments (4 given)

>>> ValidationError('foo', 'bar').message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foo'

Verglichen mit:

>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'

150
2017-11-14 21:09



Sehen Sie, wie Ausnahmen standardmäßig funktionieren vs Es werden mehr Attribute verwendet (Rückverfolgung entfällt):

>>> raise Exception('bad thing happened')
Exception: bad thing happened

>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')

also möchtest du vielleicht eine Art von "Ausnahmeschablone", die als Ausnahme selbst kompatibel arbeiten:

>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened

>>> raise nastyerr()
NastyError: bad thing happened

>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')

Dies kann leicht mit dieser Unterklasse durchgeführt werden

class ExceptionTemplate(Exception):
    def __call__(self, *args):
        return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass

und wenn Sie diese tupelartige Standarddarstellung nicht mögen, fügen Sie einfach hinzu __str__ Methode zum ExceptionTemplate Klasse, wie:

    # ...
    def __str__(self):
        return ': '.join(self.args)

und du wirst haben

>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken

37
2017-08-07 16:23



Sie sollten überschreiben __repr__ oder __unicode__ Methoden anstelle der Verwendung von Nachrichten, die Argumente, die Sie beim Erstellen der Ausnahme angeben, befinden sich in der args Attribut des Ausnahmeobjekts.


14
2017-08-23 21:46



Nein, "Nachricht" ist nicht verboten. Es ist nur veraltet. Ihre Anwendung wird mit der Verwendung der Nachricht funktionieren. Vielleicht möchten Sie den Deprecation-Fehler natürlich loswerden.

Wenn Sie benutzerdefinierte Ausnahmeklassen für Ihre Anwendung erstellen, werden viele von ihnen nicht nur von Exception abgeleitet, sondern von anderen, wie ValueError oder ähnlichen. Dann müssen Sie sich an ihre Verwendung von Variablen anpassen.

Und wenn Sie in Ihrer Anwendung viele Ausnahmen haben, ist es normalerweise eine gute Idee, für alle eine gemeinsame benutzerdefinierte Basisklasse zu haben, damit die Benutzer Ihrer Module dies tun können

try:
    ...
except NelsonsExceptions:
    ...

Und in diesem Fall können Sie das tun __init__ and __str__ benötigt dort, also müssen Sie es nicht für jede Ausnahme wiederholen. Aber einfach die Nachrichtenvariable etwas anderes als die Nachricht aufrufen, macht den Trick.

In jedem Fall brauchen Sie nur die __init__ or __str__ wenn Sie etwas anderes machen als das, was Exception selbst tut. Und wenn die Depretierung, dann brauchen Sie beide, oder Sie erhalten einen Fehler. Das ist nicht viel extra Code, den man pro Klasse braucht. ;)


5
2017-08-23 21:58



Probieren Sie dieses Beispiel

class InvalidInputError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return repr(self.msg)

inp = int(input("Enter a number between 1 to 10:"))
try:
    if type(inp) != int or inp not in list(range(1,11)):
        raise InvalidInputError
except InvalidInputError:
    print("Invalid input entered")

0
2017-07-22 05:27