1827 Stimmen

Der richtige Weg, um benutzerdefinierte Ausnahmen in modernen Python deklarieren?

Was ist der richtige Weg, um benutzerdefinierte Ausnahmeklassen in modernem Python zu deklarieren? Mein primäres Ziel ist es, zu folgen, was auch immer Standard andere Ausnahmeklassen haben, so dass (zum Beispiel) jede zusätzliche Zeichenfolge, die ich in der Ausnahme enthalten ist, von was auch immer Werkzeug die Ausnahme gefangen gedruckt.

Unter "modernem Python" verstehe ich etwas, das in Python 2.5 läuft, aber für Python 2.6 und Python 3.* "korrekt" ist. Und mit "benutzerdefiniert" meine ich eine Exception Objekt, das zusätzliche Daten über die Ursache des Fehlers enthalten kann: eine Zeichenkette, vielleicht auch ein anderes beliebiges Objekt, das für die Ausnahme relevant ist.

Ich wurde durch die folgende Verwerfungswarnung 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 Attribute mit dem Namen message . Ich entnehme PEP-352 dieses Attribut hatte in 2.5 eine besondere Bedeutung, die jetzt abgeschafft werden soll, also ist dieser Name (und nur dieser) jetzt wohl verboten? Pfui.

Ich bin mir auch unscharf bewusst, dass Exception hat einen magischen Parameter args aber ich habe nie gewusst, wie man es benutzt. Ich bin mir auch nicht sicher, ob es der richtige Weg ist, die Dinge in Zukunft zu tun; viele der Diskussionen, die ich online gefunden habe, deuten darauf hin, dass sie versuchen, args in Python 3 abzuschaffen.

Update: zwei Antworten haben vorgeschlagen, die __init__ y __str__ / __unicode__ / __repr__ . Das scheint eine Menge Tipparbeit zu sein, ist das notwendig?

1907voto

gahooa Punkte 121696

Vielleicht habe ich die Frage nicht verstanden, aber warum nicht?

class MyException(Exception):
    pass

Um etwas außer Kraft zu setzen (oder zusätzliche Args zu übergeben), tun Sie dies:

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

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

In Python 2 müssen Sie diese etwas komplexere Form von super() :

super(ValidationError, self).__init__(message)

699voto

frnknstn Punkte 6397

Mit modernen Python-Ausnahmen müssen Sie nicht mehr die .message oder außer Kraft setzen .__str__() o .__repr__() oder irgendetwas davon. Wenn Sie nur eine informative Meldung wünschen, wenn Ihre Ausnahme ausgelöst wird, tun Sie dies:

class MyException(Exception):
    pass

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

Das ergibt einen Traceback, der mit MyException: My hovercraft is full of eels .

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

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

Um diese Details jedoch in einem except Block ist ein wenig komplizierter. Die Details werden in der Datei args Attribut, das eine Liste ist. Sie müssten etwa so vorgehen:

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 auf sie zuzugreifen, aber dies ist sehr abschreckend (und war vor einiger Zeit sogar zur Abschaffung vorgesehen). Wenn Sie mehr als eine einzelne Information benötigen und die obige Methode für Sie nicht ausreicht, dann sollten Sie eine Unterklasse Exception wie beschrieben in der Lehrgang .

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

310voto

"Was ist der richtige Weg, um benutzerdefinierte Ausnahmen in modernem Python zu deklarieren?"

Dies ist in Ordnung, es sei denn, Ihre Ausnahme ist wirklich ein Typ einer spezifischeren Ausnahme:

class MyException(Exception):
    pass

Oder besser (vielleicht perfekt), anstelle von pass einen Docstring geben:

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

Unterklassenbildung von Ausnahme-Unterklassen

Von der docs

Exception

Alle eingebauten, nicht systembedingten Ausnahmen sind von dieser Klasse abgeleitet. Alle benutzerdefinierten Ausnahmen sollten ebenfalls von dieser Klasse abgeleitet werden. Klasse abgeleitet werden.

Das bedeutet, dass wenn Ihre Ausnahme ein Typ einer spezifischeren Ausnahme ist, untergliedern Sie diese Ausnahme anstelle der generischen Exception (und das Ergebnis wird sein, dass Sie immer noch von Exception wie in der Dokumentation empfohlen). Außerdem können Sie zumindest einen Docstring angeben (und sind nicht gezwungen, die pass Schlüsselwort):

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

Setzen Sie Attribute, die Sie selbst mit einer benutzerdefinierten __init__ . Vermeiden Sie die Übergabe eines dict als Positionsargument, zukünftige Benutzer Ihres Codes werden es Ihnen danken. Wenn Sie das veraltete message-Attribut verwenden, vermeiden Sie durch die eigene Zuweisung ein 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 ist wirklich nicht nötig, einen eigenen Text zu schreiben. __str__ o __repr__ . Die eingebauten sind sehr schön, und Ihre genossenschaftliches Erbe sorgt dafür, dass Sie sie nutzen.

Kritik an der besten Antwort

Vielleicht habe ich die Frage nicht verstanden, aber warum nicht?

class MyException(Exception):
    pass

Auch hier besteht das Problem darin, dass Sie sie entweder speziell benennen müssen (indem Sie sie importieren, wenn sie an anderer Stelle erstellt wurde) oder eine Ausnahme abfangen müssen (aber Sie sind wahrscheinlich nicht darauf vorbereitet, alle Arten von Ausnahmen zu behandeln, und Sie sollten nur Ausnahmen abfangen, auf deren Behandlung Sie vorbereitet sind). Ähnliche Kritik wie unten, aber zusätzlich ist das nicht die Art und Weise der Initialisierung über super und Sie erhalten eine DeprecationWarning wenn Sie auf das Nachrichtenattribut zugreifen:

Bearbeiten: um etwas zu überschreiben (oder zusätzliche Args zu ü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 dict von Fehlermeldungen an den zweiten Parameter übergeben, und später mit e.errors darauf zugreifen

Außerdem müssen genau zwei Argumente übergeben werden (abgesehen von der self .) Nicht mehr und nicht weniger. Das ist eine interessante Einschränkung, die künftige Nutzer vielleicht nicht zu schätzen wissen.

Um es direkt zu sagen: Es verstößt gegen 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'

86voto

Eugene Yarmash Punkte 130008

Um Ihre eigenen Ausnahmen korrekt zu definieren, sollten Sie einige bewährte Verfahren befolgen:

  • Definieren Sie eine Basisklasse geerbt von Exception . Auf diese Weise lassen sich alle Ausnahmen im Zusammenhang mit dem Projekt leicht abfangen:

    class MyProjectError(Exception):
        """A base class for MyProject exceptions."""

    Die Organisation der Ausnahmeklassen in einem separaten Modul (z.B. exceptions.py ) ist im Allgemeinen eine gute Idee.

  • Um eine spezifische Ausnahme zu erstellen, unterklassifizieren Sie die Basisausnahmeklasse.

    class CustomError(MyProjectError):
       """A custom exception class for MyProject."""

    Sie können auch benutzerdefinierte Ausnahmeklassen unterordnen, um eine Hierarchie zu erstellen.

  • Um einer benutzerdefinierten Ausnahme Unterstützung für zusätzliche Argumente hinzuzufügen, definieren Sie eine __init__() Methode mit einer variablen Anzahl von Argumenten. Rufen Sie die Methode der Basisklasse __init__() und übergibt ihm alle Positionsargumente (denken Sie daran, dass BaseException / Exception erwarten eine beliebige Anzahl von Positionsbezogene Argumente ). Speichern Sie zusätzliche Argumente für die Instanz, z. B.:

    class CustomError(MyProjectError):
        def __init__(self, *args, **kwargs):
            super().__init__(*args)
            self.foo = kwargs.get('foo')

    Um eine solche Ausnahme mit einem zusätzlichen Argument auszulösen, können Sie verwenden:

     raise CustomError('Something bad happened', foo='foo')

Dieser Entwurf entspricht dem Liskov-Substitutionsprinzip da Sie eine Instanz einer Basisausnahmeklasse durch eine Instanz einer abgeleiteten Ausnahmeklasse ersetzen können. Außerdem können Sie damit eine Instanz einer abgeleiteten Klasse mit denselben Parametern wie die übergeordnete Klasse erstellen.

64voto

fameman Punkte 2587

Ab Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ), ist die empfohlene Methode nach wie vor:

class CustomExceptionName(Exception):
    """Exception raised when very uncommon things happen"""
    pass

Bitte vergessen Sie nicht zu dokumentieren, warum eine Ausnahme erforderlich ist!

Bei Bedarf ist dies der richtige Weg für Ausnahmen mit mehr Daten:

class CustomExceptionName(Exception):
    """Still an exception raised when uncommon things happen"""
    def __init__(self, message, payload=None):
        self.message = message
        self.payload = payload # you could add more args
    def __str__(self):
        return str(self.message) # __str__() obviously expects a string to be returned, so make sure not to send any other data types

und holen sie wie:

try:
    raise CustomExceptionName("Very bad mistake.", "Forgot upgrading from Python 1")
except CustomExceptionName as error:
    print(str(error)) # Very bad mistake
    print("Detail: {}".format(error.payload)) # Detail: Forgot upgrading from Python 1

payload=None ist wichtig, um ihn einlegbar zu machen. Bevor Sie es wegwerfen, müssen Sie error.__reduce__() . Das Laden wird wie erwartet funktionieren.

Vielleicht sollten Sie nach einer Lösung mit Pythons suchen return Anweisung, wenn Sie viele Daten an eine äußere Struktur übertragen müssen. Dies scheint mir klarer/pythonischer zu sein. Erweiterte Ausnahmen werden in Java häufig verwendet, was manchmal lästig sein kann, wenn man ein Framework verwendet und alle möglichen Fehler abfangen muss.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X