angle-up arrow-clockwise arrow-counterclockwise arrow-down-up arrow-left at calendar card-list chat check envelope folder house info-circle pencil people person person-plus phone plus question-circle search tag trash x

Erstellen Sie Ihre eigenen benutzerdefinierten Ausnahmeklassen Python , die auf Ihre Anwendung zugeschnitten sind

17 Juni 2020 durch Peter
In Python

Eine ordnungsgemäße Ausnahmebehandlung macht den Code leichter lesbar, erfordert aber auch, dass Sie sich sehr genau überlegen, was Sie tun wollen.

post main image
https://unsplash.com/@surface

Die Verwendung von Ausnahmen in Python sieht einfach aus, ist es aber nicht. Wahrscheinlich sollten Sie die Ausnahmen und die Ausnahmebehandlung studieren, bevor Sie einen Code für Python schreiben, aber TL;DR. Es gibt Beispiele im Internet, leider sind die meisten sehr trivial. Wie auch immer, ich habe das recherchiert und bin auf einen Code gestoßen, von dem ich dachte, dass ich ihn mit Ihnen teile. Hinterlassen Sie einen Kommentar, wenn Sie Vorschläge haben.

Was ist ein Fehler und was ist eine Ausnahme, was ist der Unterschied? In Python wird eine Ausnahme ausgelöst, wenn während der Programmausführung ein Fehler auftritt. Der Fehler kann z.B. ein ValueError, ZeroDivisionError sein.

Die Vorteile von Ausnahmen gegenüber Statusrückgaben lassen sich wie folgt zusammenfassen, siehe auch Link unten 'Warum ist es besser, eine Ausnahme zu werfen, als einen Fehlercode zurückzugeben':

  1. Exceptions lässt Ihren Code frei von allen Prüfungen, die notwendig sind, wenn der Teststatus bei jedem Anruf zurückkehrt
  2. In Ausnahmen können Sie den Rückgabewert von Funktionen für Istwerte verwenden
  3. Das Wichtigste ist: Ausnahmen können nicht durch Untätigkeit ignoriert werden, während Statusrückgaben

Nicht alle Menschen sind so positiv, aber ich werde hier keinen Krieg beginnen.

Irgendwo in unserem Code können wir einen Ausnahmebehandler haben, der z.B. zum Rollback einer Datenbankeinfügung verwendet werden kann. Wir können die Ausnahme auch zum obersten Code sprudeln lassen und dem user einen Fehler präsentieren.

Protokollierung und Parameter

Wenn etwas passiert, müssen wir sicherstellen, dass wir den Fehler protokollieren. Um das Auffinden und Lösen eines (Kodierungs-)Problems zu erleichtern, nehmen wir so viele Informationen wie möglich in unser Protokoll auf. Ich habe beschlossen, die folgenden Informationen in das Protokoll aufzunehmen:

  • eine Rückverfolgung
  • die folgenden (optionalen) Parameter:
    • e
      Dies ist der (oft) von einer Ausnahme zurückgegebene Wert
    • code
      Dies kann unser eigener Fehlercode sein
    • Nachricht
      Dies ist die Nachricht, die wir dem user (Website-Besucher, API user) zeigen wollen
    • details
      Weitere Informationen, die für die user hilfreich sein können, z.B. mitgelieferte Parameter
    • fargs
      Dies sind die Argumente der Funktion, bei der die Ausnahme aufgetreten ist, nützlich für die Fehlersuche

Ein Beispiel

Angenommen, wir erstellen eine Anwendung, die eine Datenbankklasse und eine imap-Serverklasse verwendet. Wir erstellen unseren benutzerdefinierten Exception-Handler. Es ist gute Praxis, ihn in einer Datei exceptions.py zu haben, die wir in unsere Anwendung importieren:

# file: exceptions.py

class AppError(Exception):

    def __init__(self, e=None, code=None, message=None, details=None, fargs=None):
        self.e = e
        self.code = code
        self.message = message
        self.details = details
        self.fargs = fargs

    def get_e(self):
        return self.e

    def get_code(self):
        return self.code

    def get_message(self):
        return self.message

    def get_details(self):
        return self.details

    def __str__(self):
        s_items = []
        if self.e is not  None:
            s_items.append('e = {}'.format(self.e))
        if self.code is not  None:
            s_items.append('code = {}'.format(self.code))
        if self.message is not  None:
            s_items.append('message = {}'.format(self.message))
        if self.details is not  None:
            s_items.append('details = {}'.format(self.details))
        if self.fargs is not  None:
            s_items.append('fargs = {}'.format(self.fargs))
        return ', '.join(s_items)


class DbError(AppError):
    pass


class IMAPServerError(AppError):
    pass

Dies ist dem Anlegen normaler Klassen sehr ähnlich. In diesem Fall übergeben wir null oder mehr benannte Argumente an die Ausnahme. Wir brauchen die __str__()-Methode, damit Python eine Zeichenfolge mit verfügbaren Daten zurückgibt, die in der Datei app.log gespeichert wird. Wenn wir __str__() weglassen, zeigt app.log nur an: 'Ausnahmen.IMAPServerFehler' an.

Der Anwendungscode:

# file: app.py
 
from exceptions import AppError, DbError, IMAPServerError

import logging

logging.basicConfig(
    format='%(asctime)s - %(levelname)s: %(message)s',
    filename='app.log',
    level=logging.DEBUG
    #level=logging.INFO
)

