Mit Skriptsprache Python entfaltet erst dann ihre ganze Kraft, wenn objektorientierte Konzepte zum Einsatz kommen. Sie hilft Ihnen, komplexe Probleme in überschaubre Einheiten zu zerlegen.
Im ersten Teil des Python-Kurses (09/2006, S. 80) haben Sie den interaktiven Interpreter, Datentypen und Ablaufstrukturen kennengelernt. Wie Sie im zweiten Teil (10/2006, S. 68) gesehen haben, macht Python das Aufteilen von Code in Funktionen, Module und Pakete ganz einfach. Dieser dritte Teil zeigt, wie Python Fehler behandelt und bietet einen Einstieg in die objektorientierte Programmierung.
Ausnahmebehandlung
Haben Sie schon ein Programm zum Bearbeiten von Dateien geschrieben, ist Ihnen möglicherweise aufgefallen, dass Python Fehler beim Öffenen einer Datei nicht besonders freundlich handhabt: Fehlt zum Beispiel das entsprechende File, bricht das Programm beim Aufruf datei = open("nicht_da.txt") mit der folgenden Fehlerausgabe ab:
Traceback (most recent call last?
):
File "./nicht_da.py", line 4, ?
in ?
datei = open("nicht_da.txt")
IOError: [Errno 2] No such file ?
or directory: 'nicht_da.txt'
Bei der Ausgabe handelt es sich um einen sogenannten Traceback, der zeigt, wo der Fehler auftrat und welche Funktionen beteiligt waren, bevor der Fehler auftrat. Im Beispiel gibt es nur eine Zeile direkt auf Modulebene, deshalb ist von der Aufrufreihenfolge nicht viel zu sehen. Python kennt nun Möglichkeiten zum Abfangen des Fehlers:
try:
datei = open("nicht_da.txt")
except IOError:
print "Fehler beim Öffnen de?
r Datei"
print "Und weiter nach dem F?
ehler …"
Dabei ist IOError der Typ der Ausnahme, wie er aus dem Traceback erkennbar ist. Allerdings nutzen die zwei Zeilen bei der Fehlersuche wenig, weil die Angabe der problematischen Datei in der Ausgabe fehlt. Den Dateinamen bekommen Sie durch eine kleine Änderung am Code:
try:
datei = open("nicht_da.txt")
except IOError, io_exception:
print 'Fehler beim Öffnen de?
r Datei "%s"' % io_exception.fil?
ename
print "Und weiter nach dem F?
ehler …"
Das durch io_exception bezeichnete Objekt ist ein Ausnahme-Objekt, hier vom Typ IOError. IOError-Objekte haben unter anderem ein Attribut filename, das die problematische Datei bezeichnet.
Nach dem Ändern lautet die Ausgabe des obigen Codes:
Fehler beim Öffnen der Datei "ni? cht_da.txt" Und weiter nach dem Fehler …
Wie Sie sehen, erscheint zwar der Fehlertext, das Programm geht aber über den Fehler hinweg. Beachten Sie, dass Sie eine Ausnahme nicht direkt beim fehlererzeugenden Code abfangen müssen (Listing 1).
Listing 1
import sys
def log_datei(datei_name):
return open(datei_name, 'w')
def start():
# "global" sparsam verwenden
global log
log = log_datei("prog.log")
print "Programm gestartet"
def main():
try:
start()
except IOError, exc:
print exc
sys.exit()
if __name__ == '__main__':
main()
Existiert die Datei prog.log Datei nicht oder verweigert das Betriebssystem den Zugriff, pflanzt sich der Fehler so lange fort, bis das Try/Except-Konstrukt in der Funktion main ihn abgefängt. Falls keine Fehlerbehandlung für einen bestimmten Ausnahmetyp vohanden ist, beendet sich das Programm mit einem Traceback. Unvorhergesehene Fehler fallen also immer auf.
Mehrere Ausnahmetypen
Wollen Sie mehrere mögliche Ausnahmen behandeln, bietet es sich an, diese mit verschiedenen except-Zweigen abzufangen (Listing 2). Ist die Behandlung für beide Fehlerarten gleich, sollten Sie Redundanz vermeiden, indem Sie beide except-Zweige zusammenfassen.
Listing 2
try:
tu_was()
except IOError, io_exception:
# verwende io_exception, um
# den Fehler zu behandeln
…
except OSError, os_exception:
# verwende os_exception, um
# den Fehler zu behandeln
…
try:
tu_was()
except (IOError, OSError):
…
Beim Konstrukt im Listing 3 sind die Klammern zwingend notwendig. Fehlen sie, versteht Python unter IOError, OSError ein Ausnahme-Objekt vom Typ IOError namens OSError, analog zum Beispiel IOError, io_exception weiter oben. Auch hier ist zusätzlich die Angabe eines Namens für ein Ausnahmeobjekt möglich, das Ihnen dann weitere Informationen bereitstellt.
try:
tu_was()
except (IOError, OSError), excep?
tion:
…
Das hat jedoch den Nachteil, dass exception je nach aufgetretenem Fehler einen unterschiedlichen Typ und damit verschiedene Attribute hat. Um sich Fallunterscheidungen zu ersparen, sollten Sie getrennte except-Zweige nehmen. Wollen Sie alle Ausnahmen – egal welche – abfangen, verwenden Sie except ohne Ausnahmetypen:
try:
tu_was()
except:
…
Diese Möglichkeit spart auf den ersten Blick Schreibarbeit. Sie hat aber ihre Tücken: Damit fallen alle Ausnahmen unter den Tisch, unter anderem KeyboardInterrupt (Drücken von [Strg]+[C]) und SyntaxError (zum Beispiel beim Importieren eines fehlerhaften Moduls). Daher sollten Sie die obige Variante nur auf der obersten Ebene einer Software verwenden, um beispielsweise Traceback-Ausgaben in einem Produktionssystem zu verhindern.
Fangen Sie in einer Try/Except-Anweisung mehrere spezifische Fehler parallel zu einer allgemeinen except-Zeile ab, kommt es auf die richtige Reihenfolge an: Die Zeilen mit den speziellen Exceptions stehen vor dem allgemeinen except (Listing 3). Alle Try/Except-Konstrukte erlauben einen else-Zweig, der zum Zuge kommt, wenn keine Ausnahme ausgelöst auftrat (Listing 4).
Listing 3
try:
tu_was()
except IOError:
…
except OSError:
…
except:
…
Listing 4
try:
datei = open("test.txt")
except IOError:
print "I/O-Fehler"
else:
print "alles ok"
Aufräumen
Manchmal ist es gewünscht, bestimmten Code auszuführen, egal ob ein Fehler auftritt oder nicht. Das betrifft freizugebende Ressourcen wie Dateiobjekte, Sockets oder Datenbankverbindungen. Zu diesem Zweck gibt es das Konstrukt try ... finally: Im Beispiel wird die Datenbankverbindung geschlossen, gleichgültig, ob tu_was eine Ausnahme hervorgerufen hat oder nicht.
conn = hole_verbindung()
try:
tu_was(conn)
finally:
conn.close()
Beachten Sie, dass das Konstrukt Try/Finally nicht das Gleiche macht, wie der else-Zweig in Try/Except/Else. Der finally-Block kommt immer zum Zuge, der else-Block nur, wenn keine Ausnahme auftritt. Bis einschließlich Python 2.4 müssen Sie try ... except und try ... finally verschachteln, wenn Sie beides brauchen (Listing 5). Ab Python 2.5 funktioniert auch die Kombination auf einer Ebene mit derselben Bedeutung [1].
Listing 5
try:
try:
…
except …:
…
finally:
…
Listing 6
try:
…
except …:
…
finally:
…
Ausnahmen auslösen
Stellen Sie sich vor, Sie wollen von bestimmten Bedingungen abhängig machen, ob eine Ausnahme in Ihrer Software als Fehler gilt. Zum Beispiel könnten Sie versuchen, eine – optionale – Konfigurationsdatei zu öffnen. Ist sie nicht vorhanden, ignoriert das Programm den Fehler; ist sie aber vorhanden und nicht lesbar, löst der Code die abgefangene Ausnahme erneut aus. Dazu verwenden Sie mit die Anweisung raise (siehe Listing 7).
Listing 7
try:
config_file = open("/etc/myprog/config")
except IOError, io_exception:
if io_exception.errno == 2:
# Datei nicht gefunden: ignorieren
pass
else:
# Ausnahme (erneut) auslösen
raise
Mit raise lösen Sie nicht nur die letzte Ausnahme erneut aus; über eine selbstgewählte Ausnahme mit Typ und eventuellen Parametern erzeugt Sie zum Beispiel einheitlich formatierte Fehlermeldungen. So produziert raise ValueError("Zahl %s muss positiv sein" % x) ein ValueError-Objekt mit dem Fehlertext in Klammern. Weiter unten in diesem Artikel erfahren Sie, wie Sie eigene Ausnahmetypen erzeugen.
Objektorientiert
Beim Schreiben von größeren Programme hilft eine objektorientierte Struktur, da sie den Code von ganz alleine gliedert und so auch anderen das Verständnis für die Funktion erleichtert. Dabei steht nicht im Vordergrund, was einzelne Funktionen mit welchen Daten machen, sondern Dinge (Objekte), ihre Eigenschaften (beschrieben durch Attribute) und ihr Verhalten (beschrieben durch Methoden). Eine Klasse beschreibt eine bestimmte Art von Dingen, ist also allen Dingen dieser Art gemeinsam.
Ein Beispiel veranschaulicht, was diese trockene Theorie bedeutet: Nehmen wir eine Klasse “Person”. Im interaktiven Interpreter von Python definieren Sie eine solche Klasse folgendermaßen:
>>> class Person(object): … pass
Obwohl die Angabe (object) nicht zwingend notwendig ist, empfielt sie sich, um Klassen als Klassen neuerer Art auszuzeichnen. Zum Hintergrund: Zwischen den Python-Versionen 2.1 und 2.2 überarbeiteten die Entwickler die Implementation von Klassen überarbeitet. Alte Klassen dürfen Sie aus Kompatibilitätsgründen nach wie vor mit class Klasse: definieren. Die richtige Schreibweise verlangt aber die Angabe der Klammerblocks.
Sie erzeugen, oder genauer instanzieren, Objekte einer Klasse, indem Sie die Klasse wie eine Funktion verwendet. Mit mike = Person() und willi = Person() erschaffen Sie zwei Person-Instanzen. Durch die Definition ist es kaum möglich, zwei Person-Objekte zu unterscheiden. Das geht nur anhand einer Objektkennung, die Sie mit der Python-Funktion id erhalten:
>>> id(mike) -1213247604 >>> id(willi) -1213256052
Attribute
Wesentlich nützlicher sind die Person-Objekte, wenn Sie Eigenschaften der entsprechenden Personen darin speichern:
>>> willi = Person() >>> willi.vorname = "Willi" >>> willi.nachname = "Wusel" >>> print willi.vorname Willi
Wie Sie sehen, dürfen Sie in Objekten beliebige Eigenschaften (Attribute) anlegen und abfragen. Das gilt allerdings nicht für Objekte, wie die in Python eingebauten Typen, zum Beispiel list:
>>> L = [] >>> L.neu = 1 Traceback (most recent call last? ): File "<stdin>", line 1, in ? AttributeError: 'list' object ha? s no attribute 'neu'
Methoden
Objekte können mittels Methoden auch Verhalten implementieren. Zum Beispiel können wir Personen gehen lassen:
>>> class Person(object): … def gehe(self, tempo=3): … "Setze Tempo in km/h?." … self.tempo = tempo
Das erste Argument einer Methode bezeichnet das Objekt, für das die Methode aufgerufen wird und heißt konventionsgemäß self. Danach folgen weitere Parameter, analog zu Funktionsdefinitionen (s. zweiter Artikel der Python-Einführung). In der Methode gehe besitzt tempo den Vorgabewert 3. Die Anweisung self.tempo = tempo verknüpft die Instanzvariable tempo des durch self referenzierten Person-Objekts mit dem Wert des Arguments tempo. Nach der Zuweisung bezeichnen also self.tempo und tempo das gleiche Objekt.
Die Methode gehe ist nun wie folgt benutzbar:
>>> willi = Person() >>> willi.vorname = "Willi" >>> print willi.vorname Willi >>> willi.gehe() >>> print willi.tempo 3 >>> willi.gehe(5) >>> print willi.tempo 5 >>> mike = Person() >>> # mike.tempo ist undefiniert … print mike.tempo Traceback (most recent call last? ): File "<stdin>", line 1, in ? AttributeError: 'Person' object ? has no attribute 'tempo' >>> mike.gehe(4) >>> print mike.tempo 4 >>> # unabhängig von mike.tempo … print willi.tempo 5
Konstruktor
Wie das obige Beispiel zeigt, erzeugt der Zugriff auf ein noch nicht definiertes Attribut einen AttributeError. Um diesen zu vermeiden, setzen Sie am besten einen Wert in der so genannten Konstruktormethode __init__, die Python beim Erzeugen eines Objekts ausführt:
>>> class Person(object): … def __init__(self): … # Person steht … self.gehe(0) … def gehe(self, tempo=3): … "Setze Tempo in km/h." … self.tempo = tempo … >>> mike = Person() >>> print mike.tempo 0
Auch die Definition der __init__-Methode erlaubt Argumente:
>>> class Person(object): … def __init__(self, vorna?me, nachname): … self.vorname = vorna?me … self.nachname = nach?name … self.gehe(0)
Im Gegensatz zu C++ und Java gibt es höchstens einen Konstruktor; das “Überladen” der Konstruktormethode durch unterschiedliche Parameterlisten ist nicht möglich. Programmierer umgehen diese Einschränkung in Python üblicherweise auf eine der Arten, die der Kasten “Mehrfache Methoden-Schnittstellen” beschreibt.
Mehrfache Methoden-Schnittstellen
In diesem Beispiel akzeptiert der Konstruktor einer Klasse LogFile entweder ein Dateiobjekt oder einen Dateinamen, den er in ein Dateiobjekt umwandelt. Der Beispielcode unterscheidet nur zwischen zwei Aufrufarten, prüft aber ansonsten keine Parameter und fängt auch keine Fehler ab.
None-Vorgabewerte
Das Beispiel verwendet im Konstruktor sowohl für das Dateiobjekt als auch für den Dateinamen als Vorgabe den speziellen Wert None, gegen den Sie auch vergleichen dürfen.
class LogFile(object):
def __init__(self, file_object=None, file_name=None):
# prüfe, ob genau ein Parameter ungleich None ist
if [file_object, file_name].count(None) != 1:
# Fehlerbehandlung
…
if file_name is not None:
self.file_object = open(file_name, 'w')
else:
self.file_object = file_object
Um nun die Klasse richtig zu instanzieren, ist es notwendig, im Aufruf den Namen des Arguments zu verwenden. Der Aufruf log_file = LogFile(file_name="log_datei_name") zeigt, wie das geht. Das Unterstützen verschiedener Schnittstellen in einer Methode ist wohl die sauberste Art des Programmierens. Falls in einer Schnittstelle None ein sinnvoller Wert ist und sich damit nicht zur Anzeige eines fehlenden Arguments eignet, verwenden Sie eine Hilfsklasse.
class _MissingParameter(object):
pass
class LogFile(object):
def __init__(self,
file_object=_MissingParameter,
file_name=_MissingParameter):
…
Der führende Unterstrich in _MissingParameter deutet konventionsgemäß an, dass die Klasse nur intern zum Einsatz kommt, dass heißt, innerhalb des Moduls, das LogFile definiert. Auch hier ist, wie beim vorher beschriebenen Verfahren, der Name des Arguments beim Methodenaufruf verpflichtend.
Eigenschaften
Hierbei findet eine Prüfung statt, ob ein übergebenes Argument zum Beispiel ein bestimmtes Attribut besitzt oder ob eine bestimmte Operation möglich ist. Im Beispiel arbeitet die Funktion open auf der Variable. Beim Auftreten eines Fehlers geht die Funktion davon aus, dass es sich bei dem Argument nicht um einen Dateinamen handelt.
class LogFile(object):
def __init__(self, file_name_or_object):
try:
self.file_object = open(file_name_or_object, 'w')
except TypeError:
self.file_object = file_name_or_object
Ein Vorteil dieses Verfahrens ist die Kompaktheit des Codes. Der Nachteil liegt, dass Sie schon beim Programmieren die Art der auszulösenden Ausnahme kennen müssen (hier TypeError). Verarbeiten Sie das mehrdeutige Argument in mehreren Schritten, ist eventeull schwer feststellbar, ob eine Ausnahme gutmütig ist, also nur im Zusammenhang mit einer Fallunterscheidung auftrat, oder ob es sich womöglich um einen Programmierfehler handelt.
Verändern Sie die Schnittstelle im Konstruktor entsprechend, tritt das Problem deutlicher zutage. In diesem Fall hängt die Hilfsfunktion einen festen Pfadbestandteil an den Dateinamen an:
def _in_var_log(file_name):
return "/var/log/" + file_name
class LogFile(object):
def __init__(self, file_name_or_object):
try:
self.file_object = open(_in_var_log(file_name_or_object), 'w')
except TypeError:
self.file_object = file_name_or_object
Der TypeError tritt hier – wie oben – bei der Übergabe eines Dateiobjekts auf. Übergeben Sie jedoch irrtümlich einen Zahlenwert, meldet sich er die Hilfsfunktion beim Verbinden mit /var/log/ – allerdings ebenfalls mit einem TypeError. Unter Umständen fällt der Fehler aber auch erst viel später auf. Das Beispiel zeigt, dass diese Form von Aufrufschnittstellen für Fehler anfällig ist. Halten Sie daher den Code von vorneherein möglichst einfach, um Fehler zu vermeiden.
Explizite Typprüfung
Wer bereits erste Erfahrungen mit C++ oder Java gesammelt hat, ist eventuell versucht, das implizite Unterscheiden von Typen überladener Konstruktoren in Python nachzubilden. Im LogFile-Beispiel könnte das so aussehen:
import types
class LogFile(object):
def __init__(self, file_name_or_object):
if isinstance(file_name_or_object, types.FileType):
self.file_object = file_name_or_object
else:
self.file_object = open(file_name_or_object, 'w')
Diese Variante verträgt sich nur sehr schlecht mit Python-Prinzipien. Zum Beispiel versagt sie für so genannte dateiartige Objekte (z.?B. Objekte der StringIO-Klasse [2]) und schränkt damit die Flexibilität ein, die Python so leistungsfähig macht.
Vermeidung mehrfacher Methoden-Schnittstellen
Normalerweise ist der Zweck mehrfacher Konstruktorschnittstellen, ein Objekt auf verschiedenen Wegen zu initialisieren. Das erreichen Sie aber auch durch zusätzliche Initialisierungsmethoden:
class LogFile(object):
def init_from_file_object(self, file_object):
self.file_object = file_object
def init_from_file_name(self, file_name):
self.file_object = open(file_name, 'w')
Dieses Beispiel benötigt keine Methode __init__ mit Typunterscheidung, sondern bietet je nach gewünschtem Verhalten einen passenden Methodenaufruf an:
log_file = LogFile()
log_file.init_from_file_name("log_datei_name")
Dieser Ansatz ist sehr geradlinig und deshalb leicht verständlich. Ein möglicher Nachteil dieser Programmierschnittstelle liegt darin, dass beim Erzeugen der Instanz zunächst ein halbfertiges Objekt entsteht, was sich in nebenläufigen (multi-threaded) Programmen manchmal als ein Problem erweist. Das beschriebene Verfahren ist, entsprechend abgewandelt, natürlich auch bei anderen Methoden als Konstruktoren möglich.
Methoden = Attribute
Im Gegensatz zu manchen anderen Sprachen sind für Python Methoden nichts anderes als ausführbare Attribute (Listing 8). Der Begriff “bound” bedeutet, dass die Methode an ein bestimmtes Objekt (hier mike) gebunden ist; “unbound” bezieht sich auf die der Klasse zugeordnete Methodendefinition.
Listing 8
>>> print mike.gehe <bound method Person.gehe of <__main__.Person object at 0xb7c8a52c>> >>> print callable(mike.gehe) True >>> print Person.gehe <unbound method Person.gehe>
Vererbung
Durch die sogenannte Vererben oder Ableiten greift eine Klasse auf den Code aus anderen Klassen zu. Im Listing 9 sehen Sie ein Beispiel, das die zuletzt angegebene Person-Klasse verwendet. Die Zeilennummern gehören nicht zum Programm, sondern dienen nur der Referenz im Text.
Listing 9
class LinuxUserAutor(Person):
"""Ein Autor eines oder mehrerer Artikel im LinuxUser."""
def __init__(self, vorname, nachname):
"""Initialisiere ein Autor-Objekt."""
Person.__init__(self, vorname, nachname)
self.artikel = []
def schreibe(self, artikel_titel):
"""
Füge den bisher geschriebenen Artikeln einen mit dem
Titel artikel_titel hinzu.
"""
self.artikel.append(artikel_titel)
willi = LinuxUserAutor("Willi", "Wusel")
willi.schreibe("Seltsame Programmbeispiele")
print willi.artikel # ["Seltsame Programmbeispiele"]
willi.gehe(5)
print willi.tempo # Ergebnis: 5
Die class-Anweisung in Zeile 1 enthält in den Klammern den Namen der Klasse (Basisklasse), von der die neue Klasse erbt. Für Programmierer anderer objektorientierter Sprachen: Python unterstützt auch Mehrfachvererbung; Informationen zur Suchreihenfolge bei komplexen Hierarchien finden Sie in [3]. Damit erhält die neue Klasse alle Attribute der Basisklasse.
Die Anweisung in Zeile 6 ruft den Konstruktor der Basisklasse auf, also Person.__init__, bevor in Zeile 7 das neue Autor-Objekt mit eine Liste der veröffentlichten Artikel erhält. Durch diese Zuweisung im Konstruktor bekommt jeder Autor seine eigene Artikelliste. Die Codezeilen 9 bis 14 definieren eine neue Methode schreibe, die der Artikelliste einen neuen Artikel (bezeichnet durch dessen Namen) hinzufügt.
Die Zeilen 16 bis 20 zeigen die Verwendung der Klasse LinuxUserAutor. In Zeile 16 nimmt der Konstruktor die gleichen Parameter wie der der Person-Klasse, was der Definition in Zeile 4 zu entnehmen ist. Der Autor darf nun Artikel schreiben (Zeilen 17 und 18). Zusätzlich hat ein Autor durch die Vererbung alle Eigenschaften der Person-Klasse, so dass in den Zeilen 19 und 20 die Verwendung der Methode Person.gehe möglich ist. Abbildung 1 zeigt die Vererbungshierarchie von Person und LinuxUserAutor als UML-Klassendiagramm.

