Maak uw eigen Python aangepaste uitzonderingsklassen op maat van uw toepassing.
Een goede uitzonderingsbehandeling geeft u een betere leesbaarheid van de code, maar vereist ook dat u heel goed nadenkt over wat u wilt doen.
Het gebruik van uitzonderingen in Python ziet er gemakkelijk uit, maar dat is het niet. Waarschijnlijk moet u de uitzonderingen en de behandeling van uitzonderingen bestuderen voordat u een Python code schrijft, maar TL;DR. Er zijn voorbeelden op het internet, helaas zijn de meeste zeer triviaal. Hoe dan ook, ik heb dit onderzocht en kwam met wat code die ik dacht te delen met jullie. Laat een reactie achter als je suggesties hebt.
Wat is een fout en wat is een uitzondering, wat is het verschil? In Python wordt een uitzondering gemaakt als er tijdens de uitvoering van het programma een fout optreedt, bijvoorbeeld een ValueError, ZeroDivisionError.
De voordelen van uitzonderingen ten opzichte van statussen kunnen als volgt worden samengevat, zie ook onderstaande link 'Waarom is het beter om een uitzondering te maken dan een foutcode terug te geven?
- Uitzonderingen laat uw code schoon van alle controles die nodig zijn bij het testen van de status van elke oproep.
- Uitzonderingen laten u toe om de terugkeerwaarde van de functies te gebruiken voor de werkelijke waarden
- Het belangrijkste is: uitzonderingen kunnen niet genegeerd worden door niets te doen, terwijl de status terug kan gaan naar
Niet alle mensen zijn zo positief, maar ik ga hier geen oorlog beginnen.
Ergens in onze code kunnen we een exception handler hebben die bijvoorbeeld gebruikt kan worden om een database insert terug te rollen. We kunnen ook de uitzondering laten bubbelen naar de top van de code en een foutmelding geven aan de user.
Logging en parameters
Als er iets gebeurt, moeten we ervoor zorgen dat we de fout registreren. Om het lokaliseren en oplossen van een (codeer)probleem te vergemakkelijken zetten we zoveel mogelijk informatie in ons logboek. Ik heb besloten dat ik de volgende informatie in het logboek wil hebben:
- een traceback
- de volgende (optionele) parameters:
- e
Dit is de waarde die (vaak) door een uitzondering wordt geretourneerd - code
Dit kan onze eigen foutcode zijn - bericht
Dit is het bericht dat we willen laten zien aan de user (websitebezoeker, API user) - details
Meer informatie die nuttig kan zijn voor de user, bijv. meegeleverde parameters - fargs
Dit zijn de argumenten van de functie waar de uitzondering zich voordeed, handig voor het debuggen
- e
Een voorbeeld
Stel dat we een applicatie maken die gebruik maakt van een databaseklasse en een imap-serverklasse. We maken onze aangepaste Exception handler. Het is een goede gewoonte om het in een bestand exceptions.py te hebben dat we importeren in onze applicatie:
# 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
Dit lijkt erg op het maken van normale klassen. In dit geval geven we nul of meer genoemde argumenten door aan de uitzondering. We hebben de __str__() methode nodig om Python een string met beschikbare data terug te laten sturen die in het app.log bestand wordt opgeslagen. Als we __str__() weglaten dan wordt app.log alleen weergegeven: exceptions.IMAPServerError'.
De app code:
# 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()
Merk op dat uitzonderingen worden gelogd met behulp van 'logging.exception' op een bestand app.log. Ook gebruiken we de Python locals() functie om de argumenten van de functie of methode waar de uitzondering zich voordeed vast te leggen. In de 'connect to remote system'-uitzondering verhogen we met alle parameters die we in het logboek willen hebben. Tot slot gebruiken we hier 'raise ... from e' construct. Dit onthult de ZeroDivisionError als de oorspronkelijke oorzaak.
Om te draaien, typ:
python3 app.py
Het resultaat is:
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
en het logboekbestand 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 bevat twee ERROR-items. Een voor de 'Input must be 8 of 16' fout en een voor de 'Connection to remote system failed' fout.
Samenvatting
Het leven zou eenvoudig kunnen zijn, maar dat is het niet. Een goede uitzonderingsbehandeling zorgt ervoor dat u de code gemakkelijker kunt lezen, maar vereist ook dat u heel goed nadenkt over wat u wilt doen. Het zou mooi zijn als we ook de argumenten van de aangeroepen functies kunnen toevoegen die leiden tot de functie die de uitzondering opheft, maar dit is moeilijker.
Links / credits
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/
Lees meer
Exceptions
Recent
- Database UUID primaire sleutels van je webapplicatie verbergen
- Don't Repeat Yourself (DRY) met Jinja2
- SQLAlchemy, PostgreSQL, maximum aantal rijen per user
- Toon de waarden in SQLAlchemy dynamische filters
- Veilige gegevensoverdracht met Public Key versleuteling en pyNaCl
- rqlite: een alternatief voor SQLite met hoge beschikbaarheid en distributed
Meest bekeken
- Met behulp van Python's pyOpenSSL om SSL-certificaten die van een host zijn gedownload te controleren
- Gebruik van UUIDs in plaats van Integer Autoincrement Primary Keys met SQLAlchemy en MariaDb
- PyInstaller en Cython gebruiken om een Python executable te maken
- Maak verbinding met een dienst op een Docker host vanaf een Docker container
- SQLAlchemy: Gebruik van Cascade Deletes om verwante objecten te verwijderen
- Flask RESTful API verzoekparametervalidatie met Marshmallow-schema's