class A:

    def do_a(self, a):
        # connect to remote system
        b = B()
        b.do_b('unnamed parameter', b=a, c={ 'one': 'first', 'two': 'second' })

        # do something else


class B:

    def do_b(self, a, b=None, c=None):
        fname = 'B.do_b'
        fargs = locals()

        abc = 'not in locals'

        # check values
        if b not in [8, 16]:
            raise IMAPServerError(
                fargs = fargs,
                e = 'Input must be 8 or 16',
                message = 'Input must be 8 or 16, value supplied: {}'.format(b)
            )

        # connect to remote system
        try:
            # simulate something went wrong
            # raise IMAPServerError('error 123')
            if b == 8:
                d = b/0

        except (ZeroDivisionError, IMAPServerError) as e:
            raise IMAPServerError(
                fargs = fargs,
                e = e,
                message = 'Connection to remote system failed. Please check your settings',
                details = 'Connection parameters:  username = John'
            ) from e


def run(i):

    a = A()
    a.do_a(i)


def do_run():

    # 7: input error
    # 8: connection error
    # 16: no error
    for i in [7, 8, 16]:

        print('Run with i = {}'.format(i))
        try:
            run(i)
            print('No error(s)')

        except (DbError, IMAPServerError) as e:
            logging.exception('Stack trace')
            print('Error: {}'.format(e.message))
            print('Details: {}'.format(e.details))

    print('Ready')


if __name__ == '__main__':
    do_run()

Beachten Sie, dass Ausnahmen mit 'logging.exception' in einer Datei app.log protokolliert werden. Außerdem verwenden wir die Funktion Python locals(), um die Argumente der Funktion oder Methode zu erfassen, bei der die Ausnahme aufgetreten ist. Bei der Ausnahme 'connect to remote system' wird die Verbindung mit allen Parametern, die wir im Protokoll haben wollen, erneut hergestellt. Schließlich verwenden wir hier das Konstrukt 'raise ... from e'. Dies enthüllt den ZeroDivisionError als die ursprüngliche Ursache.

Zum Ausführen, geben Sie ein:

python3 app.py

Das Ergebnis ist:

Run with i = 7
Error: Input must be 8 or 16, value supplied: 7
Details:  None
Run with i = 8
Error: Connection to remote system failed. Please check your settings
Details: Connection parameters:  username = John
Run with i = 16
No error(s)
Ready

und die Protokolldatei app.log:

2020-06-17 15:29:41,939 - ERROR: Stack trace
Traceback (most recent call last):
  File "app.py", line 71, in do_run
    run(i)
  File "app.py", line 59, in run
    a.do_a(i)
  File "app.py", line 19, in do_a
    b.do_b('unnamed parameter', b=a, c={ 'one': 'first', 'two': 'second' })
  File "app.py", line 38, in do_b
    message = 'Input must be 8 or 16, value supplied: {}'.format(b)
exceptions.IMAPServerError: e = Input must be 8 or 16, message = Input must be 8 or 16, value supplied: 7, fargs = {'fname': 'B.do_b', 'c': {'one': 'first', 'two': 'second'}, 'b': 7, 'a': 'unnamed parameter', 'self': <__main__.B object at 0x7f20aab66748>}
2020-06-17 15:29:41,939 - ERROR: Stack trace
Traceback (most recent call last):
  File "app.py", line 46, in do_b
    d = b/0
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "app.py", line 71, in do_run
    run(i)
  File "app.py", line 59, in run
    a.do_a(i)
  File "app.py", line 19, in do_a
    b.do_b('unnamed parameter', b=a, c={ 'one': 'first', 'two': 'second' })
  File "app.py", line 53, in do_b
    ) from e
exceptions.IMAPServerError: e = division by zero, message = Connection to remote system failed. Please check your settings, details = Connection parameters:  username = John, fargs = {'fname': 'B.do_b', 'c': {'one': 'first', 'two': 'second'}, 'b': 8, 'a': 'unnamed parameter', 'self': <__main__.B object at 0x7f20aab66748>}

app.log enthält zwei ERROR-Einträge. Einen für den Fehler 'Input must be 8 or 16' und einen für den Fehler 'Connection to remote system failed'.

Zusammenfassung

Das Leben könnte einfach sein, ist es aber nicht. Eine ordnungsgemäße Ausnahmebehandlung macht den Code leichter lesbar, erfordert aber auch, dass Sie sich sehr genau überlegen, was Sie tun wollen. Es wäre schön, wenn wir auch die Argumente der aufgerufenen Funktionen hinzufügen könnten, die zu der Funktion führen, die die Ausnahme auslöst, aber das ist schwieriger.

Links / Impressum

How to get value of arguments passed to functions on the stack?
https://stackoverflow.com/questions/6061744/how-to-get-value-of-arguments-passed-to-functions-on-the-stack

Proper way to declare custom exceptions in modern Python?
https://stackoverflow.com/questions/1319615/proper-way-to-declare-custom-exceptions-in-modern-python

The Most Diabolical Python Antipattern
https://realpython.com/the-most-diabolical-python-antipattern/

Why Exceptions Suck (ckwop.me.uk)
https://news.ycombinator.com/item?id=232890

Why is it better to throw an exception rather than return an error code?
https://stackoverflow.com/questions/4670987/why-is-it-better-to-throw-an-exception-rather-than-return-an-error-code

Your API sucks (so I’ll catch all exceptions)
https://dorinlazar.ro/your-api-sucks-catch-exceptions/

Mehr erfahren:
Exceptions

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.