Zur Version ohne Bilder
freiesMagazin  Python-Sonderausgabe April 2011 (ISSN 1867-7991)
Diese Sonderausgabe von freiesMagazin beschäftigt sich allein mit dem Thema Python-Programmierung. Auf den Wunsch der Leser hin wurden die sechs Artikel von Daniel Nögel, die in den freiesMagazin-Ausgaben Oktober 2010 bis März 2011 erschienen sind, zusammengefasst und liegen nun als zusammenhängende Kompilation vor. Auf die Art kann jeder Python-interessierte Neuling die erste Schritte in der Python-Welt wagen und lernt so neben den Grundstrukturen und Sprachelementen der Sprache in den fortgeschrittenen Teilen auch die Anbindung an einer Datenbank, um eine Musikverwaltung aufzubauen.
Wir wünschen viel Spaß mit dieser Python-Sonderausgabe.
Ihre freiesMagazin-Redaktion
Zum Index

Python-Programmierung: Teil 1 – Hallo Welt

von Daniel Nögel Python erfreut sich seit einiger Zeit großer Beliebtheit bei Anfängern und Fortgeschrittenen. Die Sprache überzeugt durch Einfachheit und die große Zahl mitgelieferter Bibliotheken. Sowohl als Skript-Sprache, etwa für GIMP, als auch bei eigenständigen Projekten ist Python immer häufiger anzutreffen. Bekannte Programme wie PiTiVi oder BitTorrent setzen auf Python und auch im Hintergrund von Google und Youtube soll die mächtige Sprache nicht mehr wegzudenken sein. Dieser Artikel bildet den Anfang einer mehrteiligen Einführung in die Programmiersprache Python.
Python ist mittlerweile bei allen großen Distributionen vorinstalliert – meist in der Version 2.6+. In der Version 3.0 gab es einige größere Änderungen, die hier – wo möglich – bereits übernommen werden. Wo nicht möglich, wird auf die bevorstehende Änderung hingewiesen.
Gleichzeitig sei aber auch angemerkt, dass diese Einführung nicht jede Methode und jede Funktion erörtern kann, die zum Standardrepertoire von Python gehört. Vielmehr soll ein Einblick in die Sprache geliefert werden, der – wo es notwendig ist – hilfreiche und wichtige Methoden kurz anspricht. Für tiefer gehende Einblicke empfiehlt sich beispielsweise die offizielle Dokumentation von Python [1].

Die interaktive Shell

Python-Skripte werden nicht kompiliert, sondern zur Laufzeit von einem Interpreter ausgeführt. Python-Skripte sind somit immer direkt lauffähig. Der Python-Interpreter hat zudem einen interaktiven Modus – hier können Befehle direkt abgesetzt werden.
Dieser interaktive Modus kann in einem Terminal mit dem Befehl
$ python
gestartet werden.

Die interaktive Python-Konsole nach dem Start (hier: Version 2.6.5).
Hinter der Eingabeaufforderung (>>>) können beliebige Python-Befehle abgesetzt werden. Die Befehlszeile
>>> print("Hallo Python!")
gibt in der nächsten Zeile erwartungsgemäß Hallo Python! aus. Auch Berechnungen lassen sich direkt in ihr durchführen:
>>> 3+7 
10 
>>> 7*10 
70 
>>> 3-7 
-4 
>>> 8/4 
2 
>>>8/3
2
Die wichtigsten mathematischen Operatoren sind damit gleich bekannt: Die Verwendung von „+“, „-“, „/“ und „*“ sollte keine Schwierigkeiten bereiten. Auffällig erscheint allerdings das letzte Ergebnis. In den Python-Versionen vor 3.0 geht der Python-Interpreter bei Divisionen davon aus, dass der Benutzer eine Ganzzahldivision durchführen möchte. Erst die Eingabe
>>> 8/3.0
führt zum erwünschten Ergebnis und zeigt auch Nachkommastellen an [2].
Die interaktive Konsole ist ideal, um erste Erfahrungen mit Python zu sammeln. Für größere Projekte empfiehlt sich aber ein Texteditor.
Im Folgenden soll es so gehalten werden, dass Codeblöcke mit Eingabeaufforderung (>>>) immer in der Python-Konsole ausgeführt werden. Zeilen ohne diese Zeichen sind als Ausgabe der Konsole zu interpretieren.
Codeblöcke ohne Eingabeaufforderung sind meist als allgemeine Beispiele und Veranschaulichungen zu verstehen.

Hallo Welt

Ein erstes kleines Skript ist im Texteditor der Wahl schnell erstellt. Anders als in der interaktiven Python-Konsole, wo Eingaben direkt berechnet und auf den Bildschirm ausgegeben werden, benötigt man in eigenständigen Skripten eine Funktion, welche die gewünschten Informationen auf dem Bildschirm ausgibt – dazu dient in Python die print()-Funktion. Nur in der interaktiven Konsole kann auf diese Funktion in der Regel verzichtet werden (wie etwa bei den mathematischen Operationen oben).
#!/usr/bin/env python
# -*- coding: utf-8 -*-

print("Hallo Welt!")
Listing: hello_world.py
Diese Zeilen werden als hello_world.py gespeichert. Die Datei hello_world.py wird nun mit dem Befehl
$ chmod +x hello_world.py
als ausführbar markiert und schließlich mit
$ ./hello_world.py
oder
$ python hello_world.py
gestartet. Es erscheint folgende Meldung im Terminal:
Hallo Welt!
Das erste kleine Python-Skript ist funktionsfähig! Bei der ersten Zeile handelt es sich um die sogenannte Shebang-Zeile (siehe „Shebang – All der Kram“, freiesMagazin 11/2009 [3]). Hier wird festgelegt, dass die Datei mit dem Python-Interpreter auszuführen ist. Die zweite Zeile informiert den Python-Interpreter über die verwendete Zeichenkodierung. Diese beiden Zeilen sollten in allen Python-Dateien vorhanden sein. Ohne Hinweis auf die Kodierung kann es zu Problemen mit Umlauten in Zeichenketten kommen.

Das erste „nützliche“ Programm

Als nächstes soll ein etwas ambitionierteres Projekt in Angriff genommen werden: Der Benutzer soll seinen Namen eingeben können und diesen dann in einer Box aus Gleichheitszeichen dargestellt bekommen.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

name = raw_input("Hallo! Wie heisst du? ")
name_with_borders = "= {0} =".format(name)
line = "=" * len(name_with_borders)

print(line)
print(name_with_borders)
print(line)
Listing: your_name.py
Hier gibt es schon Neues zu sehen: In der vierten Zeile wird der Benutzer nach seinem Namen gefragt. Die Funktion raw_input() gibt die als Parameter übergebene Zeichenkette auf dem Bildschirm aus und wartet dann auf die Eingabe des Benutzers. Diese wird nach dem Drücken von „Enter“ in der Variable name gespeichert (genauer: es wird ein Zeichenketten-Objekt erstellt, auf das die Variable verweist). Erst danach werden die übrigen Zeilen des Skriptes verarbeitet.
Achtung: Ab Python 3 heißt es input() und nicht mehr raw_input(). Bis Python 3 ist von der Verwendung von input() dringend abzuraten, weil die Benutzereingabe direkt vom Interpreter ausgeführt wird – das ist in den allermeisten Fällen nicht erwünscht.
In Zeile 5 wird die Eingabe des Nutzers mit Gleichheitszeichen umgeben. Dazu wird die Zeichenkette = {0} = erzeugt und die Zeichenfolge {0} mit Hilfe der format()-Methode durch den Inhalt der Variable name ersetzt. In einem späteren Teil dieser Reihe wird auf diese Art der Ersetzung genauer eingegangen werden. In Zeile 6 wird mit der Funktion len() die Länge des Benutzernamens inklusive Gleichheits- und Leerzeichen ermittelt und dann die entsprechende Anzahl von Gleichheitszeichen ausgegeben. Dazu wird hier der *-Operator benutzt – auch Zeichenketten lassen sich in Python also „multiplizieren“! So entsteht eine Folge von Gleichheitszeichen, die so lang ist, wie die Zeichenkette name_with_borders.
In Zeile 9 wird die in Zeile 5 erstellte Zeichenkette auf dem Bildschirm ausgegeben, Zeilen 8 und 10 geben jeweils die in Zeile 6 erstellte „Linie“ auf dem Bildschirm aus. Das Gesamtergebnis sieht dann wie folgt aus:
Hallo! Wie heisst du? Margot
==========
= Margot =
==========

Zeichenketten

