Erstellen Sie Ihre eigenen benutzerdefinierten Ausnahmeklassen Python , die auf Ihre Anwendung zugeschnitten sind
Eine ordnungsgemäße Ausnahmebehandlung macht den Code leichter lesbar, erfordert aber auch, dass Sie sich sehr genau überlegen, was Sie tun wollen.
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':
- Exceptions lässt Ihren Code frei von allen Prüfungen, die notwendig sind, wenn der Teststatus bei jedem Anruf zurückkehrt
- In Ausnahmen können Sie den Rückgabewert von Funktionen für Istwerte verwenden
- 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
- e
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
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas