Ausnahmen und RegelnInhaltIm sechsten Kapitel behandeln wir die verbleibenden Themen zur Beschreibung der Programmiersprache Python und decken die folgenden Konzepte ab.
ExceptionsEs gab bisher viele Beispiele von Fehlermeldungen, zum Beispiel beim Zugriff auf Variablen, die nicht existieren. Undefined Variable
>>> new_variable Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'new_variable' is not defined >>> Im obigen Beispiel gibt es die Fehlermeldung NameError: name 'new_variable’ is not defined. Bei dieser Fehlermeldung handelt es sich um eine sogenannte Exception from Typ NameError. Bisher haben wir immer so getan, als müssten Exceptions zwangsweise dazu führen, dass die Ausführung von Programmen abgebrochen wird. Das stimmt jedoch nicht. Das besondere am Konzept von Exceptions als Fehlermeldung ist, das man sie behandeln kann und sie dann nicht mehr zwingend zum Abbruch der Ausführung eines Programms führen. Hierzu gibt es die Schlüsselwörter try und except. Mit try beginnt man einen Block, in dem möglicherweise eine Exception erwartet wird. Tritt innerhalb des try Blocks eine Exception auf bricht das Programm nicht einfach ab. Stattdessen wird der except Block ausgeführt. Tritt keine Exception auf, wird der except Block ignoriert.
Ausführung vom Programm
>>> python3 try_except.py
undefined_variable is not defined
defined_variable is defined
Im obigen Beispiel fängt man beliebige Exceptions ab, also nicht nur Exceptions vom Typ NameError, wie wir eigentlich erwarten, sondern auch alle andern. Man kann einschränken, welche Typen von Exception behandelt werden, in dem man die erwarteten Typen bei der except Anweisung angibt. NameError Exception
>>> try: ... undefined_variable ... print("undefined_variable is defined") ... except NameError: # only handles NameError ... print("undefined_variable is not defined") ... undefined_variable is not defined Tritt eine anderer Typ von Ausnahme auf, wird dieser nicht behandelt. other Exceptions
>>> try: ... 1/0 # division by zero not allowed -> ZeroDivisionError ... except NameError: # only handles NameError but not ZeroDivisionError ... print("there was a NameError") ... Traceback (most recent call last): File "<stdin>", line 2, in <module> ZeroDivisionError: division by zero Für den Fall, dass man erwartet das verschiedene Typen von Exceptions auftreten könnten, kann man diese sowohl in einem except behandeln, als auch durch mehrere except Blöcke. mehrere Exceptions
>>> try: ... 1/0 ... except (NameError, ZeroDivisionError): ... print("there was a NameError or a ZeroDivisionError") ... there was a NameError or a ZeroDivisionError >>> try: ... 1/0 ... except NameError: ... print("there was a NameError") ... except ZeroDivisionError: ... print("there was a ZeroDivisionError") ... there was a ZeroDivisionError 1. Built-in ExceptionsDie am häufigsten vorkommenden Typen von Ausnahmen sind die built-in Exceptions, die im Sprachstandard in Python direkt mitgeliefert werden. NameError und ZeroDivisionError haben wir bereits kennengelernt. Weitere Beispiele für Exception sind:
Die vollständige Liste der built-in Exceptions ist in der Python Dokumentation verfügbar. 2. Erzeugen von ExceptionsBisher haben wir uns nur angeguckt, wie man Exceptions behandelt. Jetzt wollen wir uns angucken, wie man selbst Exceptions erzeugt. Das erzeugen von Exceptions wird auch werfen genannt. Als Beispiel für das Werfen von Exceptions nutzen wir wieder unsere Wurzelfunktion. Außerdem verdeutlichen wir den Unterschied zwischen dem TypeError und dem ValueError.
Bisher haben wir die Funktion immer mit dem korrekten Typ von Objekt aufgerufen, also mit Zahlen. In der Funktion selbst wird jedoch nirgends überprüft, ob es wirklich eine Zahl ist. Was bei dem Aufruf mit einer Zeichenkette passiert, ist daher nicht direkt offensichtlich. unclear Exceptions
>>> import /home/deameni/python_examples/my_fonctions/my_sqrt >>> my_sqrt("42") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in my_sqrt TypeError: unsupported operand type(s) for -: 'int' and 'str' Wir bekommen als einen TypeError, da die Substraktion von Zahlen und Zeichenketten nicht definiert ist. Positiv hierbei ist, das der Typ der Exception, die geworfen wird, richtig ist, da Zeichenketten als Typ ja nicht unterstützt werden sollten. Die Fehlermeldung selbst ist jedoch sehr unglücklich. Statt der klaren Aussage, dass die Quadratwurzel für Zeichenketten nicht definiert ist, bekommt man eine konkrete Division in der Funktion als Problem gemeldet. Um eine bessere Fehlermeldung zu liefern, können wir selbst am Anfang der Methode überprüfen, ob es sich um eine Zahl handelt. Falls dies nicht der Fall ist, können wir mit Hilfe des Schlüsselworts raise selbst einen TypeError mit einer besseren Fehlermeldung generieren. raise Exceptions
>>> def my_sqrt_exceptions(x): ... """Returns the square root of x""" ... # checks type of parameter with isinstance ... if not (isinstance(x, float) or isinstance(x, int)): ... # raise TypeError is not numeric ... raise TypeError("Only supports int and float") ... guess = 1 ... while(abs(guess*guess-x)>0.0001): ... guess = (1/2)*(guess+x/guess) ... return guess ... >>> my_sqrt_exceptions("42") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in my_sqrt_exceptions TypeError: Only supports int and float Die nächste Eigenschaft der Quadratwurzel, die wir bisher ignoriert haben, ist die Tatsache, dass die Wurzel von negativen Zahlen im reellen undefiniert ist. Dieses Eigenschaft spiegelt sich aber nicht in unserer Funktion wieder. Führt man die Funktion mit negativen Zahlen aus, landet man in der Regel in einer Endlosschleife. Daher sollte man vor dem Ausführen des Heronverfahrens überprüfen, ob die Zahl positiv ist. Wenn dies nicht der Fall ist, kann man mit einem ValueError auf den ungültigen Eingabewert (der korrekten Klasse) reagieren. raise Exceptions 2
>>> def my_sqrt_exceptions(x): ... """Returns the square root of x""" ... if not (isinstance(x, float) or isinstance(x, int)): ... raise TypeError("Only supports int and float") ... if x<0: ... # raise ValueError for invalid input values ... raise ValueError("Square root only defined for positive numbers") ... guess = 1 ... while(abs(guess*guess-x)>0.0001): ... guess = (1/2)*(guess+x/guess) ... return guess ... >>> my_sqrt_exceptions(-42) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in my_sqrt_exceptions ValueError: Square root only defined for positive numbers 3. Benutzerdefinierte ExceptionsEs gibt Fälle, wo man für eine Anwendung eigene Ausnahmen definieren möchte, zum Beispiel weil keine built-in Exception passend ist. Hierfür kann man eigene Exceptions definieren, in dem man eine neue Klasse erstellt, die von der Klasse Exception abgeleitet ist. Benutzerdefinierte Exceptions
>>> class MyException(Exception): ... pass # empty class block requires pass ... >>> raise MyException("My very own Exception") Traceback (most recent call last): File "<stdin>", line 1, in <module> __main__.MyException: My very own Exception Man muss auch nicht direkt von Exception Ableiten. Wenn man zum Beispiel eine Bibliothek mit mathematischen Funktionen entwickelt, kann es zum Beispiel auch Sinn machen ArithmeticException als Basisklasse zur nehmen. 4. Hierarchien zwischen ExceptionsExceptions sind durch Vererbung als Klassenhierarchie organisiert. Dies ist für die Auswertung der except Anweisung wichtig: Es wird nicht nur genau die definierte Klasse behandelt, sondern auch alle ihre Kindklassen. Hierdurch kann es auch mehrere except Anweisungen geben, die auf die selbe Klasse zutreffen, weil verschiedene Hierarchieebenen der gleichen Klasse abgefangen werden. In diesem Fall zählt die Reihenfolge der except Anweisungen. Zur Veranschaulichung brauchen wir zuerst eine einfache Hierarchie.
Wenn wir nun die ChildException werfen und es except Anweisungen sowohl für die ParentException als auch für die ChildException gibt, hängt das Verhalten von der Reihenfolge der Klassen im except ab. Behandelt man zuerst die ParentException wird man nie im Block der ChildException landen können. Hierarchie Exceptions
>>> import /home/deameni/python_examples/my_fonctions/my_exception >>> try: ... raise ChildException() ... except ParentException: ... print("except for ParentException") ... except ChildException: ... print("except for ChildException") ... except for ParentException Ändert man die Reihenfolge, wird die ChildException auch vom except Block der ChildException behandelt. Hierarchie Exceptions 2
>>> import /home/deameni/python_examples/my_fonctions/my_exception >>> try: ... raise ChildException() ... except ChildException: ... print("except for ChildException") ... except ParentException: ... print("except for ParentException") ... except for ChildException 5. finally and withEs gibt Fälle, da müssen Anweisungen auf jeden Fall ausgeführt werden, egal ob eine Exception geworfen wird oder nicht. Solche Anweisungen sind häufig zum Aufräumen da: Freigabe von Resourcen, sicherstellen das Daten konsistent sind, und ähnliches. Hierfür gibt es das Schlüsselwort finally. Hiermit lässt sich ein Block definieren, der immer im Anschluss an einen try Block ausgeführt wird. Selbst wenn eine Ausgabe nicht behandelt wird, wird zuerst der finally Block ausgeführt, bevor die Exception die Programmausführung beendet. Hierarchie Exceptions 2
>>> # case 1: finally after an exception was handled >>> try: ... undefined_variable ... except NameError: ... print("except") ... finally: ... print("finally block") ... except finally block >>> # case 2: finally after leaving try without exception >>> try: ... pass ... except NameError: ... print("except") ... finally: ... print("finally block") ... finally block >>> # case 3: finally with unhandled exception >>> try: ... 1/0 ... except NameError: ... print("except") ... finally: ... print("finally block") ... finally block Traceback (most recent call last): File "<stdin>", line 2, in <module> ZeroDivisionError: division by zero Es gibt auch Objekte, die ihr Aufräumverhalten direkt mitliefern. Dies kann man über das Schlüsselwort with nutzen. Als Beispiel hierfür machen wir einen kleinen Exkurs und gucken uns die Operationen zum Lesen und Schreiben von Dateien in Python an. Exkurs: Dateien Lesen und SchreibenZugriff auf Dateien ist ein typisches Beispiel für Operationen, bei denen man sicherstellen muss, dass am Ende aufgeräumt wird. Werden Dateien nicht richtig geschlossen, kann dies unerwünschte Nebenwirkungen haben. Zum Beispiel kann man eventuell die Datei von einem anderen Programm aus nicht mehr schreiben, oder es werden nicht alle Daten sauber geschrieben, so dass es zum Datenverlust kommt. Wenn man Dateien mit Hilfe von with öffnet, wird garantiert das die Datei am Ende des with Blocks sauber geschlossen wird. Zum Öffnen von Dateien gibt es in Python die Funktion open(). Inhalte können mit read gelesen werden und mit write geschrieben werden. Mit readlines kann man eine Datei zeilenweise lesen.
Ausführung vom Programm
>>> python3 dataOperation.py def my_sqrt(x): """Returns the square root of x""" guess = 1 while(abs(guess*guess-x)>0.0001): guess = (1/2)*(guess+x/guess) return guess >>> # the content of the script are similar >>> os.system('diff my_sqrt.py sqrt-copy.py') 0
Ausführung vom Programm
>>> python3 dataOperation2.py
1 def my_sqrt(x):
2 """Returns the square root of x"""
3 guess = 1
4 while(abs(guess*guess-x)>0.0001):
5 guess = (1/2)*(guess+x/guess)
6 return guess
Regeln zum ProgrammierstilDie Bennung von Variablen, Funktionen, Klassen, und Modulen, sowie die Formatierung des Quelltextes sind sehr wichtig für die Lesbarkeit von Programmen durch Menschen. Betrachten Sie zur Veranschaulichung den folgenden Quelltext.
Der Quelltext ist syntaktisch korrekt. Inhaltlich ist er identisch zur Funktion my_sqrt. Der Quelltext ist jedoch viel schwerer zu lesen. Fragen Sie sich einfach, ob Sie verstanden hätten, was diese Funktion tut, wenn Sie nicht zufällig bereits die Wurzelfunktion kennen würden. Es gibt zwei offentliche Gründe, warum die obige Funktion schwerer zu lesen ist:
Natürlich ist die obige Zelle überspitzt dargestellt. Aber insbesondere zu kurze Namen sind ein häufig verbreitetes Problem. Für Python gibt es mit PEP 8 einen offiziellen Style Guide, in dem Richtlinien für den Programmierstil festgelegt werden. Es lohnt sich für jeden Entwickler von Python einen Blick in dieses Dokument zu werfen. Hier sind nur einige Auszüge:
|