4. Was ist ein Fehler?
Ein Fehler ist die
Abweichung des Ist-Stands von der Erwartung
5. Herausforderungen für Entwickler
● Was ist die Erwartung?
→ notwendig, um Fehler erkennen zu können
● Was soll Programm im Fehlerfalle machen?
● Wer kann den Fehler beheben?
● Wie kann Programm bei der Korrektur
unterstützen?
7. Wozu Beispiele in Python?
● Leicht verständlich und gut lesbar
● Kompakter Code
● Weitgehend englische Sätze
● Prozedural und objektorientiert nutzbar
Hinweis: Beispiele i.d.R. sowohl in Python 2 als auch 3
lauffähig, tw. mit geringfügigen Anpassungen für Python 3
8. Fehler in Python
● Darstellung über Exceptions
● Durchgängige Verwendung in Standard-Bibliothek
● Vorteil: nicht unabsichtlich ignorierbar
● Bewährt in vielen anderen Programmiersprachen
(Java, C#, ...)
● alternative Ansätze (hier nicht näher betrachtet):
● spezielle oder zusätzliche Rückgabewerte (zB go)
● globale Fehlervariablen (zB errno in C)
● Spezielle Sprachkonstrukte (zB „on error goto“ in
Basic)
9. Fehler erkennen
● Erkennen mit if und einer Fehlerbedingung
● Aufzeigen mit raise und einer Fehlermeldung
● Fehler führt zum Abbruch der Routine
height)
# Actual processing would happen here.
pass
10. Fehler abfangen und ausgeben
● Aufruf des möglicherweise fehlschlagenden Codes mit try
● Bestimmte Fehler abfangen mit except
● Ausgabe: height is -3 but must be greater than 0
try:
processSomething(-3)
except ValueError as error:
print(error)
vgl. C#, Java:
catch
statt except
11. Ressourcen immer freigeben (1)
● Mit finally: sowohl bei Erfolg als auch Fehler
'some.txt', 'rb')
processData(inputFile)
finally:
12. Ressourcen immer freigeben (2)
● Mit with-Statement:
as inputFile:
● Voraussetzung: verwendete Klasse implementiert
Context Manager
→ hat Wissen darüber, was wie auf zu räumen ist
vgl. C#:
using
13. Ressourcen immer freigeben (3a)
● Mit eigenem Context Manager
● Schritt 1: Definition von __enter__() und __exit__()
class SocketClient():
'''Provide a ``socket`` and automatically close it when done.'''
def __init__(self, host, port):
self.socket = socket.create_connection((host, port))
return self
self.socket.shutdown(socket.SHUT_RDWR)
self.socket.close()
14. Ressourcen immer freigeben (3b)
● Mit eigenem Context Manager
● Schritt 2: Aufruf wie zuvor über with-Statement
'www.python.org', 80) as
pythonOrg.clientSocket.sendall(
'GET /index.html HTTP/1.0n'
+ 'Host: www.python.orgn'
+'n')
reply = pythonOrg.clientSocket.recv(64)
print(reply)
15. Ressourcen immer freigeben (4b)
● Mit eigenem Context Manager
● Schritt 2: Aufruf wie zuvor über with-Statement
with SocketClient('www.python.org', 80) as
pythonOrg:
pythonOrg.clientSocket.sendall(
'GET /index.html HTTP/1.0n'
+ 'Host: www.python.orgn'
+'n')
reply = pythonOrg.clientSocket.recv(64)
print(reply)
Ergebnis von
__enter__().
Aufruf von
__exit__().
Aufruf von
__init__().
16. Ressourcen freigeben (5)
● Nicht verwenden: __del__()
● Aufruf erfolgt durch Garbage Collector
● Nicht vorhersagbar wann → Bindet Ressource
unnötig lange
● Wenn Exception während __del__(): nur Warnung
in Log, Aufrufer bekommt nichts davon mit
● Daher nicht vorhersagbares Verhalten
→ Anwender glaubt, alles hat funktioniert
→ Entwickler kann Fehler schwer reproduzieren
● Anwendung: Python-interne Aufräumarbeiten
vgl. Java:
dispose()
17. Fehler erkennen mit assert (1)
● Beispiel von zuvor
def processSomething(height):
if height <= 0:
raise ValueError(
'height must be greater than 0')
def processSomething(height):
assert height > 0,
'height must be greater than 0'
● Als Assertion:
18. Fehler erkennen mit assert (2)
● Wenn Bedingung verletzt:
wie raise AssertionError('...')
● Deaktivieren von assert mittels Aufruf über:
$ python -O xxx.py
(Buchstabe „großes O“, nicht Ziffer „0“)
● Von assert aufgerufene Funktionen dürfen keine
Seiteneffekte haben → sonst unterschiedliches
Programmverhalten je nachdem ob -O gesetzt
● Frage: wann raise und wann assert?
→ Antwort folgt
19. Zusammenfassung
● Fehler erkennen mit raise und assert
● Fehler abfangen mit try und except
● Aufräumarbeiten: finally, with und
Context Manager
● Nicht verwenden: __del__()
21. Grundprinzipen
● Im Zentrum der Überlegungen steht die
Beseitigung des Fehlers (Lösung) und nicht
der Fehler selbst
● Klare Zuständigkeiten zwischen Entwickler
und Anwender
● Hilfreiche Fehlermeldungen
● Fehlerbedingungen und -meldung aus
Programmcode ableitbar
22. Zuständigkeiten
● Entwickler: Umsetzung des Programms zur
● Verarbeitung der Daten und Eingaben des
Anwenders
● Liefern des gewünschten Ergebnisses
● Anwender:
● Bereitstellen von Eingaben und Daten zur
Verarbeitung durch das Programm
● Bereitstellen einer Umgebung, in der das
Programm ausführbar ist (ggf. über Administrator)
23. Nutzung von assert
● Fehlererkennung: aus internen Programmzustand
● Lösung: Änderung des Programms
● Zielgruppe für Fehlermeldungen: Entwickler
● Klare Zuständigkeit beim Aufruf von Routinen:
muss Aufrufer oder Routine auf
Fehlerbedingungen reagieren?
● Besonders nützlich zur Prüfung von übergebenen
Parametern („preconditon“)
● Dient als „ausführbare“ Dokumentation
24. Nutzung von raise
● Fehlererkennung: aus Daten und in Umgebung
● Lösung:
● Daten: korrekte und vollständige Eingabe
● Umgebung: Dateien, Netzwerk, Berechtigungen, …
● Fehler erst zur Laufzeit erkennbar
● Zielgruppe für Fehlermeldungen: Anwender
26. Anforderungen
● In Literatur oft: unklare Richtlinien („hilfreich“,
„verständlich“, ...)
● In Praxis oft: Beschreibung, was falsch ist (z.B.
„ungültiges Datum“)
● Lösungsorientierter Zugang:
● Beschreibung des Ist-Zustands und des Soll-Zustands
● Beschreibung der Maßnahmen, die zur Korrektur zu
setzen sind
● Beschreibung oder Darstellung des Zusammenhangs,
in dem der Fehler aufgetreten ist
27. Ableiten der Fehlermeldung aus
Programmcode
● Allgemein:
if height <= 0:
raise ValueError(
'height is %d but must be greater than 0' % height)
● Konkret:
if actual != expected:
raise SomeError('<actual> must be
<expected>')
28. Darstellung des Zusammenhangs
● Bei raise: Anführen von Name und Wert des
Ist-Zustands (z.B. „height is -3“)
● Bei except: ursprüngliche Fehlermeldung
beibehalten und ergänzen:
● Beschreiben der Herkunft der Fehlerursache (zB
Name und Position in Eingabedatei, Feldname in
Formular, markieren in Benutzeroberfläche, ...)
● Beschreiben der Aktion, die aufgrund des Fehlers
nicht durchführbar ist
29. Beispiel: Fehler erkennende Routine
def processSomething(height):
if height <= 0:
raise ValueError('height must be greater than 0')
# Actual processing would happen here.
pass
def processSomething(height):
if height <= 0:
raise ValueError('height must be greater than 0')
# Actual processing would happen here.
pass
30. Beispiel: Fehler berichtender Code
def processAllThings(dataFile):
try:
# Process all heights read from `dataFile`.
lineNumber = 1
for line in dataFile:
except ValueError as error:
print('cannot process %s, line %d: %s' %
Beispiel für Ausgabe im Fehlerfall:
cannot process some.txt, line 17:
height is -3 but must be greater than 0
31. Wo Fehlermeldung ausgeben?
● Bei GUI oder Web-Anwendung:
● Bei Eingaben: Feld hervorheben und Meldung unter
dem betroffenem Formularfeld
● In eigenem Fehlerdialog oder auf Fehlerseite
● Zusätzlich in Log für spätere Nachvollziehbarkeit
● Bei Services: in Log
● Bei Befehlszeilenwerkzeugen: in Konsole auf
stderr
32. Ausgabe in Log-Datei
● Mit Standard Modul logging:
http://docs.python.org/2/library/logging.html
● Mehrere Stufen zur Bewertung der Meldung, u.a.:
● Info – Informationen, welche Aktionen gesetzt werden
● Error – Fehlermeldungen
● Exception – Fehlermeldung und Stack Trace
● Debug – zusätzliche interne Detailinformationen;
interessant für Entwickler und während Fehleranalysen
● Ausgabe auf Datei, Console, Netzwerk-Socket, ...
33. Logging auf stderr
● Auch für Befehlzeilenanwendungen nutzbar
import
def processData(dataPath):
_log.info(u'read "%s"', dataPath)
with open(dataPath, 'rb') as dataFile:
# Here we would actually process the data.
pass
if __name__ == '__main__':
34. Logging auf stderr
● Was passiert im angeführten Beispiel, wenn die
Datei data.txt nicht auffindbar ist?
35. Logging auf stderr
$ python somelog.py
INFO:some:read "data.txt"
Traceback (most recent call last):
File "somelog.py", line 15, in <module>
processData('data.txt')
File "somelog.py", line 8, in processData
with open(dataPath, 'rb') as dataFile:
IOError: [Errno 2] No such file or directory: 'data.txt'
$ echo $?
1
36. Logging auf stderr
● Kein eigener Code für Fehlerbehandlung
→ kein Aufwand für Entwickler
● Auch im Fehlerfalle Schließen der Datei
→ effiziente Nutzung der Ressourcen
● Anzeige der I/O-Fehlermeldung
→ Anwender kann Fehlermeldung nachgehen
● Exit Code 1
→ etwaiges aufrufendes Shell-Script kann Fehler
erkennen
● Nachteil: Stack Trace für Anwender verwirrend und
auch nicht notwending, um Fehler zu beheben
38. Exception Hierarchie
● Exceptions sind Klassen
http://docs.python.org/2/library/exceptions#exception-hierarchy
● Gruppierung von „ähnlichen“ Fehlern über
Vererbungs-Hierarchie
● Ein try kann mehrere excepts haben
● Über Reihenfolge können verschiedene Fehler
unterschiedlich behandelt werden
39. Lösungsorientiere Nutzung
● Vom Entwickler lösbar: AssertionError
● Vom Anwender lösbar: EnvironmentError
→ Dateien, Netzwerk, Berechtigungen
● Situationsabhängig vom Entwickler oder
Anwender lösbar: restliche Exception wie
LookupError, ArithmeticError, ValueError, …
→ hier ist Präzisierung durch Entwickler
erforderlich
40. Alle anderen vom Anwender
behebaren Fehler
● Mit except abfangen und umwandeln in
eigene Exception, die klar als vom Anwender
behebbar definiert ist
● Beispiel: DataError
● Programm kann diese gleich wie
EnvironmentError behandeln
● Mit if … raise selbst erkannte Fehler können
gleich zu DataError führen
41. Beispiel DataError
● Für fehlerhafte Daten aus Eingangsdatei:
class DataError(Exception):
pass
raise DataError('height is %d but must be greater than 0'
% height)
42. Umwandeln einer Exception in
DataError
Fehler abfangen und Meldung übernehmen:
try:
# Process all heights read from `dataFile`.
for lineNumber, line in enumerate(dataFile,
start=1):
processSomething(long(line))
% (
dataFile.name, lineNumber, error))
43. Umwandeln einer Exception in
DataError
● In Python 3: Stack Trace erhalten mit Exception Chaining:
try:
# Process all heights read from `dataFile`.
for lineNumber, line in enumerate(dataFile,
start=1):
processSomething(long(line))
except ValueError as error:
raise DataError('file %s, line %d' % (
● Ursprüngliche Exception und Fehlermeldung ist in __cause__ ersichtlich
→ „gesamte“ Fehlermeldung zusammenbaubar
● Stack Trace enthält zuerst den ursprünglichen ValueError und
anschließend den verketteten DataError
44. Alle anderen vom Entwickler
behebaren Fehler
● Sind nun über „alles andere aber kein
DataError“ erkennbar
● Behandlung wie AssertionError
46. Vorlage für Programm
● Nutzt logging
● Nutzt Parser für
Befehlszeilenoptionen
● Vom Anwender
behebbare Fehler über
log.error()
● Vom Entwickler
behebbare Fehler über
log.exception()
● Setzt Exit Code 0 oder 1
def main(arguments=None):
if arguments is None:
arguments = sys.argv
# Exit code: 0=success, >0=error.
exitCode = 1
# Process arguments. In case of errors, report them and
exit.
parser = optparse.OptionParser(usage='process some
report')
parser.add_option("-o", "--out", dest="targetPath",
help="write report to FILE", metavar="FILE")
options, others = parser.parse_args(arguments)
if len(others) < 1:
# Note: parser.error() raises SystemExit.
parser.error('input files must be specified')
try:
_process(options, others)
exitCode = 0 # Success!
except KeyboardInterrupt:
_log.error('stopped as requested by user')
except (DataError, EnvironmentError) as error:
_log.error(error)
except Exception as error:
_log.exception(error)
return exitCode
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
sys.exit(main())
Von mir zu
implementieren
47. Durchführen des Hauptteils
exitCode = 1
…
try:
_process(options, others)
exitCode = 0 # Success!
except KeyboardInterrupt:
_log.error('stopped as requested by user')
except (DataError, EnvironmentError) as error:
_log.error(error)
except Exception as error:
_log.exception(error)
return exitCode
Von mir zu
implementieren
49. Wann except verwenden? (1)
● Ganz „außen“ in __main__ bzw. main()
● Bei GUI-Anwendungen: um abgeschlossene
Benutzeraktionen (action pattern)
● Zum Umwandeln von Exceptions in DataError
● Zum Umwandeln von Fehlern und gültige
Zustände
→ z.B. bei LookupError einen Defaultwert
verwenden
50. Wann except verwenden? (2)
● Insgesamt: selten und gezielt
● Wenig Aufwand für Entwickler
● Fehlerbehandlung i.d.R. Trivial:
1.Zusammenräumen (with, finally, ...)
2.Routine abbrechen (raise oder aufgetretene
Exception delegieren)
3.Aufrufer entscheidet, was zu tun ist
● Vorteile: Leicht wartbarer, kompakter Code mit
wenig Einrückebenen
51. Wann raise verwenden?
● Konsequente Namenskonventionen für Routinen:
● Prozeduren: „mach etwas“
Beispiel: sort(liste) → sortiert Liste, ändert Original
● Funktionen: „etwas“ gemäß dem gelieferten
Beispiel: sorted(liste) → liefert sortierte Kopie einer
Liste, Original bleibt unverändert
● Falls nicht möglich, das beschriebene „etwas“ zu
machen oder liefern: raise
● Damit klare und einfache Definition von
Fehlerbedingungen: alles, was daran hindert, „etwas“
zu machen
53. Lösungsorientierte
Fehlerbehandlung (1)
● gezielte Nutzung der vorhandene Python-
Mechanismen
● Unterscheidung: Wer kann Fehler beheben?
● Anwender zur Laufzeit: Daten, Umgebung
→ EnvironmentError, DataError
● Entwickler während Umsetzung: Programm
→ Assertions und Rest
● Zusammenräumen mit with, finally und
Context Manager (nicht mit __del__())
54. Lösungsorientierte
Fehlerbehandlung (2)
● Fehlerbehandlung im Programm:
● Mit if … raise neue Fehler erkennen
● Mit raise bereits erkannte Fehler meist einfach
weiterleiten
● An einigen wenigen stellen mit except abfangen
und Meldung ausgeben
● Schema für gute Fehlermeldung:
→ beschreibt die Lösung statt den Fehler
cannot do <some task>:
<something> is <actual> but must be <expected>