Jetzt wurden bereits erste Erfahrungen mit Zeichenketten gesammelt. Diese sollen hier vertieft werden: Zeichenketten müssen immer von Anführungszeichen umschlossen werden. Möglich sind einfache und doppelte Anführungszeichen:
name = "Bernd"
name = 'Bernd'
Es gibt keinen Unterschied zwischen diesen beiden Varianten – die Verwendung ist letztlich Geschmackssache. Allerdings sollte darauf geachtet werden, dass innerhalb einer Zeichenkette keine äußeren Anführungsstriche vorkommen können:
message = "Ich heisse "Bernd"!"
führt also zu einem Fehler. Stattdessen kann in solchen Fällen der jeweils andere Anführungsstrich genutzt werden:
message = "Ich heisse 'Bernd'!"
oder
message = 'Ich heisse "Bernd"!'
In Python gibt es außerdem aber noch dreifache Anführungsstriche (" oder “'). Innerhalb von dreifachen Anführungsstrichen kann man die einfachen und doppelten Anführungszeichen nach Belieben verwenden und sogar Zeilenumbrüche sind möglich:
print("""Hier kann ich " und ' nach Belieben einsetzen!
Ausserdem sind sogar Zeilenumbrueche moeglich!""")
Wie in vielen anderen Sprachen gibt es in Python natürlich auch die Möglichkeit, Zeichenketten durch Voranstellen eines \ zu „escapen“, d. h. so zu markieren, dass der Interpreter nicht darüber stolpert. Das fehlerhafte Beispiel von oben sähe mit Escape-Zeichen dann so aus:
message = "Ich heisse \"Bernd\"!"
Mit dem Escape-Zeichen lassen sich auch Zeilenumbrüche in Zeichenketten mit einfachen und doppelten Anführungsstrichen erzwingen – dies wäre sonst nicht möglich:
>>> print("Ein \nZeilenumbruch") 
Ein 
Zeilenumbruch 
In der Python-Dokumentation finden sich weitere Escape-Sequenzen [4].

Zeichenketten näher betrachtet

Zeichenketten in Python gehören – wie auch Zahlen – zu den unveränderbaren Datentypen. Jede Veränderung an einer Zeichenkette liefert immer eine neue Zeichenkette zurück.
>>> text1 = "HALLO!!!" 
>>> text2 = text1.lower() 
>>> print(text1)
'HALLO!!!' 
>>> print(text2)
'hallo!!!' 
Hier wurden die Großbuchstaben mit der Methode lower() in Kleinbuchstaben „umgewandelt“. Tatsächlich bleibt text1 davon aber unberührt; stattdessen wird eine völlig neue Zeichenkette erzeugt. Natürlich ist es aber möglich, eine Variable direkt neu zuzuweisen, so dass die Unveränderbarkeit von Strings in der Praxis kaum Bedeutung hat:
>>> text = "HALLO!!!" 
>>> text = text.lower() 
>>> print(text)
'hallo!!!' 
Hier wird zunächst der Variable text die Zeichenkette HALLO!!! zugewiesen. Durch die Methode lower() wird eine neue Zeichenkette mit Kleinbuchstaben erstellt. Nun zeigt die Variable text auf die neu erstellte Zeichenkette. Da jetzt keine Variable mehr auf die alte Zeichenkette zeigt, wird diese bei Zeiten automatisch aus dem Speicher gelöscht.
Analog zu lower() gibt es mit upper() eine Methode, die eine Zeichenkette mit ausschließlich Großbuchstaben erzeugt. Mit swapcase() werden kleine Buchstaben zu Großbuchstaben und umgekehrt. Geradezu unerlässlich ist die replace()-Methode. Sie ersetzt alle Vorkommen einer gesuchten Zeichenfolge innerhalb einer Zeichenkette:
>>> "Ich finde Python doof".replace("doof", "super") 
'Ich finde Python super' 
In der Python-Dokumentation finden sich viele weitere nützliche Zeichenketten-Funktionen [5].

Richtig einrücken

Viele Programmiersprachen kennen bestimmte Kontrollstrukturen, die den Programmfluss in besonderer Weise beeinflussen. Hier ein Beispiel in Pseudocode:
zaehler = 1
solange zahler <= 5 wiederhole:
gib zaehler auf dem Bildschirm aus
erhoehe zaehler um 1
gib "fertig" auf dem Bildschirm aus
Klar: Hier wird der Zähler von 1 bis 5 hochgezählt und jeweils auf dem Bildschirm ausgegeben. Aber wie oft wird „fertig“ auf den Bildschirm geschrieben? Es wird deutlich, dass dem Interpreter/Compiler irgendwie mitgeteilt werden muss, welche Information noch zur Kontrollstruktur gehört und wo der normale Programmfluss fortgesetzt wird.
Viele andere Programmiersprachen lösen das Problem mit geschweiften Klammern, die Anfang und Ende des auszuführenden Codeblocks markieren. In Python gibt es derartige Klammern nicht – zusammengehörende Codeblöcke müssen gemeinsam eingerückt werden:
zaehler = 1
solange zahler <= 5 wiederhole:
    gib zaehler auf dem Bildschirm aus
    erhoehe zaehler um 1
gib "fertig" auf dem Bildschirm aus
So weiß der Python-Interpreter, dass nur Zeilen 3 und 4 zur Kontrollstruktur gehören. „fertig“ wird nur einmal auf dem Bildschirm ausgegeben. Wäre auch Zeile 5 eingerückt, würde „fertig“ ebenfalls fünfmal ausgegeben.
Das Einrücken ist eine Besonderheit von Python und einigen wenigen anderen Sprachen, die das Lesen des Quelltextes vereinfachen soll: Der Benutzer wird gezwungen, sinnvoll einzurücken! Wie viel eingerückt wird und ob es mit Leerzeichen oder Tabulatoren geschieht, bleibt dem Benutzer überlassen – es muss nur einheitlich sein. Sobald Leerzeichen und Tabulatoren gemischt werden oder an einer Stelle mit vier Leerzeichen eingerückt wird, an anderer aber mit drei, kommt es zu Problemen und das Programm wird sehr wahrscheinlich nicht richtig ausgeführt werden. Allgemein wird das Einrücken mit vier Leerzeichen empfohlen. Fast jeder gängige Texteditor ist heute in der Lage, statt Tabulatoren Leerzeichen einzufügen, so dass dem Benutzer durch die Verwendung von Leerzeichen keinerlei Nachteile entstehen.

for-Schleife

Zuletzt soll hier nun die for-Schleife besprochen werden. Mit dieser Schleife kann man beliebige Anweisungen beliebig oft ausführen lassen. Mit folgendem einfachen Beispiel werden zehn Zahlen nacheinander ausgegeben:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

for i in range(0, 10):
    print(i)
print("Fertig")
Listing: for_schleife.py
Die Ausgabe des obigen Skriptes sieht wie folgt aus:
0
1
2
3
4
5
6
7
8
9
Fertig
Was passiert in dem obigen Beispiel genau? Die Funktion range() liefert (vereinfacht gesagt, es gibt hier Unterschiede in den verschiedenen Python-Versionen) eine Liste von 0 bis 9 zurück. Die Anweisung for i in range(0, 10) lässt sich umgangssprachlich übersetzen mit: „Für jedes Element i in der Liste der Zahlen von 0 bis ausschließlich 10 mache …“
Die Liste der Zahlen von 0 bis 9 wird also schrittweise durchlaufen. Zunächst wird i der Wert 0 zugewiesen und dann die Anweisung print(i) ausgeführt. Danach wird i der Wert 1 zugewiesen und erneut der Anweisungsblock ausgeführt etc. Man nennt dieses Vorgehen auch „Iteration“: Hier wird also über die von range() erzeugte Liste „iteriert“.
An diesem Beispiel wird auch deutlich, warum richtiges Einrücken so wichtig ist: Hätte man die Zeile print("Fertig") in Zeile 6 auch eingerückt, wäre diese Anweisung bei jedem Schleifendurchlauf ausgeführt worden – und nicht erst nach dem Durchlaufen der Schleife.
Mit der for-Schleife endet der erste Teil der Einführung in Python. Im nächsten Teil werden dann if- und while-Blöcke sowie Listen besprochen.
Links
[1] http://docs.python.org/
[2] http://www.python.org/dev/peps/pep-0238/
[3] http://www.freiesmagazin.de/freiesMagazin-2009-11
[4] http://www.python.org/doc//current/reference/lexical_analysis.htmli#grammar-token-escapeseq
[5] http://docs.python.org/library/stdtypes.html#string-methods
Autoreninformation
Daniel Nögel (Webseite) beschäftigt sich seit drei Jahren mit Python. Ihn überzeugt besonders die intuitive Syntax und die Vielzahl der unterstützten Bibliotheken, die Python auf dem Linux-Desktop zu einem wahren Multitalent machen.
Diesen Artikel kommentieren

Zum Index

Python-Programmierung: Teil 2 – Tiefere Einblicke

von Daniel Nögel Im vorherigen Teil der Python-Reihe „Python-Programmierung: Teil 1 – Hallo Welt“ wurden erste Erfahrungen mit mathematischen Operatoren, Zeichenketten und for-Schleifen gesammelt. Im zweiten Teil sollen nun besonders Listen in Python betrachtet werden. Mit if und while werden aber auch zwei weitere Kontrollstrukturen vorgestellt.

Korrekturen und Ergänzungen

Im ersten Teil wurde bereits angesprochen, dass manche Spracheigenschaften von Python sich ab Version 3.x geändert haben. Dazu gehören insbesondere die Zeichenketten. Erst ab 3.x arbeitet Python immer mit Unicode-Zeichenketten. Davor muss die Verwendung von Unicode bei der Erstellung von Zeichenketten erzwungen werden. Unterbleibt dies, können schnell schwer zu ermittelnde Probleme auftreten. Auch Zeichenketten aus Dateien und anderen Quellen sollten so früh wie möglich in Unicode umgewandelt werden. Ab Python 3 muss sich der Entwickler darum nicht mehr kümmern.
Eine Unicode-Zeichenkette wird in Python 2.x durch das Voranstellen eines u vor die Zeichenkette oder den Aufruf der Funktion unicode() erstellt [1]  [2]  [3]:
u"Ich bin ein Unicode-String"
unicode("Auch ich werde eine Unicode-Zeichenkette")
"Bei Python-Versionen < 3 bin ich ein normaler Byte-String"
Ein weiterer Unterschied zu Python 3.x, der im letzten Teil verschwiegen wurde, ist die Verwendung von print. Erst ab Python 3 wird print als Funktion verwendet und muss wie folgt aufgerufen werden:
>>> print("Hallo Welt")
Vor Python 3 wurde print als Statement implementiert:
>>> print "Hallo Welt"
Hinweis: Wie schon im ersten Teil vereinbart, werden Zeilen, die mit >>> beginnen, direkt in die interaktive Konsole von Python eingegeben – dann natürlich ohne die spitzen Klammern.
Die genauen Unterschiede sollen hier weiter keine Rolle spielen. Wichtig ist nur: Nutzer von Python 3.x verwenden print als Funktion (mit Klammern), Nutzer von Python 2.x verwenden print ohne Klammern.
Da heute noch zumeist Python 2.x verwendet wird und viele Bibliotheken für Python 3.x noch nicht angepasst wurden, werden ab diesem zweiten Teil die hier genannten Ergänzungen berücksichtigt. Allen Zeichenketten wird von nun an also ein u vorangestellt, um Unicode-Zeichenketten zu erzeugen. Benutzereingaben werden im Folgenden mit der Funktion unicode() ebenfalls in Unicode umgewandelt. Nutzer von Python 3.x müssen das vorangestellte u und die Funktion unicode() jeweils auslassen – in 3.x wird ja ohnehin immer mit Unicode gearbeitet.

Operatoren

Bevor nun in den nächsten Abschnitten if- und while-Blöcke behandelt werden, sollen zuerst einige Operatoren besprochen werden. Operatoren sind – vereinfacht gesagt – (mathematische) Vorschriften, durch die aus zwei Objekten ein neues Objekt gebildet wird.
Eine Auswahl von Operatoren in Python
Operator Typ Funktion
+, -, *, / Mathematisch Addition, Subtraktion, Multiplikation, Division
** Mathematisch Potenzierung
<, >, <=, >= Vergleich kleiner als, größer als, kleiner als oder gleich, größer als oder gleich
== Vergleich gleich
!= Vergleich ungleich
= Zuweisung weist einen Wert zu
in Listen-Operator/ Mitgliedschaftstest testet, ob der rechte Operand Mitglied im linken Operanden ist
and Bool. Operator Konjunktion, logisches Und
or Bool. Operator Disjunktion, logisches Oder
not Bool. Operator Negation, logische Verneinung
Die üblichen mathematischen Operatoren sind sicher ebenso bekannt wie die Vergleichsoperatoren. Für Verwunderung sorgt vielleicht der Divisionsoperator: / liefert bis Python 3 ganzzahlige Ergebnisse, wenn nicht explizit Fließkommazahlen dividiert werden. Erst ab Python 3 gibt dieser Operator Fließkommazahlen zurück, wenn das Ergebnis keine natürliche Zahl ist.
Auch auf den Unterschied des Vergleichsoperators == und des Zuweisungsoperators = soll hingewiesen werden: x == 3 liefert abhängig von x entweder True oder False zurück. x = 3 dahingegen weist x den Wert 3 zu. Gerade bei Anfängern ist das eine beliebte Fehlerquelle.
Der in-Operator kommt bei allen iterierbaren Objekten (also besonders Listen) zur Geltung: Mit ihm lässt sich in Erfahrung bringen, ob ein bestimmter Eintrag in einer Liste vorhanden ist.
Die Booleschen Operatoren [4] and und or dienen zur Verknüpfung mehrere Wahrheitswerte. Der Ausdruck 3 < 5 and 3 < 2 ist offensichtlich falsch, der Ausdruck 3 < 5 or 3 < 2 dahingegen wahr. Der Operator not dreht einen Wahrheitswert schlicht um: Der Ausdruck 3 < 5 and not 3 < 2 ist also ebenfalls wahr.
Eine vollständige Übersicht der Operatoren in Python findet sich unter anderem im kostenlos verfügbaren Buch „A Byte of Python“ [5].

if-Anweisung

if-Blöcke bieten die Möglichkeit, das Ausführen eines bestimmten Code-Teiles von einer oder mehreren Bedingungen abhängig zu machen.
In dem Kopf des if-Blockes wird die Bedingung für die Ausführung definiert, also beispielsweise:
number = 5
if number > 3:
    print u"Zahl groesser als 3"
Bei der Definition derartiger Bedingungen sind besonders vergleichende Operatoren wichtig. Im Kopf eines if-Blockes können – durch boolesche Operatoren verknüpft – eine ganze Reihe derartiger Vergleiche aneinandergereiht werden:
number = 20
if number > 10 and number < 40:
    print u"Zahl liegt zwischen 10 und 40"
Durch den Operator and müssen beide Vergleiche wahr sein, damit der if-Rumpf ausgeführt und die Meldung ausgegeben wird. Verwendet man dahingegen den Operator or, muss nur eine der Bedingungen wahr sein:
good_looking = False
rich = True
if good_looking == True or rich == True:
    print u"Heirate mich!"
Hier wird die Meldung „Heirate mich!“ ausgegeben, wenn die Variable good_looking oder die Variable rich True ist (oder beide). In Zeile 3 werden die Variablen dazu mit True verglichen. Dieser Vergleich mit True ist eigentlich immer unnötig. Üblich und schöner zu lesen ist folgende Schreibweise:
if good_looking or rich:
    print u"Heirate mich!"
Am Ende dieses Abschnitt soll noch kurz auf die Möglichkeit eingegangen werden, mehrere Eventualitäten mit if abzudecken:
if number < 10:
    print u"Kleiner 10"
elif number < 20:
    print u"Kleiner 20"
else:
    print u"Groesser oder gleich 20"
Das Schlüsselwort elif steht für else if und gelangt immer dann zur Ausführung, wenn die vorherige if-Bedingung nicht erfüllt war. Mit elif können – ebenso wie mit if – eine Vielzahl von Bedingungen definiert werden.
Wäre number beispielsweise 3, wäre die Bedingung in Zeile 1 wahr und Zeile 2 käme zur Ausführung. Wäre number aber 11, wäre die Bedingung in Zeile 1 nicht erfüllt und der Interpreter würde die Bedingung in Zeile 3 prüfen. Da diese in diesem Fall wahr wäre, käme Zeile 4 zur Ausführung. Wäre number aber nun 40 und entsprechend keine der beiden Bedingungen wahr, käme Zeile 6 zur Ausführung: Das Schlüsselwort else ist also immer dann (und nur dann) von Bedeutung, wenn keine der vorherigen if oder elif-Bedingungen erfüllt wurde.

while-Schleife

Eine weitere wichtige Kontrollstruktur in Python ist die while-Schleife. So lange die im Schleifenkopf definierten Bedingungen wahr sind, wird der Schleifenrumpf ausgeführt. Ein sehr einfaches Beispiel ist folgende Endlosschleife:
while True:
    raw_input(u"Wie war Ihr Name noch gleich?")
Da die Bedingung True immer wahr ist, wird die Schleife nie enden. Durch die Tastenkombination „Strg“ + „C“ kann die Ausführung des Programms aber beendet werden.
Sinnvoller ist eine derartige Schleife natürlich, wenn eine Abbruchbedingung definiert wird. Denkbar wäre hier beispielsweise das Sammeln von Namen, bis der Benutzer das Programm durch die Eingabe von exit beendet.
names = []
running = True
while running:
    user_input = unicode(raw_input(u"Geben Sie einen Namen ein oder 'exit' zum Beenden > "))
    if user_input == u"exit":
        running = False
    else:
        names.append(user_input)
print u"Sie haben folgende Namen eingegeben:"
print names

Vier verschiedene Namen werden eingegeben.
Wichtig ist hier die Funktion unicode(): Sie wandelt in Python 2.x die Eingabe des Benutzers in Unicode um. Da in Python 3.x von Haus aus mit Unicode-Zeichenketten gearbeitet wird, gibt es diese Funktion dort nicht mehr.
Hinweis: Nutzer von Python 3 verwenden statt raw_input lediglich input.

Zwischenfazit: Kontrollstrukturen

Bisher wurde folgende Kontrollstrukturen behandelt: if, for und while. Für diese Strukturen gilt:
Kontrollstrukturen können natürlich auch verschachtelt werden. Folgendes Beispiel veranschaulicht dies:
if username == u"Bernd":
    if password == u"xy":
        print u"Alles ok"
    else:
        print u"Password falsch"
else:
    print u"Benutzername falsch"
Der innere if-Block muss also insgesamt eine Ebene eingerückt werden – er gehört ja zum Rumpf des äußeren if-Blockes. Der Rumpf des inneren if-Blockes muss um zwei Ebenen eingerückt werden.
Jede Verschachtelungsebene muss also durch Einrückung von der vorherigen Ebene getrennt werden.
Weitere Informationen über Kontrollstrukturen finden sich in der Python-Dokumentation [6].

Listen

In Teil 1 dieser Einführung wurde mit der Funktion range() eine Liste von 0 bis 9 generiert. Hier soll nun abschließend näher auf Listen eingegangen werden. Bei Listen handelt es sich um einen Datentyp, der beliebige andere Datentypen verwalten kann (sogar gemischt) – gewissermaßen also ein Aktenschrank für Zeichenketten, Zahlen und alle möglichen anderen Objekte, die in Python vorkommen (sogar Listen lassen sich in Listen ablegen, so dass verschachtelte Listen möglich sind) [7].
Listen werden in Python mit eckigen Klammern ([ und ]) gekennzeichnet. Sie sind sehr leicht zu erstellen:
>>> persons = []
>>> type(persons)
<type 'list'>
>>> persons = list()
>>> type(persons)
<type 'list'>
>>>persons = [u"Peter", u"Hermann", u"Simon"]
In Zeile 1 wird eine leere Liste erstellt und an den Namen persons gebunden. In Zeile 2 wird mit der Funktion type() der Typ des Objektes, welches an persons gebunden ist, ausgegeben. Wie erwartet, handelt es sich dabei um eine Liste. Zeile 4 zeigt die Erzeugung mittels der Funktion list(). Das Ergebnis ist das gleiche. In Zeile 7 sieht man, dass man in Python eine Liste direkt befüllen kann. Es werden die drei Unicode-Zeichenketten Peter, Hermann und Simon in die Liste eingetragen.
Wie schon in Teil 1 gezeigt wurde, lässt sich sehr einfach über Listen iterieren. Listen haben aber auch zusätzliche Methoden, die sehr nützlich sein können.
>>> persons = [u"Peter", u"Hermann", u"Simon"]
>>> persons.append(u"Carla")
>>> persons.append(u"Hermann")
>>> persons
[u'Peter', u'Hermann', u'Simon', u'Carla', u'Hermann']
>>> persons.remove(u"Hermann")
>>> persons
[u'Peter', u'Simon', u'Carla', u'Hermann']
Mit der Methode append() kann ein weiterer Eintrag angehängt werden, wie man in den Zeilen 2 und 3 sehen kann. Zeile 5 zeigt das Ergebnis: Die beiden Unicode-Objekte Carla und Herman wurden in der Reihenfolge der append-Aufrufe an die Liste angefügt.
Analog dazu lassen sich Einträge mit der Methode remove() entfernen (Zeile 6). Hierbei sollte beachtet werden, dass jeweils nur das erste Vorkommen von Hermann entfernt wird. Gibt es mehrere gleichlautende Einträge, muss remove() auch mehrfach ausgeführt werden, etwa wie folgt:
>>> persons = [u"Peter", u"Hermann", u"Hermann"]
>>> while u"Hermann" in persons:
>>>     persons.remove(u"Hermann")
>>> print persons
['Peter']
Wichtig hierbei: Da die Zeichenketten Hermann in der Liste Unicode-Objekte sind, sollte auch als Such-Zeichenkette ein Unicode-Objekt angegeben werden, um Fehler zu vermeiden. Wird versucht, einen Eintrag zu entfernen, der gar nicht in der Liste vorhanden ist (etwa Heidi), kommt es zu einer Fehlermeldung – hier als Beispiel in der interaktiven Python-Konsole:
>>> persons = [u"Peter", u"Hermann", u"Simon"]
>>> persons.append(u"Carla")
>>> persons.remove(u"Hermann")
>>> print persons
[u'Peter', u'Simon', u'Carla']
>>> persons.remove(u"Heidi")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
Nach den Veränderungen der Liste in den Zeilen 1 bis 3 ist in Zeile 5 noch alles in Ordnung: Hermann wurde aus der Liste gelöscht, Carla hinzugefügt. Der Versuch, Heidi zu entfernen scheitert: Dieser Eintrag ist in der Liste gar nicht vorhanden. Die Zeilen 7-9 zeigen die Reaktion des Python-Interpreters darauf. In einem späteren Teil dieser Reihe werden Python-Fehler (meist Exceptions genannt) näher behandelt. Hier soll zunächst gezeigt werden, wie vor dem Löschen eines Eintrages überprüft werden kann, ob er sich überhaupt in der Liste befindet:
if u"Heidi" in persons:
    persons.remove(u"Heidi")
Nun wird mit dem Operator in geprüft, ob der Eintrag Heidi überhaupt in der Liste existiert. Sehr schön zu sehen ist dabei, wie intuitiv und natürlich Python sein kann.

Listen-Indizes

Beim Umgang mit Listen sollte man wissen, dass Python die Listeneinträge mit einem sogenannten „Index“ verwaltet. Jedem Listeneintrag wird mit 0 beginnend eine eindeutige Zahl zugewiesen. Der erste Eintrag wird also mit 0 angesprochen, der zweite Eintrag mit 1 usw. So ist es sehr leicht, auf einzelne Einträge zuzugreifen:
>>> letters = [u"a", u"b", u"c"]
>>> letters[1]
u"b"
Damit wird der zweite Listeneintrag ausgelesen – der erste Listeneintrag hat ja den Index 0. Soll von hinten gezählt werden, wird einfach ein negativer Index angegeben:
>>> letters[-3]
u"a"

Weitere Listen-Methoden

Die gerade besprochenen Indizes spielen auch bei bestimmten Methoden von Listen eine Rolle: So gibt es mit insert() und pop() die Möglichkeit, Einträge an einer bestimmten Stelle der Liste einzufügen oder zu entfernen:
>>> letters = [u"a", u"c", u"e"]
>>> letters.insert(1, u"b")
>>> letters
[u"a", u"b", u"c", u"e"]
>>> letters.insert(3, u"d")
>>> letters
[u"a", u"b", u"c", u"d", u"e"]
>>> letters.pop()
u"e"
>>> letters
[u"a", u"b", u"c", u"d"]
>>> letters.pop(2)
u"c"
>>> letters
[u"a", u"b", u"d"]
In Zeile 2 wird mit der insert()-Methode b an die richtige Stelle der Liste befördert, in Zeile 5 wird mit d analog verfahren. Zu beachten ist hier, dass der erste Parameter der insert()-Methode immer die gewünschte Position im Index der Liste angibt (daher muss erneut von 0 gezählt werden), der zweite Parameter beinhaltet das einzufügende Objekt. pop() löscht das letzte Element aus einer Liste und gibt dieses zurück. Alternativ kann auch ein bestimmter Eintrag aus einer Liste gelöscht werden – dazu wird der entsprechende Index als Parameter angegeben.

Slicing

Sehr wichtig für Listen ist auch das Slicing – also das „Zerschneiden“. Mit dem slicing-Operator können einzelne Elemente oder Ausschnitte von Listen ausgelesen werden. Der Operator sieht dabei wie folgt aus:
[von:bis]
von steht dabei für den Eintrag der Liste, bei dem das Zerschneiden beginnen soll – es wird von 0 gezählt. bis steht für den Listeneintrag, vor dem das Zerschneiden endet:
>>> li = [u"a", u"b", u"c", u"d", u"e"]
>>> li[0:3]
[u"a", u"b", u"c"]
>>> li[2:5]
[u"c", u"d", u"e"]
Es ist auch möglich, das Ende des Schnittes vom Ende der Liste aus zu definieren – indem ein negatives Vorzeichen gewählt wird:
>>> li[0:-1]
[u"a", u"b", u"c", u"d"]
>>> li[0:-2]
[u"a", u"b", u"c" ]
>>> li[1:-2]
[u"b", u"c"]

Abkürzen erlaubt

Soll der erste Schnitt gleich am Anfang der Liste gesetzt werden, muss nicht nicht immer 0 als Startpunkt gesetzt werden:
>>> li = [u"a", u"b", u"c", u"d", u"e"]
>>> li[:3]
gibt wie gewünscht [u"a", u"b", u"c"] zurück. Auch beim zweiten Schnitt kann abgekürzt werden: Soll dieser hinter dem letzten Listenelement erfolgen, wird ebenfalls keine Angabe gemacht:
>>> li[2:]
[u"c", u"d", u"e"]
Es ist nicht schwer zu erraten, was der Ausdruck
>>> li[:]
folglich bewirken muss.

Listen durch Slices verändern

Bisher wurde nur lesend auf verschiedene Listen-Indizes zugegriffen: Die Ursprungsliste wurde dabei jedoch nie verändert. Mit dem Zuweisungsoperator lassen sich aber auch einzelne Indizes überschreiben oder ganze Bereiche einfügen:
>>> li = [u"a", u"b", u"c"]
>>> li[2] = u"e"
>>> li
[u'a', u'b', u'e']
>>> li[2:2] = [u"c", u"d"]
>>> li
[u'a', u'b', u'c', u'd', u'e']
>>> li[3:] = [1, 2, 3]
>>> li
[u'a', u'b', u'c', 1, 2, 3]
Hier wird zunächst eine Liste mit den Buchstaben a bis c erstellt. Der dritte Eintrag der Liste, wird in Zeile 2 durch e ersetzt. In Zeile 4 ist zu sehen, dass die Liste li dadurch verändert wurde. In Zeile 5 werden zwischen b und e zwei weitere Listenelemente eingefügt: Durch den Slice [2:2] wird der Schnitt direkt vor dem dritten Listenelement gesetzt (Index 2), so dass die Buchstabenreihenfolge wieder stimmt. In Zeile 8 ist schließlich zu sehen, wie ein ganzer Slice der Liste überschrieben wird.
Es ist sehr zu empfehlen, das Slicing und die anderen hier vorgestellten Methoden und Funktionen in der interaktiven Python-Konsole ein wenig zu erproben. Auch die Python-Dokumentation kann wertvolle Hinweise zum Umgang mit Listen liefern [8].

Ein kleines Beispiel

Das folgende Beispiel setzt einige der hier erlernten Techniken ein.
#!/usr/bin/env python
# coding: utf-8

allowed_tries = 5
counter = 1

users = [u"Karl", u"Willi", u"Joe"]
passwords = [u"karl123", u"willi456", u"joe789"]

while counter <= allowed_tries:
    username = unicode(raw_input(u"Bitte geben sie ihren Benutzernamen ein: "))
    password = unicode(raw_input(u"Bitte geben sie ihr Passwort ein: "))

    if not username in users:
        print u"Dieser Benutzer existiert nicht!"
    else:
        idx = users.index(username)
        if passwords[idx] == password:
            print u"Erfolgreich eingeloggt!"
            break
        else:
            print u"Sie haben ein falches Passwort eingegeben"

    counter += 1

    if counter > allowed_tries:
        print u"Sie haben es zu oft versucht!"
Listing: beispiel.py
Hinweis: Benutzer von Python 3.0 verwenden anstatt raw_input() schlicht input().
In den Zeilen 7 und 8 werden zwei Listen definiert: users beinhaltet die verschiedenen Benutzer, passwords deren Passworte. Dabei gehören immer die Listeneinträge mit dem selben Index-Wert zusammen (also Karl und karl123 etc.).
Die Schleife in diesem Beispiel wird höchstens fünfmal ausgeführt – nach fünf Durchläufen hat counter den Wert 6, so dass die Bedingung der while-Schleife nicht mehr wahr ist.
In Zeile 14 wird geprüft, ob der Benutzername nicht in der Liste vorkommt – in diesem Fall wird die Fehlermeldung in Zeile 15 ausgegeben und der Zähler in Zeile 24 um 1 erhöht. Anderenfalls (ab Zeile 16) wird zunächst mit der Methode index() die Position des Benutzernamens in der Liste users ermittelt. In Zeile 18 wird das dazugehörige Passwort mit dem vom Benutzer eingegebenen Passwort verglichen. Stimmen beide überein, wird in Zeile 19 eine Meldung ausgegeben und die Schleife in Zeile 20 mit dem neuen Schlüsselwort break abgebrochen.
Im nächsten Teil dieser Reihe wird auf eine besondere Art der Ersetzung in Zeichenketten („String Substitution“) sowie Module und Funktionen eingegangen.
Links
[1] http://docs.python.org/howto/unicode.html
[2] http://wiki.python-forum.de/Von%20Umlauten,%20Unicode%20und%20Encodings
[3] http://wiki.python.de/User%20Group%20M%C3%BCnchen?action=AttachFile&do=view&target=unicode-folien.pdf
[4] http://de.wikipedia.org/wiki/Boolesche\_Algebra
[5] http://abop-german.berlios.de/read/operators.html
[6] http://docs.python.org/py3k/reference/compound\_stmts.html
[7] http://docs.python.org/faq/design.html#how-are-lists-implemented
[8] http://docs.python.org/tutorial/datastructures.html#more-on-lists
Autoreninformation
Daniel Nögel (Webseite) beschäftigt sich seit drei Jahren mit Python. Ihn überzeugt besonders die intuitive Syntax und die Vielzahl der unterstützten Bibliotheken, die Python auf dem Linux-Desktop zu einem wahren Multitalent machen.
Diesen Artikel kommentieren

Zum Index

Python-Programmierung: Teil 3 – Funktionen und Module

von Daniel Nögel Im vorherigen Teil „Python-Programmierung: Teil 2“ wurden Listen, Zeichenketten und die beiden Kontrollstrukturen if und while behandelt. Dieses Mal werden mit Funktionen und Modulen zwei wichtige Möglichkeiten vorgestellt, um eigene Python-Projekte zu strukturieren und wiederverwendbar zu machen. Zunächst sollen aber noch die versprochenen Ersetzungen bei Zeichenketten besprochen werden. Mit den „Dictionaries“ wird zudem ein weiterer wichtiger Datentyp in Python vorgestellt.

Substitution von Zeichenketten

In den letzten beiden Teilen dieser Reihe wurden schon mehrfach einfache Zeichenketten erstellt. In der Regel möchte man aber nicht nur bloße Zeichenketten ausgeben, sondern bestimmte dynamische Informationen darin transportieren, etwa den Namen des Benutzers. Dies funktioniert in Python so, dass man zunächst Platzhalter in der Zeichenkette definiert und diese später mit der format()-Methode gegen den gewünschten Inhalt austauscht (substituiert).
>>> message = u"Hallo {0}, du hast {1} Euro im Portemonnaie.".format(u"Karl", 10)
>>> print message
Hallo Karl, du hast 10 Euro im Portemonnaie.
Die Methode format() ersetzt also die Zeichenfolge {0} innerhalb der Zeichenkette durch den ersten Parameter, die Zeichenfolge {1} durch den zweiten Parameter usw. Python kümmert sich dabei automatisch um das Umwandeln der Datentypen – so können sehr leicht auch Zahlen in Zeichenketten eingefügt werden, ohne dass sich der Benutzer um irgendwelche Umwandlungen zu kümmern hätte. Folgendes Beispiel dient zur Veranschaulichung:
>>> names = [u"Karl", u"Bernd", u"Hannes", u"Ina" ]
>>> for name in names:
...    print u"'{0}' hat {1} Buchstaben".format(name, len(name))
Hinweis: Wie in den Artikeln zuvor steht >>> für eine Eingabe in der Python-Shell und muss nicht mit eingegeben werden. Mit drei Punkten ... zeigt die Shell an, dass ein Befehl noch nicht abgeschlossen ist und sich über mehrere Zeilen erstreckt. Diese Punkte müssen ebenfalls nicht mit eingeben werden.
Die Ausgabe des obigen Beispiels lautet:
'Karl' hat 4 Buchstaben
'Bernd' hat 5 Buchstaben
'Hannes' hat 6 Buchstaben
'Ina' hat 3 Buchstaben
Bisher wurden nur positionale Argumente verwendet, das heißt: {0} verweist jeweils auf den ersten Parameter von format(), {1} auf den zweiten etc. Um die Übersicht zu wahren, ist auch die Angabe von Namen möglich. format() erlaubt dabei eine sehr weitreichende Formatierung:
>>> for name in names:
...    print u"'{username}' \
...    hat {namelength} \
...    Buchstaben".format(\
...    username=name, \
...    namelength=len(name))
So lässt sich etwa festlegen, wie viele Nachkommastellen ausgegeben werden sollen, ob und wie viele Leerzeichen der Zeichenkette vorangestellt werden sollen und vieles mehr [1].
Achtung: In Zeichenketten, die mit format() formatiert werden, werden alle geschweiften Klammern als Ersetzungszeichen interpretiert. Folgende Zeile wird also zu einem Fehler führen:
>>> print u"{0} mag Klammern wie \
... { oder }".format(u"Bernd")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: u' oder '
Hier müssen die letzten beiden Klammern maskiert werden:
>>> print u"{0} mag Klammern wie {{ oder }}".format(u"Bernd")
Bernd mag Klammern wie { oder }
Doppelte geschweifte Klammern werden also von der format()-Methode ignoriert.

Dictionaries

Sogenannte „Dictionaries“ oder „Dicts“ werden in anderen Sprachen oft „Hashes“ oder „assoziative Arrays“ genannt. Wie auch Listen können Dictionaries beliebige andere Datentypen verwalten. Während Listen aber ihre Einträge intern mit fortlaufenden Nummern adressieren (die sogenannten Indizes), können die Einträge in Dictionaries mit Zeichenketten, beliebigen Zahlen oder anderen Datentypen adressiert werden. Somit besteht jedes Dictionary aus zwei wesentlichen Elementen: Schlüsseln (keys) und Werten (values).
Ein leeres Dict wird in Python entweder mit der Funktion dict() oder zwei geschweiften Klammern erstellt [2]:
>>> persons = dict()
und
>>> persons = {}
sind also äquivalent.
In folgendem Beispiel sollen nun verschiedene Personen und ihr jeweiliges Alter in einer Datenstruktur gespeichert werden. Dies könnte wie folgt aussehen:
>>> persons = {u"Peter":18, \
... u"Ilse":87, u"Juergen":33, \
... u"Jutta":25}
Wie also auch Listen lassen sich Dicts initial befüllen. Die Namen sind in diesem Beispiel jeweils die Schlüssel, das Alter der dazugehörige Wert. Schlüssel und Wert werden durch einen Doppelpunkt getrennt, mehrere Schlüssel/Wert-Paare durch Kommata.
Um das Alter von Peter aus dem Dict auszulesen, genügt folgender Aufruf:
>>> print persons[u"Peter"]
18
Es fällt auf: Obwohl Dicts mit geschweiften Klammern erstellt werden, wird – wie auch bei Listen – mit eckigen Klammern auf die Werte zugegriffen. Auch sonst gibt es einige Parallelen zwischen Dictionaries und Listen. Um beispielsweise zu überprüfen, ob der Eintrag Hans in einem Dict vorhanden ist, wird ebenfalls der Operator in genutzt:
>>> if u"Hans" in persons:
...     print persons[u"Hans"]
... else:
...     print u"Der Eintrag Hans \
...     ist nicht vorhanden"
Während der in-Operator aber bei Listen das Vorhandensein des Wertes Hans abfragt, bezieht sich der Operator bei Dicts auf den Schlüssel Hans. Ebenso wie bei Listen führt der Zugriff auf ein nicht vorhandenes Element/Schlüssel zu einem Fehler. Wie man derartige Fehler sehr leicht abfängt, wird in einem der folgenden Teile besprochen werden.
In manchen Situationen ist es aber vielleicht gar nicht so wichtig, ob ein bestimmter Eintrag nun in einem Dict vorhanden ist oder nicht. Für solche Fälle gibt es die get()-Methode von Dicts:
>>> print persons.get(u"Hans", 15)
15
>>> print persons.get(u"Peter", 5)
18
Die Methode get() erwartet als ersten Parameter einen beliebigen Schlüssel. Ist der Schlüssel im Dict vorhanden, wird der dazugehörige Wert zurückgegeben. Andernfalls wird der zweite Parameter (in Zeile 1 also 15) zurückgegeben. So lassen sich beispielsweise Standardwerte für nicht vorhandene Schlüssel implementieren.
Gut zu sehen ist, dass der Aufruf in Zeile 3 nicht 5, sondern 18 zurück gibt, denn dieser Wert wurde oben dem Schlüssel Peter zugewiesen.
Der zweite Parameter der Methode get() ist optional: Er muss nicht angegeben werden. Wird kein zweiter Parameter angegeben, gibt die Methode None zurück, wenn der gesuchte Schlüssel im Dict nicht vorhanden ist:
>>> print persons.get(u"Anke")
None
Natürlich können auch jederzeit weitere Einträge zu Dicts hinzugefügt oder bestehende Einträge verändert werden:
>>> persons[u"Peter"] = 99
>>> print persons[u"Peter"]
99
>>> persons[u"Hans"] = 15
>>> print persons[u"Hans"]
15
In Zeile 1 wird das Alter von Peter auf 99 verändert. In Zeile 4 wird der Eintrag Hans hinzugefügt.
Dictionaries lassen sich – ebenso wie Listen – auch sehr leicht verschachteln. In folgendem Beispiel wird ein verschachteltes Dict genutzt, um ein kleines Adressbuch zu implementieren:
>>> addresses = {
...     u"Peter":{u"street":"Musterstr. 16", u"mobile":"0151/123 456"},
...     u"Jutta":{u"street":"Beispielstr. 99", u"mobile":"0151/33 44 55"},
... }
Hier wird – zur besseren Lesbarkeit über mehrere Zeilen verteilt – ein verschachteltes Dict erstellt. In Zeile 1 wird dem Namen addresses ein Dict zugewiesen. Dieses wird dabei direkt initial befüllt. Den Schlüsseln Peter und Jutta wird dabei nicht wie oben ihr Alter als Wert zugeteilt, sondern es dienen Dicts als Werte.
Der Aufruf
>>> print addresses[u"Peter"]
gibt nun das dazugehörige Dict zurück:
{u'mobile': '0151/123 456', u'street': 'Musterstr. 16'}
Auf dieses Dict kann auch direkt zugegriffen werden:
>>> print addresses[u"Peter"][u"street"]
Musterstr. 16
Dieser Aufruf irritiert vielleicht zunächst. Etwas verständlicher (aber länger) ist folgende Vorgehensweise:
>>> peters_data = addresses[u"Peter"]
>>> type(peters_data)
<type 'dict'>
>>> peters_data[u"street"]
'Musterstr. 16'
In Zeile 1 wird der zum Schlüssel Peter gehörige Wert der Variable peters_data zugewiesen. Dieser Wert ist wiederum ein Dict mit den zu Peter gehörigen Adressdaten (siehe obiges Beispiel). Zeile 2 und 3 zeigen, dass die Variable peters_data auf ein Dict zeigt. In Zeile 4 und 5 wird nun wiederum der Schlüssel street dieses zweiten Dicts ausgegeben.
Aus dem Ausdruck
>>> print addresses[u"Peter"][u"street"]
wird also zunächst
{u"street":u"Musterstr. 16", u"mobile":"0151/123 456"}[u"street"]
was schließlich auf den Wert Musterstr. 16 verweist.
Insgesamt eignen sich Dicts hervorragend, um Datenstrukturen wie Wörterbücher oder Adressbücher abzubilden. Überall dort, wo Informationen über bestimmte Schlüssel zugänglich sein sollen, empfiehlt sich die Verwendung von Dictionaries.
Ebenso wie über Listen kann sehr leicht über Dicts iteriert werden. Dabei ist aber zu beachten, dass Dicts keine feste Reihenfolge kennen: Es gibt also keine Garantie dafür, dass die Schlüssel eines Dicts beim Iterieren in der Reihenfolge durchlaufen werden, in der sie erstellt wurden [3].

Funktionen

Funktionen sind ein wichtiges Strukturierungsmerkmal moderner Programmiersprachen. Durch sie können häufig benötigte Arbeitsschritte leicht wiederverwertet werden. Damit dienen sie auch der Lesbarkeit und Wartbarkeit des Quelltextes.
Eine Funktion wird in Python wie folgt deklariert:
def say_hallo():
    print u"Hallo"
Diese Funktion kann nun einfach mit say_hallo() aufgerufen werden und wird bei jedem Aufruf die Meldung Hallo auf dem Bildschirm ausgeben. Das Schlüsselwort def leitet hierbei die Deklaration einer Funktion ein, danach folgt der Name der Funktion, der nach dem Paar runden Klammern mit einem Doppelpunkt abgeschlossen wird. Zu beachten ist, dass der Rumpf von Funktionen einzurücken ist – wie bei allen Kontrollstrukturen in Python.
Natürlich handelt es sich bei dem obigen Beispiel noch um eine sehr einfache Funktion. Folgende Funktion greift ein Beispiel aus Teil 1 dieser Reihe wieder auf und wird beliebige Zeichenketten mit einer Box aus Leerzeichen umgeben:
def boxify(text):
    text_with_borders = u"= {0} =".format(text)
    line = len(text_with_borders) * u"="

    output = u"{0}\n{1}\n{2}".format(line, text_with_borders, line)
    return output
In Zeile 1 wird – wie gehabt – eine Funktion definiert. Sie hat den namen boxify und erwartet genau einen Parameter (hier: text). Es können prinzipiell natürlich beliebig viele Parameter im Funktionskopf definiert werden – getrennt werden sie durch Kommata.
In Zeile 2 wird links und rechts der übergebenen Zeichenkette text ein Gleichheits- und Leerzeichen eingefügt, in Zeile 3 wird eine Zeichenkette erstellt, die ausschließlich Gleichheitszeichen enthält.
Zeile 5 wirkt nur auf den ersten Blick kompliziert: Wie bereits in Teil 2 dieser Reihe erörtert wurde, handelt es sich bei der Zeichenfolge \n um eine sogenannte Escape-Sequenz, die einen Zeilenumbruch erzeugt. Somit beinhaltet die Zeichenkette output drei Zeilen: Die erste Zeile enthält ausschließlich Gleichheitszeichen, die zweite Zeile die übergebene Zeichenkette mit Gleichheitszeichen links und rechts, die dritte Zeile schließlich erneut nur Gleichheitszeichen.
Neu ist das Schlüsselwort return – es gibt den nachfolgenden Ausdruck (hier: output) zurück.
Wird diese Funktion nun aufgerufen, geschieht zunächst scheinbar nichts. Die Funktion boxify() macht keine Bildschirmausgaben, sondern gibt nur eine Zeichenkette zurück. Diese muss also noch an die print()-Funktion weitergegeben werden:
>>> print boxify(u"Mein Haus, mein Garten, meine Box")
=====================================
= Mein Haus, mein Garten, meine Box =
=====================================
Eine Besonderheit ist bei return noch zu beachten: Die Anweisung bricht die Funktion sofort ab und liefert einen Rückgabewert – nachfolgende Codezeilen der Funktion werden nicht mehr ausgeführt:
def test():
    print u"Hallo!"
    return
    print u"Dies ist ein Test"

print u"start"
test()
print u"ende"
Dieses Beispiel gibt nur die folgende Meldung aus:
start
Hallo!
ende
Die Zeile 4 (Dies ist ein Test) kommt niemals zur Ausführung. Gut zu sehen ist auch, in welcher Reihenfolge die Anweisungen ausgeführt werden: Zwar wird in den Zeilen 1 bis 4 die Funktion definiert – sie wird aber noch nicht ausgeführt. Es muss also immer zwischen der „Deklaration“ einer Funktion und dem „Aufruf“ derselben unterschieden werden. Zuerst wird hier demnach die Meldung start ausgegeben. Dann wird die Funktion aufgerufen und abgearbeitet – die Meldung Hallo! erscheint. Durch die Anweisung return wird der Programmfluss nun in Zeile 8 fortgesetzt – ende wird ausgegeben.
Zu beachten ist, dass Funktionen keine return-Anweisung haben müssen – haben sie keine, kehrt der Programmfluss nach dem Abarbeiten des Funktionsrumpfes zum Ausgangspunkt zurück. Der Rückgabewert der Funktion ist dann None.
Eine weiteres wichtiges Element von Funktionen sind Standardparameter. Sie werden durch ein Gleichheitszeichen definiert:
def say_something(what, who=u"Karl"):
    print u"{0} sagt: '{1}'".format(who, what)

say_something(u"Hi")
say_something(u"Tach auch", u"Bernd")
Der Parameter what ist obligatorisch – bei jedem Funktionsaufruf muss er angegeben werden, sonst kommt es zu einem Fehler. Der zweite Parameter hingegen ist optional: Wird er nicht angegeben, erhält er automatisch den Wert Karl. Entsprechend gibt obiges Skript folgende Ausgabe aus:
Karl sagt: 'Hi'
Bernd sagt: 'Tach auch'
Wichtig ist: Bei der Funktionsdefinition müssen zuerst immer die obligatorischen Parameter angegeben werden. Die Funktion
def say_something(who="Karl", what):
   ...
führt also zu einem Fehler beim Versuch, das Skript auszuführen.

Parameter an Funktionen übergeben

In den obigen Beispielen wurden bereits verschiedene Parameter an die Funktionen übergeben. Etwa Tach auch und Bernd an die Funktion say_something(). In dem Beispiel muss sich der Programmierer peinlich genau an die im Funktionskopf definierte Reihenfolge halten. Die Parameter sind also abhängig von der Position – sie sind „positional“.
Als Beispiel sei die folgende (nicht besonders schöne) Funktion gegeben, bei der die Argumente alle in einer bestimmten Reihenfolge angegeben werden müssen:
def print_a_lot(name, country, street, adress, mobile, age, sex, hobbies):
    print u"{name} stammt aus {country} ...".format(name=name, country=country)
Statt hier nun die erforderlichen Argumente immer in der einzig richtigen Reihenfolge anzugeben, gibt es noch die Möglichkeit, die Argumente per Schlüsselwort zu übergeben:
print_a_lot(name=u"Bernd", age=18, sex=u"m", street=u"Musterstrasse", adress=18, country=u"Deutschland", mobile=u"0151-123456789", hobbies=u"lesen")
Diese Art des Aufrufes kann die Lesbarkeit von Quelltexten enorm erhöhen.
def print_info(name, country=None, street=None, adress=None, mobile=None, age=None, sex=None, hobbies=None):
    if age:
        print u"Hallo {0}! Du bist {1} Jahre alt!".format(name, age)
    else:
        print u"Hallo {0}".format(name)
Die Funktion print_info() unterscheidet sich von print_a_lot() darin, dass alle Parameter bis auf name optional sind – sie müssen nicht angegeben werden. Wird aber ein Alter übergeben (age), wird zusätzlich zum Namen auch das Alter ausgegeben. Der Aufruf ist:
print_info(u"Jutta", age=25)
Der erste Parameter ist positional: Hier wird der Name übergeben. Da alle anderen Parameter optional sind, werden sie einfach ausgelassen. Lediglich der age-Parameter wird noch als Schlüsselwort-Argument übergeben.
In Python sind auch Funktionen ganz normale Objekte. Auch sie lassen sich damit beispielsweise an Namen binden:
>>> from operator import add
>>> add(1, 3)
4
>>> plus = add
>>> plus
<built-in function add>
>>> plus(1, 3)
4
Achtung: Wichtig ist hier, dass die Funktion in Zeile 5 ohne runde Klammern an einen Namen gebunden wird. Andernfalls würde die Funktion aufgerufen und das Ergebnis an den Namen gebunden werden. Anders gesagt: Mit Klammern wird eine Funktion aufgerufen, ohne Klammern wird das Funktionsobjekt angesprochen, die Funktion aber nicht ausgeführt.
In Zeile 1 wird zunächst die Funktion add() aus dem Modul operator importiert (siehe dazu nächster Abschnitt). Die Funktion add() addiert – wie der Operator + – zwei Zahlen. Sie stellt die gleiche Funktionalität nur als Funktion zur Verfügung (Zeile 3).
In Zeile 5 wird die Funktion add() zunächst an den Namen plus gebunden. In Zeile 6 und 7 wird gezeigt, dass der Name plus noch immer auf die Funktion add() verweist – add und plus sind identisch.
In Zeile 9 wird deutlich, dass man an Namen gebundene Funktionen genau so wie normale Funktionen aufrufen kann. Am Ende dieses Teils wird diese Technik an einem praktischen Beispiel veranschaulicht.

Module

Module bieten eine einfache Möglichkeit, seine Skripte um zusätzliche Funktionen zu erweitern. Es wurde bereits angesprochen, dass Python mit einer Vielzahl zusätzlicher Bibliotheken ausgeliefert wird – diese Bibliotheken heißen in Python Module. Sie werden durch den Befehl import in ein eigenes Skript eingebunden [4]:
import os

counter = 0

files = os.listdir(".")
for entry in files:
    if os.path.isfile(entry):
        counter += 1

print u"Es gibt {0} Dateien in diesem Verzeichnis".format(counter)
In Zeile 1 wird der Interpreter angewiesen, das Modul os im jetzigen Skript verfügbar zu machen. Dieses Modul enthält verschiedene Funktionen zum Kopieren, Verschieben oder Löschen von Dateien. Auch das Auflisten des aktuellen Verzeichnisinhaltes gehört dazu [5].
In Zeile 5 wird die Funktion listdir() des Modules os aufgerufen. Der Parameter . verweist auf das aktuelle Verzeichnis. Die Funktion erstellt eine Liste mit allen Dateien und Ordnern des aktuellen Verzeichnisses und gibt diese Liste zurück. Die Variable files zeigt nun auf diese Liste.
In Zeile 6 wird eine for-Schleife definiert, die über die zuvor erstellte Liste iteriert. In Zeile 7 wird überprüft, ob es sich bei dem jeweiligen Eintrag um eine Datei oder ein Verzeichnis handelt – auch dazu wird eine Funktion aus dem Modul os genutzt. Falls es sich um eine Datei handelt, gibt diese Funktion True zurück, andernfalls False. Nur im ersten Fall ist die if-Bedingung wahr und der Zähler wird erhöht.
Nach dem Durchlauf der Schleife setzt der normale Programmfluss fort – die letzte Zeile des Skriptes wird ausgeführt und zeigt die Zahl der Dateien im aktuellen Verzeichnis an.
Es gibt auch eine Möglichkeit, Funktionen aus Modulen so zu importieren, dass sie im Skript direkt verfügbar sind – mit der Anweisung from MODUL import FUNKTION/OBJEKT. So könnte es im obigen Beispiel etwa heißen:
from os import listdir
Allerdings müsste dann auch Zeile 5 angepasst werden: Da durch den from-Import die listdir()-Funktion direkt im Namensraum [5] des Skriptes verfügbar ist, müsste die Zeile nun wie folgt lauten:
files = listdir(".")
Das sieht auf den ersten Blick sehr bequem aus, führt aber jetzt in Zeile 7 zu Problemen: Da nur die Funktion listdir importiert wurde, ist ein Zugriff auf die Funktion os.path.isfile nicht möglich. Diese Funktion müsste nun zusätzlich importiert werden.
Das Importieren von einzelnen Funktionen hat noch andere Nachteile [6]: So erschwert es die Verständlichkeit des Quelltextes, da Fremde nicht wissen, ob mit listdir hier die Funktion aus dem os-Modul gemeint ist oder vielleicht irgendwo im Quelltext noch eine eigene listdir-Funktion zu finden ist, die etwas ganz anderes macht. In aller Regel sollte daher von from-Importen abgesehen und die zusätzliche Schreibarbeit in Kauf genommen werden.

Was es nicht alles gibt

Das os-Modul erscheint noch recht bodenständig: Bisher wurde damit der Inhalt eines Verzeichnisses aufgelistet und überprüft, ob eine bestimmte Datei ein Ordner oder eine Datei ist. Darüber hinaus gibt es aber Module für grafische Oberflächen [7], für Datenbanken [8], für die Bildbearbeitung [9] oder für mathematische und wissenschaftliche Anforderungen [10]. Es gibt Module, um Spiele zu programmieren [11], Module, um automatisiert Eingaben in Webseiten vorzunehmen [12], Module für Mediendateien [13], für IMAP [14] oder sogar BitTorrent [15]. Nicht alle diese Module gehören dabei zum Standardumfang [16] der Sprache – die fehlenden lassen sich aber leicht über die Paketquellen oder das eigens für Python entwickelte EasyInstall-System installieren.

Module selbst gemacht

Es stellt sich nun natürlich die Frage, wie sich derartige Module selbst erstellen lassen. Die meisten Leser dieser Reihe werden dabei vermutlich schon lange das eine oder andere Python-Modul erstellt haben:
#! /usr/bin/env python
# -*- coding: utf-8 -*-

def boxify(text):
    text_with_borders = u"= {0} =".format(text)
    line = len(text_with_borders) * u"="

    output = u"{0}\n{1}\n{2}".format(line, text_with_borders, line)
    return output
Listing: box.py
Ein Python-Modul ist zunächst nämlich nichts anderes als eine Textdatei mit der Endung .py. Die oben besprochene Funktion boxify() soll im Folgenden in ein eigenes Modul ausgegliedert werden.
Diese obigen Zeilen werden in die Datei box.py gespeichert. Die ersten beiden Zeilen wurden bereits im ersten Teil dieser Reihe besprochen: Es handelt sich um die Shebang-Zeile und um die Angabe der Zeichenkodierung. Auch die Funktion boxify() sollte mittlerweile hinlänglich bekannt sein.
Damit wurde nun das Modul box erstellt. Der Modulname entspricht also immer dem Dateinamen abzüglich der Endung .py.
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import box

name = unicode(raw_input(u"Bitte Namen eingeben: "))
print box.boxify(name)
Listing: myprog.py
Um dieses Modul nun verwenden zu können, wird eine weitere Python-Datei im selben Verzeichnis erstellt: myprog.py (siehe oben).
In Zeile 4 wird das selbst erstellte Box-Modul importiert. In Zeile 6 wird der Benutzer zur Eingabe seines Namens aufgefordert. In Zeile 7 schließlich wird die Funktion boxify() aus dem box-Modul mit dem eingegebenen Namen aufgerufen. Das Resultat wird mit print auf dem Bildschirm ausgegeben.

Einschränkungen und Erweiterungen

Der Python-Interpreter hat eine recht genaue Vorstellung davon, in welchen Verzeichnissen sich ein Modul befinden darf [17]. Befindet sich das Modul nicht in einem dieser Verzeichnisse, kommt es zu einem Fehler. Der Python-Interpreter schaut aber zusätzlich auch immer in das Programmverzeichnis – hier also etwa in das Verzeichnis der Datei myprog.py. So lange die selbst erstellten Module in diesem Verzeichnis zu finden sind, befindet sich der Anfänger also auf der sicheren Seite.
Eine Erweiterung des Modul-Systems stellen die sogenannten „Packages“ dar [18]. Damit lassen sich verschiedene zusammengehörige Module bündeln und strukturieren. In dieser Einführung wird aber auf eine weitergehende Behandlung dieser Thematik verzichtet. Wer aber größere Bibliotheken programmieren möchte oder eine ganze „Werkzeugsammlung“ in Modulen organisieren will, sollte sich Packages einmal näher ansehen [19].
Nachdem nun Listen, Dictionaries, Zeichenketten, Module, Funktionen und verschiedene Kontrollstrukturen in den ersten drei Teilen dieser Einführung besprochen wurden, sollen im nächsten Teil Klassen vorgestellt werden.

Praktisches Beispiel: Der Taschenrechner

In folgendem Beispiel kommen verschiedene bereits erlernte Techniken zum Einsatz.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from operator import add, sub, mul, div

def info(*args):
    print "Moegliche Befehle:"
    print ",".join(dispatch)
    print "Syntax: BEFEHL [PARAM_A PARAM_B]"

dispatch = {
    u"help": info,
    u"add": add,
    u"sub": sub,
    u"mul": mul,
    u"div": div,
    u"addiere": add,
    u"plus": add
}

def parser():
    while True:
        user_command = unicode(raw_input("calc: "))
        tokens = user_command.split()
        command = tokens[0]
        arg_a, arg_b = None, None
        if len(tokens) > 1:
            arg_a = int(tokens[1])
            arg_b = int(tokens[2])
        print tokens
        if command == u"quit":
            return
        elif command in dispatch:
            result = dispatch[command](arg_a, arg_b)
            print ">>> {0}".format(result)
        else:
            print "Unbekanntes Kommando '{0}'".format(command)
            print "Tippe 'help' fuer Hilfe."

if __name__ == "__main__":
    parser()
Listing: calc.py
In Zeile 4 werden die Funktionen add, sub, mul und div aus dem Modul operator importiert. Sie stellen die Funktionalität der Operatoren +, -, * und / als Funktion zur Verfügung (siehe oben).
In Zeile 6 wird die Funktion info() definiert. Das Sternchen (*) vor dem Parameter args führt dazu, dass die Funktion beliebig viele (positionale) Argumente akzeptiert und diese als Liste args bereitstellt. Keine Sorge: Dieses Vorgehen dient in diesem Fall nur dazu, den Taschenrechner unempfindlicher gegen Fehleingaben zu machen. Die Funktion ignoriert alle Parameter und gibt lediglich alle möglichen Befehle durch Kommata getrennt aus (Zeile 8).
In Zeile 11 wird ein Dict definiert und initial befüllt. Den Schlüsseln werden hier Funktionen als Werte zugewiesen. Oder anders: Die Funktionen werden an Schlüssel des Dicts gebunden. Ein Aufruf von dispatch["plus"](4+1) ist im Folgenden also identisch mit einem Aufruf von add(4, 1). In Zeile 13, 17 und 18 ist zu sehen, dass die Funktion add gleich mehreren Dict-Schlüsseln zugewiesen wird. Jeder Dict-Schlüssel ist dabei ein möglicher Befehl. Der Benutzer wird später also mit addiere, add und plus gleichermaßen addieren können.
Das Herzstück des Taschenrechners ist die Funktion parse(), die in Zeile 21 definiert wird. In Zeile 22 wird weiterhin eine Schleife implementiert. Da die Bedingung True immer wahr ist, handelt es sich hierbei erst einmal (scheinbar) um eine Endlosschleife.
In Zeile 23 wird eine Benutzereingabe eingelesen. Diese könnte etwa wie folgt aussehen:
add 3 7
In Zeile 24 wird diese Benutzereingabe durch die Funktion split() aufgeteilt. Da diese Funktion hier ohne Parameter aufgerufen wird, teilt sie die Zeichenkette bei allen Leerräumen (Whitespace-Zeichen). Die Eingabe von add 3 7 würde so zur Liste ["add", "3", "7"] führen, die dem Namen tokens zugewiesen wird.
In Zeile 25 wird das erste Element der Liste (der Befehl) dem Namen command zugewiesen. Weiterhin werden zwei Variablen arg_a und arg_b erstellt. Sie sollen später die Zahlen enthalten, die addiert werden sollen, zeigen aber zunächst nur auf None.
Zeile 27 testet, ob die Liste tokens mehr als nur ein Element enthält. Ist dies der Fall, werden das zweite und dritte Element der Eingabe (also 3 und 7, wenn man vom obigen Beispiel ausgeht) mit der int()-Funktion in Zahl-Objekte umgewandelt und den Namen arg_a bzw. arg_b zugewiesen. In Zeile 30 wird die Liste tokens ausgegeben.
In Zeile 30 ff. findet sich nun eine if-Kontrollstruktur. Nach der Eingabe von quit soll das Programm beendet werden. Dazu wird mit dem Schlüsselwort return die Abarbeitung der Funktion parse direkt unterbrochen. Die in Zeile 22 definierte „Endlosschleife“ verfügt damit also doch über eine Abbruchbedingung.
Das Schlüsselwort elif in Zeile 33 wurde auch bereits besprochen. Es wird getestet, ob die erste Benutzereingabe (add im vorherigen Beispiel) im Dict dispatch (Zeile 11) vorhanden ist. In Zeile 34 geschieht nun die eigentlich „Magie“ des Taschenrechners:
result = dispatch[command](arg_a, arg_b)
Abhängig vom Inhalt der Variable command wird nun der dazugehörige Schlüssel im Dict angesprochen. Da diesen Schlüsseln aber in Zeile 11 ff. Funktionen zugewiesen wurden, wird mit der Anweisung dispatch[command] abhänging von command eine Funktion angesprochen. Die Klammern hinter dieser Anweisung (arg_a, arg_b) enthalten also die Parameter für diese Funktion. Das Ergebnis der jeweiligen Funktion ist nun über die Variable result verfügbar.
Was passiert hier also? Der Taschenrechner liest Benutzereingaben in der Form BEFEHL ZAHL1 ZAHL2 ein. Ist nun BEFEHL ein Schlüssel im Dict dispatch, wird die dazugehörige Funktion aufgerufen. Der Befehl addiere würde somit zum Aufruf der Funktion add führen, der Befehl info zum Aufruf der Funktion info. Die Eingaben ZAHL1 und ZAHL2 werden dabei jeweils als Parameter übergeben. Dies ist auch der Grund, warum die in Zeile 6 definierte Funktion info mit *args beliebig viele Parameter übernimmt. So reagierte sie tolerant auf eventuelle Falschangaben des Benutzers, denn diese werden in jedem Fall an die Funktion übergeben.
In Zeile 35 wird das Ergebnis der aufgerufenen Funktionen formatiert und ausgegeben. Da in der Funktion info kein Rückgabewert festgelegt wurde, gibt sie immer None zurück.
In Zeile 36-38 wird nun schließlich für den Fall vorgesorgt, dass BEFEHL nicht im Dict dispatch vorkommt. Dann hat der Benutzer eine ungültige Eingabe gemacht und wird darauf hingewiesen.
Der if-Block in Zeile 40 f. stellt schließlich sicher, dass die Funktion parser() nur ausgeführt wird, wenn das Python-Skript direkt gestartet wurde. Würde man das Skript als Modul importieren (siehe oben), würde der Taschenrechner nicht automatisch ausgeführt werden.
Natürlich wirkt dieser Taschenrechner auf den ersten Blick sehr kompliziert. Er zeigt aber, warum das Binden von Funktionen an Namen (oder hier: Dict-Schlüssel) sehr sinnvoll sein kann. Ohne diese Technik müsste der Programmierer jede Eingabe von Hand auswerten:
if command == "add" or command == "addiere" or command == "plus":
    result = arg_a + arg_b
elif command == "sub":
    result = arg_a - arg_b
elif command == "div":
    result = arg_a / arg_b
elif command == "mul":
    result = arg_a * arg_b
elif command == "info":
    info()
elif command == "quit":
    return
else:
    print "Unbekanntes Kommando '{0}'".format(command)
    print "Tippe 'help' fuer Hilfe."
    
print ">>> {0}".format(result)
Links
[1] http://docs.python.org/library/string.html#format-string-syntax
[2] http://docs.python.org/tutorial/datastructures.html#dictionaries
[3] http://www.python.org/dev/peps/pep-0372/
[4] http://docs.python.org/tutorial/modules.html
[5] http://docs.python.org/library/os.html
[6] http://effbot.org/zone/import-confusion.htm
[7] http://de.wikipedia.org/wiki/Liste_von_GUI-Bibliotheken#Python
[8] http://initd.org/tracker/pysqlite
[9] http://www.pythonware.com/products/pil/
[10] http://www.scipy.org/
[11] http://www.pygame.org/news.html
[12] http://wwwsearch.sourceforge.net/mechanize/
[13] http://pymedia.org/
[14] http://docs.python.org/library/imaplib.html
[15] http://www.rasterbar.com/products/libtorrent/python_binding.html
[16] http://docs.python.org/modindex.html
[17] http://docs.python.org/using/cmdline.html#envvar-PYTHONPATH
[18] http://docs.python.org/tutorial/modules.html#packages
[19] http://docs.python.org/distutils/index.html
Autoreninformation
Daniel Nögel (Webseite) beschäftigt sich seit drei Jahren mit Python. Ihn überzeugt besonders die intuitive Syntax und die Vielzahl der unterstützten Bibliotheken, die Python auf dem Linux-Desktop zu einem wahren Multitalent machen.
Diesen Artikel kommentieren

Zum Index

Python-Programmierung: Teil 4 – Klassenunterschiede

von Daniel Nögel
Im dritten Teil dieser Einführung „Python-Programmierung: Teil 3“ wurden mit Funktionen und Modulen zwei elementare Konstrukte zur Strukturierung von Python-Skripten vorgestellt. In diesem Teil soll nun mit Klassen ein weiteres wichtiges Konstrukt besprochen werden.

Objektorientierte Programmierung

Bei der objektorientierten Programmierung (OOP [1]) handelt es sich um ein sogenanntes Programmierparadigma – also um ein bestimmtes Entwurfsmuster beim Programmieren. Bei der OOP wird versucht, Daten und Funktionen zu sinnvollen Einheiten zusammenzufassen und an sogenannte Objekte zu binden.
Neben „Objekten” gibt es zumindest noch drei weitere wichtige Begriffe im Zusammenhang mit objektorientierter Programmierung, die im Folgenden immer wieder auftauchen: „Klassen“, „Attribute“ und „Methoden“.
Als Beispiel sollen für eine Rennsimulation unterschiedliche Autos zur Verfügung stehen. Diese Autos unterscheiden sich in Länge, Gewicht, Geschwindigkeit, Beschleunigung, Farbe etc. Wenn nun 20 unterschiedliche Autos gleichzeitig um die Wette fahren, müssen viele Daten verwaltet werden. Es bietet sich an, diese Daten jeweils zusammenzufassen und zu bündeln. So könnte es beispielsweise eine Objekt „Auto“ mit folgenden Eigenschaften geben:
Diese Definition der späteren Auto-Objekte im Quelltext eines Programmes nennt man Klasse. Jedes Objekt wird aus dieser Klasse erstellt – man spricht auch von der Instanz einer Klasse. Klasse und Objekt verhalten sich also stark vereinfacht ausgedrückt so zueinander, wie „Bauplan“ und „Auto auf der Straße“.
class Car(object):
    def __init__(self, speed=0, max_speed=140, acceleration=5, color="grey"):
        self.speed = speed
        self.max_speed = max_speed
        self.acceleration = acceleration
        self.color = color
    
    def brake(self):
        self.speed -= self.acceleration
        print "Current Speed: {0}".format(self.speed)
        
    def accelerate(self):
        self.speed += self.acceleration
        print "Current Speed: {0}".format(self.speed)
        
    def honk(self):
        print "Tuuuuut Tuuuut"
Listing: BeispielKlasseCar.py
Bei Länge, Gewicht und den anderen Angaben handelt es sich offensichtlich um Eigenschaften der jeweiligen Autos. Eigenschaften von Objekten nennt man Attribute. Objekte können aber auch Funktionen besitzen – also etwa bremse_ab() oder gib_gas(). Diese Funktionen von Klassen bzw. Objekten nennt man Methoden.

Klassen in Python

Das Beispiel BeispielKlasseCar.py zeigt, wie man eine (leicht abgewandelte) Auto-Klasse in Python umsetzt.
In Zeile 1 wird die Klasse definiert. Dies wird durch das Schlüsselwort class gekennzeichnet. In Klammern dahinter wird angegeben, von welchen anderen Klassen die neue Klasse erben soll (siehe dazu den nächsten Abschnitt). In älteren Python-Versionen sollte hier immer von object geerbt werden – nur dann steht der volle Funktionsumfang der Klassen in Python zu Verfügung. Wer bereits Python >= 3 verwendet, verzichtet auf diese Angabe und hat dennoch den vollen Funktionsumfang der Klassen. Wie jede Kontrollstruktur in Python wird auch der Kopf der Klasse mit einem Doppelpunkt abgeschlossen.
In Zeile 2 wird die Spezialmethode __init__ definiert. Diese wird aufgerufen, wenn eine Instanz der Klasse (also ein Objekt) erstellt wird. Die Methode __init__ wird hier mit fünf Parametern definiert: self, speed, max_speed, acceleration und color.
In Python muss jede Methode (egal ob Spezialmethoden wie __init__() oder eigene Methoden wie brake()) als erstes den Parameter self akzeptieren. Durch diesen Parameter kann später zu jeder Zeit auf die Attribute und Methoden eines Objektes zugegriffen werden. self ist also – wie der Name schon andeutet – eine (Selbst)Referenz des späteren Objektes. Das mag zunächst umständlich und lästig wirken – tatsächlich gibt es aber gute Gründe für dieses Vorgehen [2].
Die übrigen Parameter (speed, …, color) sind optional. Sie definieren die Standardwerte für das Auto-Objekt.
In den Zeilen 3 bis 6 werden die Parameter an Klassenattribute gebunden: Erst dadurch haben die späteren Objekte auch tatsächlich die entsprechenden Eigenschaften.
In Zeile 8 wird eine Methode brake() definiert. Als Klassenmethode muss auch sie den Parameter self akzeptieren. In Zeile 9 wird deutlich warum: Über diesen Parameter kann wieder auf das in Zeile 3 definierte Attribut speed zugegriffen werden. Im Fall der Methode brake() wird das Attribut speed um die Höhe des Attributs acceleration reduziert.
In den Zeilen 12 und 16 werden weiterhin noch die Methoden accelerate() und honk() definiert.

Klassen zum Leben erwecken

Die Klasse Car wurde definiert. Bisher gibt es aber noch keine Instanz dieser Klasse. Im Folgendem werden zwei Car-Objekte erstellt:
>>> car1 = Car()
>>> car2 = Car(speed=50)
>>> car1.speed
0
>>> car2.speed
50
>>> car1.accelerate()
Current Speed: 5
>>> car1.accelerate()
Current Speed: 10
>>> car2.brake()
Current Speed: 45
>>> car1.honk()
Tuuuuut Tuuuut
In den Zeilen 1 und 2 werden Instanzen der Klasse Car erstellt. Dabei wird implizit die Funktion __init__() aufgerufen. Für car1 werden keine Parameter übergeben, sodass die Standard-Parameter greifen (speed=0). Für car2 wird dahingegen speed=50 übergeben, so dass das speed-Attribut dieser Car-Instanz von Anfang an auf 50 gesetzt wird.
Die Zeilen 3 und 5 veranschaulichen dies: Hier wird jeweils das speed-Attribut der beiden Car-Objekte ausgelesen. Für car1 ist es 0, für car2 erwartungsgemäß 50. In den Zeilen 7 und 9 wird car1 jeweils beschleunigt. Da die Beschleunigung acceleration in Zeile 2 der Klassendefinition mit 5 angegeben wurde, wird das speed-Attribut jeweils um 5 erhöht.
In Zeile 11 wird die brake() Methode von car2 aufgerufen. Das speed-Attribut vermindert sich damit von 50 auf 45.
Zeile 13 zeigt, dass Klassenmethoden nicht immer Attribute der Klasse manipulieren müssen: Hier „hupt“ car1, indem die aufgerufenen Methode eine entsprechende Meldung auf dem Bildschirm ausgibt.
Das Beispiel zeigt, wie aus dem Bauplan Car zwei Car-Objekte erstellt und an die Namen car1 bzw. car2 gebunden wurden. Diese Objekte sind völlig unabhängig voneinander: Veränderungen am Attribut speed von car1 wirken sich nicht auf car2 aus und umgekehrt. Theoretisch könnten nun beliebig viele weitere Car-Objekte erstellt werden:
>>> ferrari = Car(max_speed=280, acceleration=10, color="red")
>>> trabbi = Car(max_speed=70, acceleration=2, color="blue")

Vererbung

Vererbung ist ein weiteres mächtiges Element der OOP: Es ist damit möglich, Attribute und Methoden von anderen Klassen zu „erben“ und weitere Attribute und Methoden hinzuzufügen. So können bequem Klassen konstruiert werden, die aufeinander aufbauen und sich gegenseitig erweitern.
Oben wurde bereits die Klasse Car für eine fiktive Rennsimulation definiert. Durch Vererbung lassen sich spezifischere Klassen von Autos erstellen – etwa eine Rennwagenklasse mit hoher Beschleunigung und hoher Endgeschwindigkeit:
class Racer(Car):
    def __init__(self, color="red"):
        Car.__init__(self, speed=0, max_speed=400, acceleration=10, color=color)
        self.current_gear = 0
        
    def honk(self):
        print "Tuuuuut Tuuuut Tuuuut"
        
    def shift_up(self):
        self.current_gear += 1
        print "Current gear: {0}".format(self.current_gear)
    
    def shift_down(self):
        self.current_gear -= 1
        print "Current gear: {0}".format(self.current_gear)
Listing: BeispielKlasseRacer.py
Die Definition von BeispielKlasseRacer.py ähnelt sehr der bei der Klasse Car. Allerdings wird hier von Car geerbt (Zeile 1). Die neue Klasse Racer hat also zunächst die gleichen Attribute und Methoden wie die Klasse Car. Die Init-Methode unterschiedet sich aber schon deutlich: Sie kennt neben dem Parameter self nur noch den Parameter color.
Nun wurde zwar von der Klasse Car geerbt – diese wurde aber noch nicht initialisiert. Irgendwie soll die Racer-Klasse ja die gleichen Attribute erhalten, wie die Car-Klasse (speed, acceleration etc.). Dies geschieht in Zeile 3.
Die Init-Methode wird hier deshalb explizit aufgerufen, damit als erster Parameter (self) eine Referenz auf das neue Racer-Objekt übergeben werden kann. Nach der Initialisierung in Zeile 3 hat das Racer-Objekt also die Attribute speed, max_speed und acceleration mit den Werten 0, 400 und 10. Außerdem noch das das Attribut color.
In Zeile 4 erhält die Klasse Racer ein zusätzliches Attribut. Da die Wagen der Racer-Klasse eine manuelle Schaltung erhalten sollen, wird der gerade eingelegte Gang an das Attribut current_gear gebunden.
Die Bedeutung der drei Funktionen honk(), shift_up() sowie shift_down() lassen sich am folgenden Beispiel gut ablesen:
>>> ferrari = Racer()
>>> ferrari.honk()
Tuuuuut Tuuuut Tuuuut
>>> ferrari.shift_up()
Current gear: 1
>>> ferrari.accelerate()
Current Speed: 10
>>> ferrari.accelerate()
Current Speed: 20
>>> ferrari.shift_up()
Current gear: 2
Zunächst wird also eine Instanz der Klasse Racer erstellt und an den Namen ferrari gebunden. Der Aufruf der Methode honk() führt bei Instanzen der Car-Klasse zur Ausgabe von Tuuuuut Tuuuut – bei Instanzen der Racer-Klasse aber zu Tuuuuut Tuuuut Tuuuut. Die Methode honk() der Klasse Racer überschreibt also die gleichnamige Methode der Elternklasse.
In den Zeilen 4 und 10 kommen die neuen Methoden shift_up() und shift_down() ins Spiel. Sie erweitern die ursprüngliche Car-Klasse also. In den Zeilen 6 und 8 wiederum wird deutlich, dass die Methode accelerate() offensichtlich von der Car-Klasse geerbt wurde. Obwohl sie in der Racer-Klasse nicht definiert wurde, steht sie zur Verfügung und zeigt das gleiche Verhalten wie in der Car-Klasse.
Es lassen sich noch viele andere Beispiele finden, in denen Klassen und Vererbungen sinnvoll zur Modellierung von Datenstrukturen eingesetzt werden können. Etwa in einem Rollenspiel: Hier würde es vermutlich zunächst die Basisklasse Charakter geben – diese hätte Attribute wie Kampfkraft, Leben oder Schnelligkeit. Darauf aufbauend gäbe es Klassen wie Magier, Kämpfer oder Drache, die jeweils unterschiedliche Attribute haben (mehr oder weniger Lebensenergie etc.) und ganz eigene Methoden wie zaubere() oder spucke_feuer() implementieren.

Klassen im Einsatz

Natürlich sind weder Renn- noch Rollenspiele der primäre Einsatzzweck von Python. Es sind lediglich sehr anschauliche Beispiele dafür, wie man ausgehend von einer Basisklasse verschiedene andere Klassen entwirft.
Ein etwas praxisnäheres Beispiel könnte aber eine Musikverwaltung sein. Auch hier stellt sich die Frage: Wie lässt sich die Funktionalität, die implementiert werden soll, sinnvoll strukturieren und in Klassen kapseln?
Eine Musikverwaltung arbeitet in aller Regel mit einer Datenbank. Hier wäre eine eigene Klasse sicher sinnvoll. Sie hätte Methoden wie erstelle_datenbank(), finde_lied() oder trage_lied_ein(). Der Vorteil dabei: Wenn im gesamten Programm diese Schnittstellen der Datenbankklasse benutzt werden, kann sehr leicht eine zweite Datenbankklasse umgesetzt werden – etwa um auch die Unterstützung von PostgreSQL anzubieten. Da die Schnittstellen (also die verfügbaren Methoden und Attribute) bei beiden Klassen identisch sind, ist ein Austausch ohne Probleme möglich.
Weiterhin benötigt die Musikverwaltung eine grafische Benutzeroberfläche (GUI). Auch diese würde in eine Klasse gekapselt werden und hätte Methoden wie zeige_dialog(), knopf_wurde_geklickt() oder eingabefeld_wurde_veraendert(). Wenn die GUI-Klasse gut entworfen wird, kann später theoretisch sehr leicht eine Ersatz-GUI entwickelt werden, etwa wenn doch Qt [3] statt GTK [4] verwendet werden soll.
Diese Beispiele lassen sich leicht fortspinnen. Die Anbindung an last.fm (Attribute: benutzername, passwort; Methoden: uebertrage_gerade_gespieltes_lied() oder anmelden()) ließe sich sicher ebenso sinnvoll in einer Klasse kappseln wie etwa ein Cover-Downloader für Amazon.

Alles ist eine Klasse

In Python haben Klassen aber eine noch viel grundlegendere Bedeutung, denn alle Datentypen (Zeichenketten, Ganz- und Fließkommazahlen, Listen oder Dictionaries) sind in Python Objekte. Es gibt also auch entsprechende Klassen, von denen geerbt werden kann:
class BoxiCode(unicode):
    def boxify(self):
        text_with_borders = u"= {0} =".format(self)
        line = len(text_with_borders) * u"="
    
        output = u"{0}\n{1}\n{2}".format(line, text_with_borders, line)
        return output
Listing: BoxiCode.py
Hier etwa wird von der Klasse unicode geerbt. Unicode-Objekte können wie folgt erstellt werden:
>>> unicode("Hallo")
u'hallo'
Aus dieser Einführung ist folgende Kurzschreibweise bereits bekannt:
>>> u"Hallo"
u'Hallo'
In beiden Fällen wird also eine Unicode-Zeichenkette mit dem Inhalt Hallo erstellt. Beide Aufrufe sind äquivalent.
Die neue Klasse BoxiCode erbt von unicode und fügt lediglich eine Methode hinzu: das altbekannte boxify().
>>> s=BoxiCode("Hallo Welt")
>>> s
u'Hallo Welt'
>>> s.lower()
u'hallo welt'
>>> print s.boxify()
==============
= Hallo Welt =
==============
BoxiCode hat offensichtlich sämtliche Methoden von unicode geerbt – im Beispiel an der Methode lower() zu sehen. Zusätzlich gibt es nun aber eine Methode, um den jeweiligen Text in einer ASCII-Box anzeigen zu lassen.
Auf gleiche Weise könnte man eine Listenklasse erstellen, die zusätzliche Methoden hat. Oder eine Ganzzahlklasse mit zusätzlichen Methoden:
class SuperInt(int):
    def iseven(self):
        return self % 2 == 0
Die neue Klasse SuperInt erbt von int und hat die neue Methode iseven(). Diese gibt True zurück, wenn die jeweilige Zahl ohne Rest durch 2 teilbar ist (% ist der Modulo-Operator).
>>> x = SuperInt(15)
>>> y = SuperInt(16)
>>> x.iseven()
False
>>> y.iseven()
True
>>> z = x+y
>>> z.iseven()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'iseven'
In den Zeilen 1 und 2 werden zwei SuperInt-Objekte erstellt. In den folgenden beiden Zeilen wird gezeigt, dass die neue Methode wie erwartet arbeitet: 15 ist ungerade, 16 gerade. In Zeile 7 werden die beiden SuperInt-Objekte addiert und das Ergebnis wird an den Namen z gebunden. Beim Versuch, auch hier die Methode iseven() aufzurufen, kommt es zu einem Fehler.
Offensichtlich handelt es sich bei dem neu erstellten Objekt, das die Summe von x und y repräsentiert, nicht mehr um ein von SuperInt abgeleitetes Objekt. Es handelt sich um ein einfaches int-Objekt und das kennt die Methode iseven() nicht.
Das hängt mit der Art und Weise zusammen, wie Python Ganzzahl-Objekte addiert. Der Aufruf 3 + 5 wird intern als 3.__add__(5) interpretiert. Es wird also die Methode __add__ der links stehenden Ganzzahl aufgerufen. Diese Methode erstellt ein neues int-Objekt. Soll nun stattdessen ein neues SuperInt-Objekt erstellt werden, müsste die Methode __add__ in der SuperInt-Klasse neue implementiert werden. Da es aber auch Methoden für Subtraktion, Division, Multiplikationen und viele andere mathematische Operationen gibt, würde dies in viel (fehlerträchtige) Arbeit ausarten.
Dieses Beispiel soll veranschaulichen, dass das Erweitern von Datentypen in Python prinzipiell möglich ist und manchmal durchaus sinnvoll sein kann. Es kann aber auch zu schwer nachvollziehbaren Fehlern führen und erschwert das Lesen des Quelltextes unter Umständen enorm. Methoden wie boxify() oder iseven() sollten daher immer als unabhängige Funktionen umgesetzt werden. Für Anfänger gilt also: Nicht von Basisdatentypen erben, keine „Spezialmethoden“ implementieren!

Die Sache mit den Unterstrichen

Attribute und Methoden in Python kommen bisweilen mit allerlei Unterstrichen daher. Es gibt drei Varianten, die dabei zu unterscheiden sind. Da es dabei häufig zu Missverständnissen und Fehlern kommt, sollen die drei Varianten kurz angeschnitten werden:

Zwei Unterstriche vor und nach dem Methodennamen (__foo__)

Hierbei handelt es sich um spezielle Methoden, die auf bestimmte Aufrufe reagieren. So wurde bereits erörtert, dass der erste Aufruf von MeineKlasse() die __init__-Methode der entsprechenden Klasse aufruft. Die Methode __add__ wiederum wird aufgerufen, wenn die Klasse links von einem +-Operator steht:
x = MeineKlasse()
x + 3
wird also als
x = MeineKlasse()
x.__add__(3)
interpretiert. Analog gibt es Methoden für viele andere mathematische Operatoren. Auch Operatoren für Listen oder Dictionaries gibt es:
x = MeineKlasse()
x["test"]
ruft intern etwa die Methode __getitem__() auf:
x = MeineKlasse()
x.__getitem__("test")
Kurz und bündig erklärt: Von doppelten Unterstrichen umgebene Methodennamen stehen für Spezialmethoden von Klassen. Entsprechend sollten sie auch nur in diesem Zusammenhang genutzt werden.
Durch die Spezialmethoden ist es möglich, eigene Klassen wie Listen, Zeichenketten oder Zahlen agieren zu lassen bzw. auf die entsprechenden Operatoren zu reagieren. Eine Übersicht verschiedener Spezialmethoden findet sich in der Python-Dokumentation [5].

Einfacher Unterstrich vor Attributen (self._foo)

Hierbei handelt es sich lediglich um eine Konvention, um ein Attribut als „privat“ zu markieren. Python kennt aber – anders als andere Programmiersprachen – keine richtigen privaten Variablen [6]. Daher ist ein einfacher Unterstrich eher als Hinweis oder als eine Erinnerung zu verstehen: “Dieses Attribut ist privat und kein Teil der offiziellen Schnittstelle.“

Doppelter Unterstrich vor Attributen, höchstens ein Unterstrich danach (self.__foo_ bzw. self.__foo)

class Test(object):
    def __init__(self):
        self._privat = "hallo"
        self.__sehrprivat = "welt"

class Test2(Test):
    def __init__(self):
        Test.__init__(self)
        self.__sehrprivat = "noch ein test"
        
        print self._Test__sehrprivat
        print self._Test2__sehrprivat
Listing: TestKlassen.py
Auch hiermit werden private Variablen markiert. Im Unterschied zum vorherigen Abschnitt werden diese aber zur Laufzeit umbenannt. Die Umbenennung von Attributen mit zwei Unterstrichen wird in Python „Name Mangling“ genannt [7]. Durch diese Umbenennung soll verhindert werden, dass Klassen, die voneinander erben, versehentlich mit ihren Attributen die Attribute der Elternklasse überschreiben:
>>> x = Test2()
welt
noch ein test
>>> x.__dict__
{'_Test2__sehrprivat': 'noch ein test', '_Test__sehrprivat': 'welt', '_privat': 'hallo'}
Das Spezialattribut __dict__ referenziert ein Dict mit allen Attributen einer Klasse. Hier ist deutlich zu erkennen, was Name Mangling bewirkt: den jeweiligen Attributen wird der Klassenname vorangestellt. So können Eltern- und Kindklasse (Test und Test2 also) die gleichen Attribut haben, ohne dass das Attribut der Elternklasse überschrieben wird.
Aber auch das Name Mangling ist keine Sicherheitsfunktion. Rein private Attribute gibt es in Python nicht.

Bemerkungen und Ausblick

Die hier besprochenen Beispiele zeigen einige der grundlegenden Mechanismen von Klassen in Python auf. Python bietet darüber hinaus viele Erweiterungen, die die Klassen noch mächtiger und nützlicher machen – etwa mehrfache Vererbung [8] oder sogenannte „Properties“ [9].
Im nächsten Teil dieser kleinen Einführung soll der erste Grundstein für eine Musikdatenbank gelegt werden. Auch wird die Fehlerbehandlung in Python ein Thema sein.
Links
[1] http://de.wikipedia.org/wiki/Objektorientierte_Programmierung
[2] http://neopythonic.blogspot.com/2008/10/why-explicit-self-has-to-stay.html
[3] http://de.wikipedia.org/wiki/Qt
[4] http://de.wikipedia.org/wiki/GTK+
[5] http://docs.python.org/reference/datamodel.html#basic-customization
[6] http://docs.python.org/tutorial/classes.html#tut-private
[7] https://secure.wikimedia.org/wikipedia/en/wiki/Name_mangling#Name_mangling_in_Python
[8] http://www5.in.tum.de/persons/ferstlc/python_kapitel_12_002.htm#mja9ad55f483dad0b289bb6a13fc9dd3fa
[9] http://realmike.org/blog/2010/07/18/introduction-to-new-style-classes-in-python/
Autoreninformation
Daniel Nögel (Webseite) beschäftigt sich seit drei Jahren mit Python. Ihn überzeugt besonders die intuitive Syntax und die Vielzahl der unterstützten Bibliotheken, die Python auf dem Linux-Desktop zu einem wahren Multitalent machen.
Diesen Artikel kommentieren

Zum Index

Python-Programmierung: Teil 5 – In medias res

von Daniel Nögel Im vorherigen Teil dieser Einführung „Python-Programmierung: Teil 4“ wurden Klassen besprochen. Mit diesem Teil soll nun ein Einstieg in die praktische Programmierung in Angriff genommen werden. Zunächst wird dazu aber noch auf die Fehlerbehandlung in Python eingegangen.

Fehlerbehandlung

Fehler werden in Python „Exceptions“ oder „Ausnahmen“ genannt. Sie treten beispielsweise auf, wenn eine Datei nicht geöffnet werden kann, ein Schlüssel einer Liste abgefragt wird, der gar nicht existiert oder auf Variablen zugegriffen wird, die noch nicht bekannt sind. Tritt ein Fehler auf, wird das Skript sofort beendet; in der Konsole erscheint eine Fehlermeldung:
>>> names = [u"Peter", u"Isabell"]
>>> names.remove(u"Karla")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
In diesem Beispiel wird zunächst eine Liste mit Namen definiert. Beim Versuch, den Eintrag Karla zu entfernen, tritt ein Fehler auf, da dieser Eintrag sich gar nicht in der Liste befindet. Um diese spezielle Situation exakt zu erfassen, wirft Python eine Ausnahme vom Typ ValueError. Python kennt bereits in seiner Standardbibliothek viele Typen von Fehlern [1]. Zusätzlich gibt Python noch die Zeilennummer und eine kurze Fehlerbeschreibung aus.
Nun ist es in den meisten Fällen nicht erwünscht, dass das Programm einfach endet und eine kryptische Fehlermeldung ausgibt. Daher ist es möglich, Stellen, an denen Fehler auftreten können, mit folgender Syntax zu umgeben:
try:
    names.remove(u"Karla")
except ValueError:
    print "Eintrag nicht in der Liste"
Bei dem Versuch, die Anweisung in Zeile 2 auszuführen, wird wie oben auch ein ValueError ausgelöst. Statt aber das Programm abzubrechen, überprüft der Interpreter, ob einer der except-Ausdrücke (es sind mehrere möglich) diesen Fehler abfängt. Das ist in Zeile 3 der Fall; der Programmfluss wird daher an dieser Stelle fortgesetzt. Grob ließe sich try... except... also mit versuche... im Fehlerfall mache... übersetzen.
Es lassen sich auch mehrere mögliche Fehler in einem except-Block abfangen:
try:
    names.remove(u"Karla")
except (SyntaxError, ValueError) as error:
    print u"Folgender Fehler ist aufgetreten {0}".format(error)
Hier wird also auf einen möglichen SyntaxError ebenso reagiert wie auf einen ValueError. Das Konstrukt except ... as error führt dazu, dass der aufgetretene Fehler im Rumpf des except-Blocks als error zur Verfügung steht. Sprich, der aufgetretene Fehler wird an den Namen error gebunden. Obwohl Fehler-Objekte keine Zeichenketten sind, können sie in vielen Situationen wie solche verwendet werden:
>>> error = ValueError("Hilfe")
>>> str(error)
'Hilfe'
In Zeile 4 des zweiten Beispiels oben geschieht im Grunde genommen Ähnliches: Das Fehler-Objekt wird implizit in eine Zeichenkette umgewandelt, sodass auf der Konsole einige Details zum Fehler erscheinen.

else und finally

Wie auch if-Blöcke kennen try... except-Blöcke das else-Statement. Der Rumpf eines else-Blockes wird ausgeführt, wenn im try-Block kein Fehler aufgetreten ist. Der finally-Block schließlich kommt immer zur Ausführung, egal ob ein Fehler auftrat oder nicht:
l = [u"Peter", u"Isabell"]
try:
    names.remove(u"Karla")
except ValueError:
    print "Karla war gar nicht in der Liste."
else:
    print "Karla wurde aus der Liste entfernt."
finally:
    print "Mir doch egal, ob Karla in der Liste war oder nicht!"
Abhängig davon, ob Karla nun in der Liste vorhanden war oder nicht, erscheint entweder die Meldung:
Karla war gar nicht in der Liste.
Mir doch egal, ob Karla in der Liste war oder nicht!
oder
Karla wurde aus der Liste entfernt.
Mir doch egal, ob Karla in der Liste war oder nicht!

Ästhetik des Scheiterns

Die Ausnahmebehandlung in Python gilt als sehr mächtig und wird oft gegenüber manuellen Tests bevorzugt. Statt also von Hand zu überprüfen, ob eine Datei vorhanden ist und ob der Benutzer die nötigen Rechte hat, diese Datei zu lesen, würde ein Python-Programmierer es einfach auf einen Versuch ankommen lassen und die Datei öffnen. Tritt ein Fehler auf, wird dieser durch entsprechende except-Blöcke abgefangen und dort darauf reagiert.
Auftretende Fehler werden gewissermaßen von „unten nach oben“ durchgereicht:
def innen():
    return int("asd")

def mitte():
    return innen()

def aussen():
    return mitte()

print aussen()
Der Umwandeln einer Zeichenkette wie asd in eine Ganzzahl wird zu einem ValueError führen. Die Frage lautet nun: An welcher Stelle muss dieser Fehler nun abgefangen werden?
Tatsächlich gibt es theoretisch vier Möglichkeiten: Der Fehler tritt zunächst in der Funktion innen() auf. Dort könnte er mit einem try... except...-Block abgefangen werden. Wird der Fehler dort nicht behandelt, wird er an die übergeordnete Funktion mitte() weitergegeben. Geschieht auch hier keine Fehlerbehandlung, überprüft der Interpreter, ob auf nächsthöherer Ebene (also in aussen()) eine Fehlerbehandlung stattfindet. Ist auch dies nicht der Fall, besteht noch die Möglichkeit, den Fehler in Zeile 10 abzufangen. Erst wenn an all diesen Stellen keine Fehlerbehandlung stattgefunden hat, bricht der Interpreter die Abarbeitung des Skripts ab.
Viele Anfänger mögen sich jetzt die Frage stellen: Wieso vermeidet man nicht einfach Fehler, indem man dafür sorgt, dass die möglichen Ausnahmen nicht auftreten können? Folgender Code-Schnipsel soll das verdeutlichen:
# ein Dictionary
persons = {u"Peter": "m", u"Karla": "w"}

# irgend wo spaeter im Code
if u"Peter" in persons:
    gender = persons[u"Peter"]
Man könnte an dieser Stelle natürlich auch einen KeyError abfangen:
try:
    gender = persons[u"Peter"]
except KeyError:
    pass
Diese kleine Situation zeigt das Dilemma. Oftmals ist nicht unbedingt klar und direkt ersichtlich, wie man ein solches Problem optimal löst. In Python wird das EAFP-Prinzip (frei übersetzt: „Erst schießen, dann fragen.“) dem LBYL-Paradigma (in etwa: „Schau, bevor du abspringst“) vorgezogen [2]. EAFP würde offensichtlich für die zweite Variante sprechen, außerdem spricht dafür, dass das Abklopfen aller Eventualitäten den Code unter Umständen sehr aufblähen kann. Andererseits handelt es sich bei Exceptions um Ausnahmen. Sollte es – wieso auch immer – sehr wahrscheinlich sein, dass der Schlüssel (oben: Peter) an dieser Stelle nicht verfügbar ist, so wäre die Lösung mittels Test auf Vorhandensein sinnvoller, da es expliziter die erwartete Situation ausdrückt. Entscheidend für die gute Nutzung von Exceptions ist also die Situation im Code. Über die Vor- und Nachteile von EAFP und LBYL hat Oran Looney einen sehr anschaulichen Artikel verfasst [3]. Weitere allgemeine Hinweise zur Fehlerbehandlung in Python finden sich in der offiziellen Dokumentation [4].

Musikverwaltung

Die bisher vorgestellten Funktionen und Möglichkeiten Pythons sollen nun in einem etwas umfassenderen Beispiel – einer kleinen Musikverwaltung – veranschaulicht werden. In den nächsten beiden Teilen dieser Reihe wird dieses Beispiel dann sukzessive ausgebaut. Den Anfang macht ein kleines Modul, das Verzeichnisse rekursiv nach Musikdateien durchsucht und deren Tags in einer Liste von Dictionaries abbildet.

ID-Tags lesen

Zum Auslesen der ID3-Tags wird die Python-Bibliothek mutagen [5] eingesetzt. In Ubuntu ist es als Paket python-mutagen in den Paketquellen verfügbar und kann darüber bequem nachinstalliert werden.
Das Modul zum Auslesen der MP3s soll readmp3s heißen. Entsprechend wird die Datei readmp3s.py angelegt und mit folgenden Inhalt befüllt:
#!/usr/bin/env python
# coding:utf-8

import os
import sys
from mutagen.easyid3 import EasyID3
import mutagen.mp3
import mutagen.oggvorbis
Listing: readmp3s-imports.py
Neben Shebang und Zeichenkodierung finden sich hier fünf Importe: Das Modul os beinhaltet diverse betriebsystemabhängige Schnittstellen. Das Modul sys wird genutzt, um auf die Parameter des späteren Programmes zugreifen zu können. Der Import
from mutagen.easyid3 import EasyID3
erscheint zunächst kompliziert. Es wird aber lediglich vom Submodul easyid3 des Paketes mutagen die Klasse EasyID3 in den Namensraum des Skriptes importiert. In Teil 3 dieser Einführung (siehe freiesMagazin 12/2010 [6]) wurde von derartigen from-Importen abgeraten, mutagen wurde aber bewusst so geschrieben, dass das gezielte Importieren einzelner Subbibliotheken möglich und sinnvoll ist.
Mit import mutagen.mp3 wird schließlich noch das Submodul mp3 in den Namensraum des Skriptes importiert. Der letzte Import verfährt parallel mit dem Submodul oggvorbis. Diese beiden Module stellen später Schnittstellen zu den ID3- bzw. Vorbis-Tags bereit.
Als nächstes wird eine Funktion implementiert, welche die Tags der Audiodateien ausliest und zurückgibt. Treten Fehler auf, gibt die Funktion None zurück.
def get_infos(path):

    path = path.decode("utf-8")
    
    if path.lower().endswith(".mp3"):
        audio = mutagen.mp3.MP3(path, ID3=EasyID3)
    else:
        audio = mutagen.oggvorbis.OggVorbis(path)
    
    length = audio.info.length

    infos = {"path":path, "length":length}
    for tag in ["title", "artist", "album"]:
        content =  audio.get(tag, [None])[0]
        infos[tag] = content
    
    return infos
Listing: readmp3s-get_infos.py
Der Kopf der Funktion hält keine Überraschungen bereit: Die Funktion get_infos erwartet nur einen Parameter path. In Zeile 3 werden die Pfadangaben in Unicode umgewandelt – dies ist schon allein in Hinblick auf die SQLite-Datenbank zu empfehlen. Nun ist es immer möglich, dass einige Dateinamen fehlerhaft kodiert wurden (das berühmte Fragezeichen im Dateinamen). In diesem Fall würde eine UnicodeDecodeError auftreten. Dieser Fehler wird in dieser Funktion nicht behandelt, daher muss er also an anderer Stelle abgefangen werden.
In den Zeilen 6 und 8 wird abhängig von der Dateiendung (besser wäre natürlich eine Überprüfung des MIME-Types [7]) eine Instanz der Klasse mutagen.mp3.MP3 oder mutagen.oggvorbis.OggVorbis erstellt. Es wird jeweils der Pfad zur Audiodatei übergeben. Der Zusatz ID3=EasyID3 in Zeile 6 sorgt weiterhin dafür, dass eine vereinfachte Schnittstelle auf die ID3-Tags bereitsteht. Die etwas kryptischen ID3-Tags (TPE1, TALB) können so einfach als artist bzw. album angesprochen werden. Die erstellten MP3- und OggVorbis-Objekte werden jeweils an den Namen audio gebunden und verhalten sich in vielerlei Hinsicht ähnlich. So kann in Zeile 10 bequem die Dauer der Audiodatei ausgelesen werden – unabhängig davon, um welches Format es sich dabei handelt.
In Zeile 12 wird zunächst ein neues Dict mit dem Pfad und der Dauer erstellt und an den Namen infos gebunden. Die Schleife in Zeile 13 iteriert über die Liste mit Tags, die ausgelesen werden sollen. Die Objekte mutagen.mp3.MP3 und mutagen.oggvorbis.OggVorbis verhalten sich wie ein Dict, sodass mit audio["title"] der Titel der jeweiligen Datei abgefragt werden könnte. Da aber nicht jede Audiodatei zwangsläufig jedes Tag hat, könnten hier Fehler auftreten. An dieser Stelle erspart man sich weitere Fehlerbehandlungen und nutzt die get-Methode der Dicts. Allerdings gibt Mutagen nicht einfach Zeichenketten für die gewünschten Tags aus. Es werden immer Listen zurückgegeben, sodass man statt Künstlername die Liste ["Künstlername"] erhält. Daher wird am Ende von Zeile 14 mit [0] das erste Listenelement ausgelesen – das ist in den meisten Fällen völlig ausreichend. Hier lauert freilich auch eine kleine Stolperfalle: Die get-Methode arbeitet bekanntlich mit einem Standard-Parameter, der zurückgegeben wird, falls das Dict den angefragten Wert nicht enthält. Dieser Wert ist in dem Fall None. Da die Rückgabe der get-Methode aber als Liste behandelt wird ([0]), muss auch None zunächst in eine Liste gepackt werden.
Nach dem Durchlaufen der Schleife wird in Zeile 17 das Dict an die aufrufende Funktion übergeben.
Hinweis: In den Zeilen 10 und 14 werden audio.info.length und die Rückgabe von audio.get() an die Namen length bzw. content gebunden. Dieser Zwischenschritt dient hier lediglich der Veranschaulichung und der Übersichtlichkeit. Für gewöhnlich würde man diesen Zwischenschritt auslassen:
{"path":path, "length":audio.info.length}
...
infos[tag] = audio.get(tag, [None])[0]

Verzeichnis rekursiv auslesen

Nun wird noch eine Funktion benötigt, die die MP3s auf der Festplatte findet und an die Funktion get_infos() übergibt. Diese Funktion soll read_recursively() heißen.
def read_recursively(path):

audio_infos = []
counter = 0
error_counter = 0
    
for root, dirs, files in os.walk(path):
    for fl in files:
        if fl.lower().endswith(".mp3") or fl.lower().endswith(".ogg"):
            path = os.path.join(root, fl)
            try:
                infos = get_infos(path)
            except (UnicodeDecodeError, mutagen.mp3.HeaderNotFoundError, mutagen.id3.error) as inst:
                print u"\n\nSKIPPING reading this file:"
                print path
                print u"ERROR: {0}\n".format(inst)
                error_counter +=1
            else:
                audio_infos.append(infos)
                counter +=1
        print "\rScanning: {0} Files ok, {1} Files broken".format(counter, error_counter),
print 
return audio_infos
Listing: readmp3s-read_recursively.py
Auch hier zunächst nichts Neues: Die Funktion kennt einen Parameter path. Damit soll später das Verzeichnis übergeben werden, welches rekursiv durchsucht werden soll. In Zeile 3 wird eine Liste erstellt und an den Namen audio_infos gebunden. Damit sollen später sämtliche von get_infos() erzeugten Dicts mit den Tags aufbewahrt werden. counter und error_counter sollen lediglich zählen, wie viele Audiodateien bereits verarbeitet wurden und bei wie vielen es zu Fehlern kam.
In Zeile 7 wird eine neue Funktion eingeführt: os.walk() durchläuft ein Verzeichnis rekursiv. Für jedes Verzeichnis gibt die Funktion dabei den Pfad des aktuellen Verzeichnisses (root), die Verzeichnisse in dem aktuellen Verzeichnis (dirs) und die Dateien im jeweiligen Verzeichnis (files) an. Bei os.walk() [8] handelt es sich um einen sogenannten Generator. Das ist der Grund, warum die Funktion in einer Schleife verwendet werden kann [9].
Da für die Musikdatenbank nur Dateien von Interesse sind, genügt es, in Zeile 8 über die Dateiliste eines jeden Verzeichnisses zu iterieren. In Zeile 9 wird für jede Datei geprüft, ob sie mit .mp3 oder .ogg endet. Da die Dateien theoretisch auch auf .mP3 oder .OgG enden könnten, werden die Dateinamen in Kleinbuchstaben verglichen. Auch hier gilt: Die Abfrage der Dateiendung ist keine sonderlich befriedigende Lösung – für das erste kleine Projekt sollte dieses Vorgehen aber ausreichend sein.
Da in der Liste files nur Dateinamen vorhanden sind, wird in Zeile 10 der komplette Pfad erzeugt. Die Funktion os.path.join() kümmert sich dabei darum, dass die korrekten Schrägstriche verwendet werden (Linux und Windows unterscheiden sich in dieser Hinsicht). Durch os.path.join wird weiterhin sicher gestellt, dass es nicht versehentlich zu doppelten Schrägstrichen im Pfad kommt. In Zeile 12 schließlich wird der so erstellte Pfad an die Funktion get_infos() übergeben und das Ergebnis an den Namen info gebunden.
In Zeile 11 wird aber zunächst die Fehlerbehandlung eingeleitet. Die in der Funktion get_infos() nicht behandelten Fehler sollen also an dieser Stelle abgefangen werden. In Zeile 13 werden dabei drei Fehler erwartet: Der bereits erwähnte UnicodeDecodeError findet sich ebenso wie die Fehler mutagen.mp3.HeaderNotFoundError und mutagen.id3.error. Während der UnicodeDecodeError auftritt, wenn eine Zeichenkette nicht in Unicode umgewandelt werden konnte und damit für die später verwendete SQLite-Datenbank ungeeignet ist, tritt der HeaderNotFoundError auf, wenn Mutagen in einer MP3-Datei keinen MP3-Header finden konnte. mutagen.id3.error schließlich fängt viele weitere Mutagen-Fehler ab. Dies erspart dem Entwickler die Arbeit, jeden einzelnen möglichen Fehler im Vorfeld zu definieren. Ihm genügt es (wie in diesem Beispiel) zu wissen, dass Mutagen – warum auch immer – eine bestimmte Datei nicht verarbeiten konnte.
In den Zeilen 14-17 findet die Fehlerbehandlung statt. Der Fehler wird in der Konsole ausgegeben und der Fehlerzähler wird um 1 erhöht. Nur wenn kein Fehler auftritt (Zeile 18) wird in Zeile 19 die Rückgabe von get_infos() an die Liste audio_infos angehängt. In diesem Fall wird counter um 1 erhöht.
Eine letzte Besonderheit findet sich in Zeile 21. Die Zeichenkette dort beginnt mit \r. Diese Escape-Sequenz steht für den sogenannten Wagenrücklauf. Dadurch wird der Cursor im Terminal wieder auf den Anfang der Zeile gesetzt. Nachfolgende Ausgaben mit print würden die vorherigen Ausgaben überschreiben. Da print eine Zeile im Normalfall mit einem Zeilenumbruch abschließt, wird dies hier mit dem Komma am Ende der Zeile unterbunden.
Der Wagenrücklauf und das Unterbinden des Zeilenumbruches sorgen dafür, dass die Konsole nicht mit Textausgaben überflutet wird: Die Anzahl der verarbeiteten Dateien wird immer in die selbe Zeile geschrieben.
Hinweis: In Python >= 3 müsste Zeile 17 wie folgt aussehen:
print ("\rScanning: {0} MP3s ok, {1} MP3s broken".format(counter, error_counter), end="")

Kleiner Test

Obwohl die Musikdatenbank noch in weiter Ferne ist, kann zumindest das Modul readmp3s schon einmal getestet werden. Dazu wird am Ende des Skripts noch folgende if-Block ergänzt:
if __name__ == "__main__":
    try:
        path = sys.argv[1]
    except IndexError:
        print "Usage:"
        print "readmp3s.py DIRECTORY"
    else:
        mp3s = read_recursively(path)
Listing: readmp3s-main.py
Die erste Zeile überprüft dabei, ob die Datei als Modul oder als eigenständiges Skript geladen wurde. Nur im letzteren Fall kommen die folgenden Zeilen zur Ausführung. In Zeile 3 wird der zweite Parameter des Skriptes aus der Liste sys.argv gelesen. Der erste Parameter ist immer der Dateiname (hier also readmp3s.py). Falls der Benutzer keine Parameter übergeben und die Liste nur einen Eintrag hat, wird es in Zeile 3 zu einem IndexError kommen, der aber in Zeile 4 abgefangen wird. Im Fehlerfall wird dann ein kurzer Hinweis auf den erforderlichen Parameter gegeben. In Zeile 8 wird die Funktion read_recursively() aufgerufen. Wie bereits erwähnt kommt dieser else-Block nur zur Ausführung, wenn zuvor kein Fehler auftrat.
Das Skript wird nun das angegebene Verzeichnis durchsuchen, die ID-Tags auslesen und über die Anzahl lesbarer und nicht-lesbarer MP3s informieren. Keine Sorge: Das Skript ist bisweilen etwas empfindlich, was die Zeichenkodierung und die MP3-Header betrifft und nicht alle als defekt gemeldeten MP3s sind tatsächlich unbrauchbar.
Im nächsten Teil dieser Reihe wird die Musikdatenbank um eine SQLite-Datenbank ergänzt.
Links
[1] http://docs.python.org/library/exceptions.html
[2] http://docs.python.org/glossary.html
[3] http://oranlooney.com/lbyl-vs-eafp/
[4] http://docs.python.org/tutorial/errors.html
[5] http://code.google.com/p/mutagen/
[6] http://www.freiesmagazin.de/freiesMagazin-2010-12
[7] http://de.wikipedia.org/wiki/MIME-Type
[8] http://docs.python.org/library/os.html#os.walk
[9] http://de.wikibooks.org/wiki/Python-Programmierung:_Funktionen#Generatoren_und_yield
Autoreninformation
Daniel Nögel (Webseite) beschäftigt sich seit drei Jahren mit Python. Ihn überzeugt besonders die intuitive Syntax und die Vielzahl der unterstützten Bibliotheken, die Python auf dem Linux-Desktop zu einem wahren Multitalent machen.
Diesen Artikel kommentieren

Zum Index

Python-Programmierung: Teil 6 – Datenbank für die Musikverwaltung

von Daniel Nögel Im fünften Teil dieser Reihe „Python-Programmierung: Teil 5“ wurde die Fehlerbehandlung in Python besprochen. Darüber hinaus wurde der Grundstein für eine kleine Musikverwaltung gelegt. Bisher wurden Funktionen implementiert, die ein gegebenes Verzeichnis rekursiv nach Musikdateien durchsuchen und dann deren ID-Tags auslesen. In diesem Teil soll die Musikverwaltung um eine Datenbank erweitert werden, die bequem durchsucht werden kann.

Die Datenbank

Als Datenbank wird in diesem Fall SQLite eingesetzt [1]. Bei SQLite handelt es sich um ein SQL-Datenbanksystem, das ohne Server-Software auskommt und daher auch neben der SQLite-Programmbibliothek selbst keine weitere Software auf dem Zielrechner erfordert. SQLite unterstützt viele SQL-Sprachbefehle, ist aber in einigen Bereichen simpler gehalten als beispielsweise MySQL.
Für die Verwendung in Python muss neben der SQLite-Programmbibliothek (sqlite3) noch die entsprechende Python-Schnittstelle installiert werden. Diese findet sich in Ubuntu beispielsweise im Paket python-sqlite2.
Das im letzten Teil erstellte Python-Skript soll nun um eine Datenbankanbindung erweitert werden. Wer bei den Ergänzungen den Überblick verliert, kann das fertige Skript musicdb.py auch direkt herunterladen und dort die Änderungen nachvollziehen.

Die neuen Importe

Zunächst müssen einige Importe ergänzt werden:
import sqlite3

import subprocess
from optparse import OptionParser

import codecs
from textwrap import dedent
from random import shuffle
Hier werden eine ganze Reihe neuer Module und Funktionen eingeführt. sqlite3 stellt schlicht die Schnittstelle zum SQLite-Datenbanksystem bereit [2].
Bei subprocess handelt es sich um ein Modul, mit dem aus Python heraus andere Prozesse gestartet werden können. Darüber hinaus können mit dem Modul Signale an diese Prozesse gesendet werden oder STDOUT bzw. STDERR ausgelesen werden. Auch das Schreiben nach STDIN ist mit subprocess möglich [3]. In diesem Skript wird es später lediglich benötigt, um das Medienwiedergabeprogramm Totem zu starten und einige MP3s damit abzuspielen.
Das Modul optparse hält verschieden Werkzeuge bereit, um die Optionen und Argumente von Skripten auszuwerten. Auch lassen sich damit recht einfach Übersichten der möglichen Optionen erstellen [4].
Neu ist das Modul codecs [5]. Mit dessen Funktion open() kann später bequem eine Datei mit einer beliebigen Zeichenkodierung geschrieben werden.
Die Funktion dedent aus dem Modul textwrap wird später im Skript dazu genutzt, eine mehrzeilige, eingerückte Zeichenkette ohne Einrückungen ausgeben zu können [6].
Einzig das Modul random sollte aus den vorherigen Teilen dieser Reihe bereits bekannt sein. Es stellt verschiedene Zufallsfunktionen zur Verfügung. Die Funktion shuffle durchmischt eine gegebene Liste schlicht, sodass sich damit beispielsweise eine Wiedergabeliste durchmischen ließe [7].

Die Datenbankanbindung

Als nächstes soll nun die Datenbankanbindung des Skripts erstellt werden. Diese besteht aus zwei Klassen: einer Cursor-Klasse und einer Datenbank-Klasse. Beide Klassen verfügen aber über eine Neuerung, die hier kurz erläutert werden soll.

Das Schlüsselwort with

Das Schlüsselwort with ist ein Sprachelement, das es seit Python 2.5 gibt [8]. Es wird besonders häufig beim Arbeiten mit Dateien eingesetzt, weshalb es auch an diesem Beispiel erörtert werden soll. Für gewöhnlich wird beim Umgang mit Dateien ein Konstrukt wie das folgende benötigt:
handler = open("datei.txt", "r")
try:
    print handler.read()
finally:
    handler.close()
In Zeile 1 wird dabei eine Datei geöffnet und das daraus resultierende Datei-Objekt an den Namen handler gebunden. In Zeile 3 wird der Inhalt der Datei ausgegeben. Der try...finally-Block stellt sicher, dass das Datei-Objekt anschließend in jedem Fall geschlossen wird – auch wenn beim Auslesen der Datei in Zeile 3 eventuell Fehler aufgetreten sind. Die Konstruktion „Vorbereiten, Bearbeiten, Aufräumen“ ist allerdings auch in anderen Zusammenhängen so häufig anzutreffen, dass ab Python 2.5 mit with eine deutliche Vereinfachung für derartige Fälle eingeführt wurde:
with open("datei.txt", "r") as handler:
    print handler.read()
Auch hier wird zunächst die Datei open.txt zum Lesen geöffnet und das daraus resultierende Datei-Objekt an den Namen handler gebunden. Allerdings erfolgt die Zuweisung des Namens hier durch das Schlüsselwort as. In Zeile 2 wird – wie gehabt – der Inhalt der Datei ausgegeben. Damit sind die beiden Schritte „Vorbereiten“ und „Bearbeiten“ abgehandelt. Das Aufräumen erfolgt mit dem Verlassen der Kontrollstruktur, also am Ende des with-Blocks. Es muss nicht explizit durchgeführt werden. Wie funktioniert das?
Seit Python 2.5 gibt es zwei neue spezielle Methoden, die Objekte implementieren können: __enter__ und __exit__. Die Methode __enter__ ist für die Vorbereitung zuständig und wird implizit beim Betreten des with-Blocks aufgerufen. Der Rückgabewert dieser Methode wird dann an den mit as angegebenen Namen gebunden – oben also an den Namen handler. Die Methode __exit__ wird beim Verlassen des with-Blocks aufgerufen – unabhängig davon, ob ein Fehler aufgetreten ist oder nicht.
Diese Konstruktion wird im Folgenden auch für die Datenbank verwendet. Da die SQLite-Schnittstelle für Python von Haus aus noch keinen Gebrauch von diesem neuen Sprachmittel macht, wird es hier selbst implementiert.

Der Cursor

SQLite kennt sogenannte Cursor, mit denen Datensätze in Datenbanken ausgelesen und bearbeitet werden [9]. In der Regel folgt auch das Arbeiten mit Cursorn dem Schema „Vorbereiten, Bearbeiten, Aufräumen“, weshalb sich hier die Verwendung der with-Anweisung empfiehlt.
Das bisherige Skript aus dem letzten Teil wird nun um diese Klasse ergänzt:
class Cursor(object):
    def __init__(self, connection):
        self.connection = connection

    def __enter__(self):
        self.cursor = self.connection.cursor()
        return self.cursor

    def __exit__(self, type, value, traceback):
        self.cursor.close()
Es handelt sich hierbei letztlich nur um einen sogenannten „Wrapper“, der die Verwendung von SQLite-Cursorn mit with ermöglicht. Später könnte ein Aufruf wie folgt aussehen:
with Cursor(connection) as cursor:
    cursor.machetwas()
connection ist dabei eine Referenz auf ein SQLite-Connection-Objekt, das eine offene Datenbankverbindung verwaltet. In Zeile 1 wird nun zunächst eine Instanz der Cursor-Klasse erstellt und connection als Parameter übergeben. In der Methode __init__ wird die Referenz auf das Connection-Objekt an das Attribut self.connection gebunden. Erst danach wird durch den with-Block die Methode __enter__ des Cursor-Objektes aufgerufen. Hier wird nun das SQLite-Cursor-Objekt durch den Aufruf self.connection.cursor() erstellt. Das Objekt wird an das Attribut self.cursor gebunden und mittels des Schlüsselwortes return zurückgegeben. Da der Rückgabewert der Methode __enter__ in with-Blöcken an den hinter as angegebenen Namen gebunden wird, steht nun eine Referenz auf das SQLite-Cursor-Objekt unter dem Namen cursor zur Verfügung. In Zeile 2 des Beispiels kann so bequem auf das SQLite-Cursor-Objekt zugegriffen werden. Mit dem Verlassen des with-Blocks würde schließlich die Methode __exit__ aufgerufen werden, in der das Cursor-Objekt korrekt geschlossen wird.
Noch einmal zur Erinnerung: Die Cursor-Klasse fungiert hier lediglich als Wrapper. Ohne sie sähe jeder Zugriff auf SQLite-Cursor wie folgt aus:
cursor = connection.cursor()
try:
    cursor.machetwas()
finally:
    cursor.close()

Die Datenbankklasse

Als nächstes soll nun die eigentliche Datenbankanbindung realisiert werden:
class DatabaseConnector(object):

    def __enter__(self):
        path = os.path.dirname(os.path.abspath((__file__)))
        self.db_path = os.path.join(path, "music.db")
        if not os.path.exists(self.db_path):
            self.create_database()
        self.connection = sqlite3.connect(self.db_path)
        self.connection.row_factory = sqlite3.Row
        return self

    def __exit__(self, type, value, traceback):
        self.connection.close()

    def create_database(self):
        sql = u"""
            CREATE TABLE IF NOT EXISTS mp3s (
                id INTEGER PRIMARY KEY,
                artist STRING,
                title STRING,
                album STRING,
                length INT,
                path STRING UNIQUE
            )"""
        connection = sqlite3.connect(self.db_path)
        with Cursor(connection) as cursor:
            cursor.execute(sql)
        connection.close()

    def search(self, search):
        search = u"        search = search.replace(" ", "        print search
        sql = u"""
            select *
            from mp3s
            where   artist like ? or
                    album like ? or
                    title like ?
        """
        with Cursor(self.connection) as cursor:
            return cursor.execute(sql, (search, )*3).fetchall()

    def get_all_songs(self):
        with  Cursor(self.connection) as cursor:
            return cursor.execute("SELECT * FROM mp3s").fetchall()

    def insert_songs(self, songs):
        print "Inserting MP3s"
        sql = u"""INSERT OR IGNORE INTO mp3s (
            artist,
            title,
            album,
            length,
            path
        ) VALUES (
            :artist,
            :title,
            :album,
            :length,
            :path
        )
        """
        with Cursor(self.connection) as cursor:
            cursor.executemany(sql, songs)
        self.connection.commit()

    def count(self):
        sql = "select count(id) from mp3s"
        with Cursor(self.connection) as cursor:
            return cursor.execute(sql).fetchone()[0]
Listing: musicdb-databaseconnector.py
Auch die Klasse DatabaseConnector kennt die Methode __enter__ und __exit__ und ist damit auf die Verwendung mit with ausgelegt. In der Methode __enter__ werden dabei alle relevanten Vorbereitungen für die Erstellung der Datenbankverbindung getroffen. Zunächst wird der Ordner des Skriptes per
os.path.dirname(os.path.abspath((__file__)))
ermittelt und an den Namen path gebunden [10]. In dem selben Ordner soll nun auch die Datenbank music.db abgelegt werden. Existiert diese Datenbank noch nicht, wird sie mit der Methode create_database() erstellt. Anschließend wird durch den Aufruf
self.connection = sqlite3.connect(self.db_path)
eine Verbindung zu dieser Datenbank erstellt und das Connection-Objekt an das Attribut self.connection gebunden. Mit der Zeile
self.connection.row_factory = sqlite3.Row
wird SQLite angewiesen, die Ergebnisse in Form von Row-Objekten [11] darzustellen. Dadurch lassen sich die Ergebnisse von Datenbankabfragen später sehr leicht über Schlüsselworte ansprechen.
Neben der Methode __enter__, die die Datenbankverbindung schlicht wieder schließt, kennt die Klasse DatabaseConnector weiterhin noch die Methoden search, get_all_songs, insert_songs und count. Hier werden jeweils verschiedene Datenbankzugriffe getätigt; es kommt jeweils die oben vorgestellte Cursor-Klasse zum Einsatz. Die Bedeutung der einzelnen SQL-Abfragen sollte weitestgehend bekannt oder aus dem Kontext ersichtlich sein. Im Rahmen dieser Einführung ist eine tiefergehende Auseinandersetzung mit der SQL-Syntax leider nicht möglich. Eine Übersicht der SQL-Befehle findet sich allerdings auf der Homepage des SQLite-Projektes [12]. Dennoch sollen einige Anmerkungen zu kleinen Besonderheiten gemacht werden.
In der Methode search werden dem Suchbegriff Prozentzeichen voran- und nachgestellt, außerdem werden Leerzeichen durch Prozentzeichen ersetzt. Hierbei handelt es sich lediglich um die SQL-typischen Wildcard-Zeichen. Am Ende der Methode insert_songs findet sich außerdem dieser Aufruf:
cursor.executemany(sql, songs)
Damit wird eine Liste von mehreren Liedern in die Datenbank eingetragen. songs ist in diesem Fall eine Liste von Tupeln nach folgendem Schema:
[
 ("Kuenstler", "Titel", "Album", "Laenge", "Pfad"),
 ("Kuenstler", "Titel", "Album", "Laenge", "Pfad"),
 ...
]
So können sehr effizient große Mengen von Titelinformationen in die Datenbank übernommen werden. Durch das anschließende
connection.commit()
werden die Änderungen übernommen. Dieser Aufruf muss immer dann erfolgen, wenn schreibend auf die Datenbank zugegriffen wurde. Ansonsten wären die Änderungen bei der nächsten Abfrage noch nicht sichtbar.

Wiedergabelisten erstellen

Die Musikverwaltung soll in der Lage sein, Lieder, die auf eine Suche passen, in eine Wiedergabeliste zu speichern. Dazu wird das Skript um folgende drei Funktionen erweitert:
def generate_extended_playlist(songs):
    playlist = [u"#EXTM3U\n"]
    for id, artist, title, album, length, path in songs:
        playlist.append("#EXTINF:{0},{1} - {2}\n".format(int(length), artist,
                        title))
        playlist.append("{0}\n".format(path))
    return u"".join(playlist)

def generate_simple_playlist(songs):
    return u"\n".join(hit["path"] for hit in songs)

def dump(playlist, path, encoding="utf-8"):
    with codecs.open(path, "w", encoding=encoding) as fh:
        fh.write(playlist)
Die ersten beiden Funktionen erwarten jeweils eine Liste songs. Diese Songs sind im Fall dieses Skripts eigentlich Row-Objekte, wie sie von der Methode search des DatabaseConnectors zurückgegeben werden. Diese Row-Objekte verhalten sich dahingehend wie Listen und Dicts, als dass sie den Zugriff über Schlüsselworte ebenso ermöglichen, wie über Listen-Indizes.
Die Funktion generate_simple_playlist erstellt nun schlicht eine Zeichenkette mit Pfadangaben, getrennt durch einen Zeilenumbruch (\n). Die Zeile mutet zunächst recht kompliziert an:
return u"\n".join(hit["path"] for hit in songs)
Dies ist aber nur eine kompaktere und effizientere Variante für folgenden Code (siehe Abschnitt „Kleine Aufgabe“ unten):
paths = []
for hit in songs:
    paths.append(hit["song"])
return u"\n".join(paths)
Die so erstellte Zeichenkette mit Pfandangaben kann als einfache m3u-Liste gespeichert werden. Sollen außerdem noch Meta-Informationen in der Wiedergabeliste gespeichert werden, muss eine erweiterte m3u-Liste erstellt werden. Dies geschieht durch die Funktion generate_extended_playlist. Auch hier wird eine Zeichenkette erstellt, die später als Wiedergabeliste gespeichert werden kann. Allerdings wird dabei zunächst der Umweg über eine Liste gegangen: Jeder Eintrag in der Liste repräsentiert später eine Zeile. Mit
playlist = [u"#EXTM3U\n"]
wird die Liste playlist direkt initial befüllt, sodass die spätere Wiedergabeliste in der ersten Zeile die Information enthält, dass es sich um eine erweiterte Wiedergabeliste handelt. In der folgenden for-Schleife werden die einzelnen Lieder durchlaufen. Für jedes Lied wird dabei ein Listen-Eintrag mit Meta-Informationen (#EXTINF) und ein Listeneintrag mit der dazugehörigen Pfadangabe erstellt. Erst in der letzten Zeile der Funktion wird aus der Liste playlist mit Hilfe der Methode join eine Zeichenkette, die direkt zurückgegeben wird.
Die dritte Funktion dump schreibt schließlich eine gegebene Zeichenkette (in diesem Fall die Wiedergabelisten) in eine Datei. Statt der Funktion open kommt dabei allerdings die gleichnamige Funktion aus dem Modul codecs zum Einsatz. Diese Funktion hat den Vorteil, dass die gewünschte Ziel-Kodierung direkt wählbar ist (hier UTF-8).

Startbedingungen

In einem letzten Schritt wird nun der Block
if __name__ == "__main__":
    ...
komplett ersetzt:
if __name__ == "__main__":
    parser = OptionParser()
    parser.add_option("-s", "--scan", dest="add", help="add_directory",
            metavar="DIR")
    parser.add_option("-f", "--find", dest="search",
            help="Search database for tags", metavar="TERM")
    parser.add_option("--shuffle", dest="shuffle", metavar="INT", type="int",
            help="Takes randomly INT various songs from database")
    parser.add_option("-p", "--playlist", dest="playlist", metavar="FILE",
            help="Write the search results to a playlist (requires -s)")
    parser.add_option("-t", "--totem", dest="totem", action="store_true",
            help="Play the search result (requires -s)")
    parser.epilog = dedent("""
            'musikverwaltung' is a simple commandline music database. You are
            only able to add directories and search for tags.
            """)

    options, args = parser.parse_args()

    if not (options.search or options.shuffle) and \
            (options.playlist or options.totem):
        parser.error(dedent("""
            Writing / Playing playlists is only possible after a search.
            Use -f / --find plus searchterm"""))

    with DatabaseConnector() as database:
        if options.add:
            songs = read_recursively(options.add)
            database.insert_songs(songs)
            print
            print "Number of MP3s in your database: {0}".format(
                    database.count())
        elif options.search or options.shuffle:
            if options.search:
                searchterm = options.search.decode("utf-8")
                print
                songs = database.search(searchterm)
            else:
                songs = database.get_all_songs()
                shuffle(songs)
                songs = songs[:options.shuffle]
            if songs:
                print generate_simple_playlist(songs)
                if options.playlist:
                    if not options.playlist.lower().endswith(".m3u"):
                        options.playlist += ".m3u"
                    dump(generate_extended_playlist(songs), options.playlist)
                if options.totem:
                    command = ["totem", "--enqueue"]
                    for song in songs:
                        command.append(song["path"])
                    subprocess.Popen(command)
            else:
                print u"No results found for '{0}'".format(searchterm)
        else:
            parser.print_help()
Listing: musicdb-main.py
Darin wird zunächst eine Instanz des OptionParser erzeugt und an den Namen parser gebunden. In den folgenden Zeilen werden die verschiedenen Optionen definiert, die das Skript später kennt. Die Methode add_option fügt jeweils eine weitere Option hinzu und definiert die dazugehörigen Schalter (beispielsweise -s oder --scan), das Schlüsselwort, über das die Option später ausgelesen wird (dest) und einen kurzen Hilfetext (help). Es gibt eine Reihe weiterer Möglichkeiten, die einzelnen Optionen genauer zu spezifizieren (etwa metavar oder type, vgl. Dokumentation [13]).
In der Zeile
options, args = parser.parse_args()
dieses Codeblocks wird der OptionParser mit parse_args() angewiesen, die Parameter, mit denen das Skript gestartet wurde, auszuwerten. Im Ergebnis werden die Optionen an den Namen options gebunden, die Argumente an den Namen args.
Ab der Zeile
if not (options.search or options.shuffle) ...
beginnt die Auswertung der Parameter. Durch diese und die danach folgenden drei Zeilen wird eine Fehlermeldung ausgegeben, wenn der Nutzer die Schalter -t oder -p übergeben hat, ohne mit --shuffle oder --find eine Auswahl von Musikstücken zu treffen. parser.error() gibt die übergebene Zeichenkette aus und beendet das Skript.
In der Zeile
with DatabaseConnector() as database:
kommt die oben implementierte Klasse DatabaseConnector zum Einsatz; sie wird im with-Block an den Namen database gebunden. In der nächsten Zeile wird geprüft, ob mit den Schaltern -s oder --scan ein Verzeichnis übergeben wurde. Ist dies der Fall, enthält options.add eine Pfadangabe als Zeichenkette und der Ausdruck options.add ist wahr. In diesem Fall wird das angegebene Verzeichnis mit der im letzten Teil umgesetzten Funktion read_recursively() ausgelesen. Die so gefundenen Titelinformationen werden mit der Methode insert_song() in die Datenbank geschrieben. Anschließend wird noch die Gesamtzahl der Titel in der Datenbank auf der Konsole ausgegeben.
Ab der Zeile
elif options.search or options.shuffle:
werden die Optionen --shuffle und -f bzw. --find behandelt. Im Fall einer Suche wird die Zeichenkette zunächst dekodiert und an den Namen searchterm gebunden. Die so erstellte Unicode-Zeichenkette kann dann an die Suchfunktion der Datenbank übergeben werden. Das Ergebnis wird an den Namen songs gebunden. Im Alternativfall ohne Suche wird eine Zufallsfunktion umgesetzt. Dazu werden zunächst mit get_all_songs() alle Lieder aus der Datenbank ausgelesen und zufällig angeordnet. Durch
songs = songs[:options.shuffle]
wird dann ein Ausschnitt dieser Gesamtliste an den Namen songs gebunden. Die Größe des Ausschnitts hat der Benutzer zusammen mit der Option --shuffle übergeben.
Dieses Vorgehen ist natürlich nicht besonders effizient: Wer 40.000 Lieder in seiner Datenbank hat, möchte sicher nicht, dass alle diese Lieder zunächst ausgelesen werden, nur um 100 zufällige Lieder davon auszuwählen. Sehr viel eleganter wäre hier eine Lösung via SQL:
SELECT * FROM mp3s ORDER BY RANDOM() LIMIT 20
Damit werden direkt 20 zufällige Lieder aus der Datenbank ausgelesen. Die im Skript umgesetzte Variante sollte in der Praxis also eher nicht eingesetzt werden. Sie besticht aber in diesem Fall dadurch, dass sie durch die Verwendung des random-Modules und die Nutzung von Slices Techniken einsetzt, die in den vorherigen Teilen bereits diskutiert wurden.
In den beiden if-Blöcken von options.search wurden die ausgewählten Lieder jeweils an den Namen songs gebunden. Für das weitere Vorgehen ist es nicht von Bedeutung, wie die Auswahl der Lieder zustande kam. Mit if songs: wird lediglich geprüft, ob überhaupt Lieder ausgewählt wurden (eine leere Liste wird als falsch ausgewertet). Nachfolgend wird eine Wiedergabeliste erstellt und gespeichert, falls die entsprechend Option options.playlist gesetzt wurde. Dabei wird der vom Nutzer angegebene Dateiname um .m3u erweitert, falls er nicht darauf endet. Am Ende kommt das subprocess-Modul zum Einsatz, wenn die Option -t bzw. --totem gesetzt wurde. Die Funktion Popen, die Totem ausführen soll, erwartet die Parameter in Form einer Liste. Diese wird initial mit totem und --enqueue befüllt und danach um die Pfadangaben der ausgewählten Musikstücke erweitert. So entsteht eine Liste nach dem Schema:
["totem", "--enqueue", "song1.mp3", "song2.mp3"]
Die entsprechende Befehlszeile in der Shell sähe wie folgt aus:
$ totem --enqueue song1.mp3 song2.mp3
Totem wird also mit der Option --enqueue gestartet, die alle folgenden Musikstücke an die Wiedergabeliste anhängt.
Schließlich deckt dieser Codeblock noch zwei weitere Eventualitäten ab: Der vorletzte else-Block ist von Bedeutung, wenn die Liste songs leer ist, also beispielsweise die Suche keine Treffer ergab. Das letzte else gibt einen Nutzungshinweis auf der Konsole aus, wenn weder die Optionen add, search noch shuffle gesetzt wurden.

Kleine Aufgabe

Mehrfach wurden kleine Fragestellungen oder Aufgaben erbeten, die die Leser bis zum Erscheinen des nächsten Teils zu lösen hätten. Im Abschnitt „Startbedingungen“ oben wird auf eine mögliche Alternative zur derzeitigen Shuffle-Funktion hingewiesen. Interessierte Leser könnten versuchen, die dort vorgeschlagenen Änderungen zu implementieren, indem sie die Klasse DatabaseConnector um eine shuffle()-Methode erweitern und das Skript so anpassen, dass diese Methode statt der jetzigen Variante zur Auswahl der Zufallstitel eingesetzt wird.
Wer sich darüber hinaus noch vertiefend mit dem Skript beschäftigen möchte, kann sich die Funktion generate_simple_playlist einmal näher ansehen. Der Einzeiler im Funktionsrumpf hat es bei näherer Betrachtung in sich, dort kommt ein sogenannter „Generator-Ausdruck“ zum Einsatz [14].

Schlusswort

Mit diesem sechsten Teil hat die einführende Vorstellung von Python eine kleine Zäsur erreicht. In Form der Musikdatenbank wurde mit Hilfe der vorgestellten Technologien und Werkzeuge erstmals ein (etwas) größeres Projekt in Angriff genommen, das zum Experimentieren und Erweitern einlädt.
Die Python-Reihe soll auch weiterhin fortgesetzt werden, allerdings wird sich der Abstand der einzelnen Teile etwas vergrößern und ab sofort voraussichtlich zweimonatlich erscheinen.
Links
[1] http://www.sqlite.org/
[2] http://docs.python.org/library/sqlite3.html
[3] http://docs.python.org/library/subprocess.html
[4] http://docs.python.org/library/optparse.html
[5] http://docs.python.org/library/codecs.html
[6] http://docs.python.org/library/textwrap.html#textwrap.dedent
[7] http://docs.python.org/library/random.html#random.shuffle
[8] http://effbot.org/zone/python-with-statement.htm
[9] http://docs.python.org/library/sqlite3.html#sqlite3.Cursor
[10] http://docs.python.org/library/os.path.html#module-os.path
[11] http://docs.python.org/library/sqlite3.html#sqlite3.Row
[12] http://www.sqlite.org/lang.html
[13] http://docs.python.org/library/optparse.html
[14] http://www.python.org/dev/peps/pep-0289/
Autoreninformation
Daniel Nögel (Webseite) beschäftigt sich seit drei Jahren mit Python. Ihn überzeugt besonders die intuitive Syntax und die Vielzahl der unterstützten Bibliotheken, die Python auf dem Linux-Desktop zu einem wahren Multitalent machen.
Diesen Artikel kommentieren

Zum Index

Impressum ISSN 1867-7991

freiesMagazin erscheint als PDF und HTML einmal monatlich.
Kontakt
E-Mail redaktion ETT freiesmagazin PUNKT de
Postanschrift freiesMagazin
c/o Dominik Wagenführ
Beethovenstr. 9/1
71277 Rutesheim
Webpräsenz http://www.freiesmagazin.de/
Erscheinungsdatum: 17. April 2011
Redaktion
Frank Brungräber Thorsten Schmidt
Dominik Wagenführ (Verantwortlicher Redakteur)
Satz und Layout
Ralf Damaschke Yannic Haupenthal
Nico Maikowski Matthias Sitte
Korrektur
Daniel Braun Stefan Fangmeier
Mathias Menzer Karsten Schuldt
Stephan Walter
Veranstaltungen
Ronny Fischer
Logo-Design
Arne Weinberg (GNU FDL)
Dieses Magazin wurde mit LaTeX erstellt. Mit vollem Namen gekennzeichnete Beiträge geben nicht notwendigerweise die Meinung der Redaktion wieder. Wenn Sie freiesMagazin ausdrucken möchten, dann denken Sie bitte an die Umwelt und drucken Sie nur im Notfall. Die Bäume werden es Ihnen danken. ;-)
Soweit nicht anders angegeben, stehen alle Artikel, Beiträge und Bilder in freiesMagazin unter der Creative-Commons-Lizenz CC-BY-SA 3.0 Unported. Das Copyright liegt beim jeweiligen Autor. freiesMagazin unterliegt als Gesamtwerk ebenso der Creative-Commons-Lizenz CC-BY-SA 3.0 Unported mit Ausnahme der Inhalte, die unter einer anderen Lizenz hierin veröffentlicht werden. Das Copyright liegt bei Dominik Wagenführ. Es wird erlaubt, das Werk/die Werke unter den Bestimmungen der Creative-Commons-Lizenz zu kopieren, zu verteilen und/oder zu modifizieren. Das freiesMagazin-Logo wurde von Arne Weinberg erstellt und unterliegt der GFDL. Die xkcd-Comics stehen separat unter der Creative-Commons-Lizenz CC-BY-NC 2.5 Generic. Das Copyright liegt bei Randall Munroe.
Zum Index




File translated from TEX by TTH, version 3.89.
On 8 May 2011, 20:03.