Person und LinuxUserAutor in einem UML-Klassendiagramm.” width=”300″ height=”183″ />
Person und LinuxUserAutor in einem UML-Klassendiagramm.Aggregation
Die Vererbung sollten Sie nur verwenden, wenn die abgeleitete Klasse vom Inhalt verwandt mit der Basisklasse ist. Ein Autor ist bspielsweise eine Art Person. Gibt es eine solche Beziehung nicht, benutzen Sie die Aggregation. Hierbei enthält eine Klasse Objekte einer anderen Klasse als Daten statt von der Klasse zu erben. Dieser Code mit Vererbung ist nicht empfehlenswert:
class Prozessor(object):
…
class Computer(Prozessor):
…
Verwenden Sie statt dessen lieber diesen Ansatz mit Aggregation:
class Prozessor(object):
…
class Computer(object):
def __init__(self):
self.prozessor = Prozess?
or()
…
Ausnahmen als Klassen
Nun ist die Zeit reif für einen Nachtrag zum behandeln von Ausnahmen: Neben den in Python eingebauten Ausnahmentypen [5] eröffnet die Sprache die Möglichkeit, eigene Ausnahmen zu programmieren. Es empfiehlt sich, diese Möglichkeit nach Kräften zu nutzen. Dazu leiten Sie von der eingebauten Klasse Exception ab (siehe Listing 10).
Listing 10
>>> class FalscheRichtung(Exception):
… pass
…
>>> class Person(object):
… def gehe(self, tempo=3):
… """Setze Tempo in km/h."""
… if tempo < 0:
… raise FalscheRichtung("rueckwaerts gehen ist gefaehrlich")
… self.tempo = tempo
… print "Tempo ist jetzt", tempo, "km/h."
…
>>> willi = Person()
>>> willi.gehe(3)
Tempo ist jetzt 3 km/h.
>>> willi.gehe(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 4, in gehe
__main__.FalscheRichtung: rueckwaerts gehen ist gefaehrlich
Der Einsatz eigener Ausnahmetypen hat den Vorteil, dass beim gezielten Prüfen auf bestimmte Ausnahmetypen in Try/Except-Anweisungen entsprechend auf das Ergebnis reagieren. Sie testen also nicht auf einen ValueError, der je nach Test viele Ursachen haben könnte, sondern auf FalscheRichtung, was Ihnen anzeigt, dass eine Person versucht hat, Ihren Programmcode falsch aufzurufen.
Um eine möglichst genaue Kontrolle über die auftretenden Fehler zu haben, ist es üblich, verzweigte Vererbungshierarchien aufzubauen. Schreiben Sie beisielsweise ein Modul, könnte an dessen Anfang Folgendes stehen:
class Fehler(Exception): pass class Dateifehler(Fehler): pass class Formatfehler(Fehler): pass
Damit können Sie durch
import modul
try:
modul.tu_was()
except modul.Fehler:
fehlerbehandlung()
sowohl Dateifehler als auch Formatfehler auf einmal abfangen. Alternativ können Sie beide Fehler getrennt behandeln:
import modul
try:
modul.tu_was()
except modul.Dateifehler:
behandle_dateifehler()
except modul.Formatfehler:
behandle_formatfehler()
Richtig unterscheiden
Sie könnten auf die Idee kommen, Fehler nur anhand der Fehlertexte zu unterscheiden statt über ihre Klassen. Tun Sie das, aus mehreren Gründen, bitte nicht:
- Die Fehlertexte zu den eingebauten Ausnahmen in Python ändern sich unter Umständen von Version zu Version. Eine auf dem Fehlertext beruhende Unterscheidung würde dann beispielsweise in Python 2.4 funktionieren, aber nicht mehr in Python 2.5.
- Auch Autoren von Modulen, die nicht zur Python-Distribution gehören, ändern gelegentlich Fehlertexte.
- Die Texte fallen in mehrsprachigen Programmen unterschiedlich aus.
Machen Sie Ihre Programme daher robuster, indem Sie Ausnahmen immer nur anhand ihrer Klassen unterscheiden. Definieren Sie im Zweifelsfall eine Vererbungshierarchie von Ausnahmetypen.
Glossar
-
objektorientierte Programmierung
-
Objektorientiertes Programmieren stellt konkrete und abstrakte Dinge mit ihren Eigenschaften und ihrem Verhalten in den Mittelpunkt des Softwareentwurfs. Das führt in der Regel zu einer besseren Wiederverwendbarkeit und Wartbarkeit von Code gegenüber dem älteren prozeduralen Programmieren.
-
Ausnahme
-
Fehlersituation während des Programmablaufs.
-
Sockets
-
Bidirektionale Softwareschnittstellen, die es einer Anwendung auf definierte Weise ermöglichen, mit der Netzwerkimplementation des Betriebssystems oder einem anderen Prozess zu kommunizieren.
-
UML
-
Die Unified Modeling Language [4] definiert Diagrammtypen und -Elemente für die Softwareentwicklung.
Infos
[1] Neue Try/Except/Finally-Syntax: http://docs.python.org/dev/whatsnew/pep-341.html
[2] StringIO-Modul: http://docs.python.org/lib/module-StringIO.html
[3] Suchreihenfolge in komplexen Vererbungshierarchien: http://www.python.org/download/releases/2.2/descrintro/#mro
[4] Unified Modeling Language: http://de.wikipedia.org/wiki/Unified_Modeling_Language
[5] Eingebaute Ausnahmen: http://docs.python.org/lib/module-exceptions.html




