Elektrotechnik / Elektronik studieren an der Universität Rostock: http://www.elektrotechnik.uni-rostock.de/studium/

Instant Python

von Magnus Lie Hetland

[Wenn dir diese Anleitung gefällt, dann schau bitte auch mein Buch Beginning Python an, vielleicht wirst du auch mein Fan bei Facebook.]

Dies ist ein extrem knapper Schnellkurs über die Programmiersprache Python. Mehr kann man dann lernen, wenn man sich die Dokumentation auf den Python-Seiten www.python.org ansieht; besonders das Tutorial (hier in deutsch). Wenn du fragst, warum dich das überhaupt interessieren sollte, kannst du die Vergleichsseite anschauen; da wird Python mit anderen Sprachen verglichen.

Das Original dieser Einführung ist in verschiedene Sprachen übersetzt worden, darunter Portugiesisch, Italienisch, Spanisch, Russisch, Französisch, Litauisch, Japanisch, Deutsch und Griechisch, und wird gerade auch ins Norwegische, Polnische, and Koreanische übersetzt. Weil dieses Dokument öfter aktualisiert wird, sind die Übersetzungen evtl. nicht immer auf dem neuesten Stand.

Anmerkung: Damit die Beispiele richtig funktionieren, müssen sie in eine Textdatei geschrieben und dann mit dem Interpreter abgearbeitet werden; versuche also nicht, sie direkt im interaktiven Interpreter auszuführen - da würden nicht alle funktionieren. (Fragt mich bitte nicht nach Einzelheiten dazu! Ich werde mit E-Mails darüber zugeschüttet... Schaut dazu in die Dokumentation oder fragt per E-Mail bei help@python.org).

Die Grundlagen

Für den Anfang wollen wir so tun, als sei Python so etwas wie Pseudo-Code. Meist stimmt das sogar. Variablen haben keinen Typ, also braucht man sie auch nicht zu deklarieren. Sie existieren von dem Moment an, da ihnen etwas zugewiesen wird, und sie verschwinden wieder, wenn sie nicht mehr verwendet werden. Die Zuweisung geschieht mit dem Operator =. Gleichheit wird mit dem Operator == getestet. Man kann mit einer einzigen Anweisung unterschiedliche Zuweisungen an mehrere Variablen erledigen:

    x,y,z = 1,2,3

erster, zweiter = zweiter, erster

a = b = 123

Blöcke werden durch Einrückung angezeigt, und zwar nur durch Einrückung. (Es gibt also dafür keinerlei BEGIN/END oder Klammern.) Einige übliche Anweisungen zur Steuerung des Programmflusses sehen so aus:

    if x < 5 or (x > 10 and x < 20):

print "Der Wert ist OK."

if x < 5 or 10 < x < 20:
print "Der Wert ist OK."

for i in [1,2,3,4,5]:
print "Dies ist die Iteration Nummer", i


x = 10
while x >= 0:
print "x ist noch nicht negativ."
x = x-1

Die ersten beiden Beispiele sind gleichwertig.

Die Index-Variable in der for-Schleife läuft durch die Elemente einer Liste (und solche Listen werden mittels eckiger Klammern genau so notiert, wie oben im Beispiel gezeigt). Für eine "gewöhnliche" for-Schleife (also eine Zählschleife), benutzt man die eingebaute Funktion range().

    # Drucke die Werte von 0 bis einschliesslich 99.
    for wert in range(100):
        print wert
            

(Die Zeile, die mit  "#" beginnt, ist ein Kommentar und wird vom Interpreter ignoriert.)

So, jetzt wissen wir genug, um (theoretisch) jeden beliebigen Algorithmus in Python zu implementieren. Wir wollen nun ein paar elementare Dinge für einen Dialog mit dem Benutzer hinzufügen. Um eine Eingabe vom Benutzer entgegenzunehmen (mit einer Eingabeaufforderung), benutzen wir die eingebaute Funktion input.

    x = input("Bitte gib eine Zahl ein: ")
print "Das Quadrat dieser Zahl ist", x*x

Die Funktion input gibt die Eingabeaufforderung aus (die auch leer sein darf) und lässt den Benutzer einen gültigen Python-Wert eingeben. In diesem Fall haben wir eine Zahl erwartet – falls etwas anderes eingegeben wurde (z.B. so etwas wie eine Zeichenkette), würde das Programm abgebrochen. Um das zu vermeiden, brauchten wir irgend eine Art von Fehlertest. Ich werde das hier nicht vertiefen; man kann aber so viel sagen: Wenn man die Benutzereingabe wortwörtlich als Zeichenkette abspeichern möchte (so dass jede Eingabe zulässig ist), dann benutzt man anstelle input die Funktion raw_input. Wenn man dann die eingegebene Zeichenkette in eine Ganzzahl umwandeln möchte, könnte man int(s) verwenden.

Anmerkung: Wenn man mit input eine Zeichenkette einlesen möchte, dann muss der Benutzer die zur Zeichenkette gehörenden Anführungszeichen ausdrücklich selbst dazuschreiben. In Python können Zeichenketten nach Belieben entweder in einfache oder in doppelte Anführungszeichen eingeschlossen sein.

So, nachdem wir uns nun Steueranweisungen, Eingaben und Ausgaben angeschaut haben, brauchen wir noch ein paar schicke Datenstrukturen. Die wichtigsten sind Listen und Wörterbücher. Listen werden in eckigen Klammern notiert und können (selbstverständlich) geschachtelt werden:

    name = ["Cleese", "John"]

x = [[1,2,3],[y,z],[[[]]]]

Eine schöne Eigenschaft von Listen ist, dass man auf ihre Elemente einzeln oder auch in Gruppen zugreifen kann; das funktioniert mittels Indizierens und Ausschneidens (englisch: "slicing"). Indizierung funktioniert (wie in vielen anderen Sprachen) so, dass in eckigen Klammern der Index an den Listennamen angehängt wird. (Das erste Listenelement hat den Index 0.)

    print name[1], name[0] # Druckt "John Cleese"

    name[0] = "Smith"
            

Ausschneiden ist fast so wie indizieren, nur nennt man hier sowohl den Startindex als auch den Stoppindex für das Ergebnis des Zugriffs auf die Liste, wobei die beiden Indizes durch einen Doppelpunkt (":") getrennt sind:

    x = ["spam","spam","spam","spam","spam","eier","und","spam"]

print x[5:7] # Druckt die Liste ["eier","und"]

Wie wir sehen, ist das zum Endindex gehörende Element in der Ergebnisliste nicht enthalten. Falls einer der Indizes weggelassen ist, wird angenommen, dass man in dieser Richtung auf den gesamten Rest der Liste zugreifen möchte. Also bedeutet list[:3], dass man auf "jedes Element vom Anfang an bis ausschließlich Element 3" zugreifen will.  (Man könnte auch sagen, dass man eigentlich erst ab dem vierten Element nicht übernimmt, weil die Zählung ja mit dem Index 0 beginnt ... ja, das ist so). list[3:] bedeutet dagegen, "jedes Element aus  list, angefangen mit (einschließlich) Element 3 bis einschließlich das letzte." Für richtig interessante Ergebnisse kann man als Index auch negative Zahlen verwenden: list[-3] ist das dritte Element vom Ende der Liste...

Da wir gerade beim Indizieren sind, ist es interessant zu wissen, dass es eine eingebaute Funktion len gibt, die die Länge einer Liste zurückgibt.

Und nun – was gibt es über Wörterbücher zu sagen? Um es einfach zu machen, sie sind so ähnlich wie Listen, nur dass ihr Inhalt nicht der Reihe nach geordnet ist. Wie wird dann aber darauf zugegriffen? Nun, jedes Element hat einen Schlüssel, oder einen "Namen", der benutzt wird, um auf dieses Element zuzugreifen, genau wie in einem richtigen Wörterbuch. Hier ein paar Beispiele für Wörterbücher:

    { "Alice" : 23452532, "Boris" : 252336,

"Clarice" : 2352525, "Doris" : 23624643}

person = { 'Vorname': "Robin", 'Nachname': "Hood",
'Taetigkeit': "Halunke" }

Um nun die Tätigkeit der  person zu erhalten, verwenden wir den Ausdruck person["Taetigkeit"]. Falls wir ihren Nachnamen ändern wollen, können wir schreiben:

    person['Nachname'] = "of Locksley"

Einfach, nicht wahr? So wie Listen können auch Wörterbücher andere Wörterbücher enthalten. Oder Listen, falls nötig. Und natürlich können Listen auch Wörterbücher enthalten. Auf die Weise kann man leicht ein paar ziemlich raffinierte Datenstrukturen erzeugen.

Funktionen

Nächster Schritt: Abstraktion. Wir möchten einem Codestück einen Namen geben und dann mit ein paar Parametern aufrufen. Mit anderen Worten – wir wollen eine Funktion (oder "Prozedur") definieren. Das ist einfach. Dazu benutzen wir das Schlüsselwort def so:

    def quadrat(x):
return x*x

print quadrat(2) # Druckt 4

Für die, die es verstehen: Wenn man einen Parameter an eine Funktion übergibt, dann übergibt man in Wirklichkeit nur den mit dem Parameter verbundenen Wert und erzeugt so einen neuen Bezug auf diesen Wert – von innerhalb der Funktion. Wenn man den "Inhalt" dieses neuen Parameternamens ändert (d.h. neu bindet), dann hat das keine Auswirkung auf das Original. Das funktioniert z.B. in Java auch so. Wir wollen uns ein Beispiel ansehen:

    def aendere(eine_liste):
eine_liste[1] = 4

x = [1,2,3]
aendere(x)
print x # Druckt [1,4,3]

Wie man sieht, wird hier (als Wert, der zum Listennamen gehört) die Originaladresse der Liste übergeben, und wenn die Funktion diese Adresse (unter dem lokalen Namen "eine_liste") benutzt und den Inhalt der zugehörigen Liste verändert, dann erscheint diese Änderung auch an der Stelle, von wo aus die Funktion aufgerufen wurde. Nun betrachten wir aber das Verhalten im folgenden Beispiel:

    def nichtaendern(x):
x = 0

y = 1
nichtaendern(y)
print y # Druckt 1

Wieso ist jetzt nichts geändert? Weil wir den ursprünglichen Wert nicht ändern! Der Wert, der hier übergeben wurde, ist der Wert der Zahl 1 — es macht keinen Sinn, den Wert der Zahl 1 abzuändern. Die Zahl 1 ist (und soll auch immer sein) die Zahl 1. Was wir getan haben, ist, den Wert der lokalen Variablen (des Parameters) x zu ändern, und das wirkt nicht zurück auf die äußere Umgebung der Funktion. (Wieso dann aber im obigen Fall des geänderten Listenelements? Nochmal: Auch dort wurde nicht der übergebene Wert – die Listenadresse selbst – geändert, sondern diese Listenadresse wurde unverändert benutzt, um auf ein Element der Originalliste zuzugreifen und das zu ändern. Am einfachsten stellt man sich das so vor, dass die aufgerufene Funktion immer nur Kopien der übergebenen Werte der Parameter übernimmt, beim Verlassen der Funktion die Parameter der Aufrufliste aber niemals zurückkopiert werden.)

Für diejenigen, die das nicht verstanden haben: Keine Sorge — es ist gar nicht so schwer, wenn man nicht zu viel darüber nachzudenkt :)

Python hat alle Arten von Rafinessen wie benannte Argumente und Standardargumente und kann auch mit einer variablen Anzahl von Argumenten für eine Funktion umgehen. Mehr Informationen darüber findet man im Python Tutorial Abschnitt 4.7.

Wenn wir wissen, wie Funktionen üblicherweise benutzt werden, dann wissen wir das, was man in Python im Wesentlichen darüber wissen muss. (Ach ja... Das Schlüsselwort return beendet die Ausführung der Funktion und gibt den ermittelten Wert zurück.)

Aber hier noch eine Sache, die nützlich sein kann: in Python sind Funktionen auch Werte. Wenn man also eine Funktion wie quadrat hat, dann kann man Sachen machen wie:

    blabla = quadrat
print blabla(2) # Druckt 4

Um eine Funktion ohne Argumente aufzurufen, darf man nicht vergessen, das als tuwas() zu schreiben, und nicht als tuwas. Das letztere gibt, wie eben gezeigt, nur die Funktion selbst als Wert zurück. (Und das gilt so auch für Methoden in Objekten... siehe unten.)

Objekte und was dazu gehört...

Ich nehme an, dass du weißt, wie objektorientierte Programmierung funktioniert. (Andernfalls würde dieser Abschnitt nicht viel Sinn machen. Kein Problem... Fang einfach ohne die Objekte an :).) In Python definiert man Klassen mit dem Schlüsselwort (Überraschung!)  class, etwa so:

    class Korb:

# Immer an das Argument *self* denken
def __init__(self,enthaelt=None):
self.enthaelt = enthaelt or []

def hinzufuegen(self,element):
self.enthaelt.append(element)

def druck_mich(self):
ergebnis = ""
for element in self.enthaelt:
ergebnis = ergebnis + " " + `element`
print "Enthaelt:"+ergebnis

Hier ist neu:

  1. Alle Methoden (d.h. Funktionen innerhalb von Objekten) erhalten am Anfang der Parameterliste einen zusätzlichen Parameter, der das Objekt selbst enthält. (In diesem Beispiel als self bezeichnet, was sich so eingebürgert hat.)
  2. Methoden werden so aufgerufen: objekt.methode(arg1,arg2).
  3. Einige Methodennamen wie __init__ sind vordefiniert und haben bestimmte Bedeutungen. __init__ ist der Name des Konstruktors der Klasse, d.h. es ist eine Funktion, die aufgerufen wird, wenn man eine Instanz erzeugt.
  4. Einige Argumente können wahlfrei sein und haben dafür einen Standardwert (wie weiter oben im Abschnitt über die Funktionen erwähnt). Das wird erreicht, indem man die Definition so schreibt: 
            def spam(alter=32): ...
    Hier kann spam mit einem oder mit null Argumenten aufgerufen werden. Wenn kein Argument benutzt wird, hat der Parameter alter den Wert 32.
  5. "Kurzschlusslogik." Das ist ziemlich pfiffig ... siehe unten.
  6. Rückwärtsapostrophs wandeln ein Objekt in seine Zeichenkettendarstellung um. (Wenn also element den Wert 1 hat, dann ist `element` das gleiche wie "1" wohingegen 'element' eine gewöhnliche Zeichenkette mit sechs Buchstaben ist.)
  7. Das Additionszeichen + wird auch zum Aneinanderketten von Listen benutzt, und Zeichenketten sind in Wirklichkeit einfach Listen von Zeichen (was bedeutet, dass man Indexierung und Ausschneiden und die Funktion len auf sie anwenden kann. Stark, oder?)

In Python sind Methoden oder Attribute nicht zugriffsgeschützt (oder privat oder so ähnlich). Verkapselung ist vielmehr eine Sache des Programmierstils. (Falls man es  wirklich braucht, gibt es Namenskonventionen, die eine gewisse Privatheit erlauben :)).

Nun zur Kurzschlusslogik...

Alle Werte in Python können als logische Werte verwendet werden. Solche, die sowieso schon "leer" aussehen wie [], 0, "" und None bedeuten das logische "falsch", während die meisten anderen Werte (wie [0], 1 oder "Hallo Welt") logisch "wahr" bedeuten.

Logische Ausdrücke wie  a and b werden so ausgewertet: Zuerst wird getestet, ob a "wahr" ist. Falls es nicht wahr ist, dann wird es einfach gleich als Ergebnis zurückgegeben. Wenn es aber wahr ist, dann wird entsprechend einfach b zurückgegeben (das dann ja den Wahrheitswert des Ausdrucks repräsentiert). Die entsprechende Logik für a or b ist: Falls a wahr ist, dann gib es zurück. Falls nicht, dann gib b zurück.

Durch diesen Mechanismus wird erreicht, dass sich die logischen Operatoren so verhalten, wie man es von ihnen erwartet, und außerdem kann man kurze und nette kleine bedingte Ausdrücke schreiben. Z.B. kann anstatt des Ausdrucks

    if a:
print a
else:
print b

geschrieben werden:

    print a or b

Das ist wirklich so etwas wie ein idiomatischer Ausdruck in Python, also kann man sich auch gleich daran gewöhnen. Wir verwenden das auch schon in der Methode Korb.__init__. Das Argument enthaelt bekommt als Standardwert None (was u.a. "falsch" bedeutet). Um zu testen, ob das Argument mit einem Wert belegt ist, könnte man schreiben:

    if enthaelt:
self.enthaelt = enthaelt
else:
self.enthaelt = []

Freilich wissen wir jetzt, dass es dafür einen besseren Weg gibt. Und warum weisen wir nicht von Anfang an schon in der Parameterliste den Wert [] zu? Weil wegen der Funktionsweise von Python sich dadurch sämtliche Körbe den Besitz einer und derselben leeren Liste teilen würden. Sobald einer dann seine Liste füllt, würden auch alle anderen Körbe diese Elemente enthalten, und ebenso wäre dann auch der Standardwert nicht mehr leer. Um mehr darüber zu erfahren, kannst du die Dokumentation lesen und nach dem Unterschied zwischen Identität und Gleichheit suchen.

Eine weitere Möglichkeit, die obige Aufgabe zu erledigen, ist:

    def __init__(self, enthaelt=[]):
self.enthaelt = enthaelt[:]

Siehst du, wie das funktioniert? Anstatt dieselbe leere Liste überall zu verwenden, benutzen wir den Ausdruck contents[:]zur Herstellung einer Kopie. (Wir schneiden einfach das Ding als ganzes aus, vom ersten bis zum letzten Index.)

Um also nun wirklich einen Korb zu erzeugen und zu benutzen (d.h. einige seiner Methoden zu verwenden) könnten wir etwa folgendes tun:

    k = Korb(['apfel','orange'])
k.hinzufuegen("zitrone")
k.druck_mich()

Es gibt außer __init__ noch andere spezielle Methoden. Eine solche Methode ist __str__, die definiert, wie das Objekt aussehen möchte, wenn es als Zeichenkette behandelt wird. Wir könnten das in unserem Korb anstelle von druck_mich verwenden:

    def __str__(self):
ergebnis = ""
for element in self.enthaelt:
ergebnis = ergebnis + " " + `element`
return "Enthaelt:"+ergebnis

Wenn wir jetzt den Korb k drucken wollen, können wir einfach schreiben:

    print k

Stark, oder?

Unterklassen bildet man so:

    class BrotKorb(Korb):
# ...

Python erlaubt Mehrfachvererbung, man darf also mehrere Oberklassen in der Klammer angeben, die dann durch Kommata getrennt sind. Instanzen von Klassen erzeugt man so: x = Korb(). Konstruktoren werden wie gesagt durch die spezielle Methode __init__ ermöglicht. Angenommen, BrotKorb hätte einen Konstruktor __init__(self,type). Dann könnte man einen Brotkorb so erzeugen: y = BrotKorb("Semmeln").

Falls man im Konstruktor von BrotKorb den Konstruktor einer oder mehrerer Oberklassen aufrufen müsste, könnte man das so machen: Korb.__init__(self). Hier muss man zusätzlich zu gewöhnlichen Parametern auch noch ausdrücklich self übergeben, weil das  __init__ der Oberklasse nicht wissen kann, mit welcher Instanz es zu tun hat.

Wer mehr über die Wunder der objektorientierten Programmierung in Python wissen möchte, kann im Tutorial in Abschnitt 9 nachschauen.

Gehirnakrobatik nach Art der Jedi-Ritter

Anmerkung: Dieser Abschnitt ist eigentlich etwas veraltet; er ist nur hier, weil ich ich die Sache gut finde. Man braucht ihn definitiv nicht zu lesen, wenn man anfangen will, Python zu lernen. Am Ende des Abschnittes steht noch etwas über Änderungen für Python 2.1.

Magst du Gehirnakrobatik? Wenn du dich wirklich traust, könntest du dich an Guido van Rossums Aufsatz über Metaclassen heranwagen. Wenn du dir das Gehirn aber erst mal lieber noch nicht verrenken willst, bist du vielleicht schon mit diesem kleinen Trick zufrieden.

Python benutzt dynamische Namensräume im Gegensatz zu lexikalischen Namensräumen. Das bedeutet, wenn wir eine Funktion wie diese haben:

    def orangensaft():
return x*2

... wo eine Variable (in diesem Fall x) nicht an ein Argument gebunden ist und innerhalb der Funktion keinen Wert zugewiesen bekommt, dann wird Python den Wert verwenden, der an derjenigen Stelle vorhanden ist, wo die Funktion aufgerufen wird. in diesem Fall:

    x = 3
y = orangensaft()
# y ist jetzt 6 x = 1 y = orangensaft() # y ist jetzt 2

Gewöhnlich ist das die Art Verhalten, die man so auch haben möchte (obwohl das obige Beispiel etwas künstlich ist  – man wird auf Variablen nur selten auf diese Weise zugreifen.) Manchmal kann es dagegen aber angenehm sein, so etwas wie einen statischen Namensraum zu haben, d.h. einen Wert aus der Umgebung der Funktion zu speichern, wenn sie erzeugt wird. In Python kann man das machen, indem man ein Standardargument als Zwischenspeicher nimmt.

    x = 4
def apfelsaft(x=x):
return x*2

Hier erhält das Argument x einen Standardwert, der der gleiche ist, wie der Wert der Variablen x in dem Moment, wo die Funktion definiert wird. So lange die Funktion ohne Argument aufgerufen wird, hat man dieses Verhalten:

    x = 3
y = apfelsaft()
# y ist jetzt 8 x = 1 y = apfelsaft() # y ist jetzt 8

Auf diese Weise wird der Wert von x also nicht geändert. Wenn wir nichts anderes wollten, hätten wir auch schreiben können

    def tomatensaft():
x = 4
return x*2

oder sogar

    def moehrensaft():
return 8

Der Knackpunkt ist dabei, dass der Wert von x zu der Zeit aus der Umgebung der Funktion gewonnen wurde, als die Funktion definiert wurde. Was haben wir davon? Nehmen wir ein Beispiel – eine Funkton, die zwei andere Funktionen zusammenfasst.

Wir möchten eine Funktion, die so arbeitet:

    from math import sin, cos

sincos = compose(sin,cos)

x = sincos(3)

Hier ist compose die Funkton, die wir ausführen wollen, und x hat den Wert -0.836021861538, was das gleiche ist wie sin(cos(3)). Also, wie machen wir das?

(Beachte, dass wir hier Funktionen als Argumente benutzen... Das allein ist schon ein netter Trick.)

Klar ist, dass compose zwei Funktionen als Parameter übergeben bekommt und eine Funktion zurückgibt, die auch wieder einen Parameter erhält. Ein erstes Gerüst für eine Lösung könnte also sein:

    def compose(fun1, fun2):
def inner(x):
pass # ... return inner

Wir könnten versucht sein, return fun1(fun2(x)) in die innere Funktion inner hineinzuschreiben und es dabei zu belassen. Nein, nein, nein. Das ergäbe ein sehr befremdliches Verhalten. Stellen wir uns den Schauplatz so vor:

    from math import sin, cos

# Falsche Version def compose(fun1, fun2): def inner(x): return fun1(fun2(x)) return inner def fun1(x): return x + " world!" def fun2(x): return "Hello," sincos = compose(sin,cos) # Die falsche Version benutzen x = sincos(3)

Welchen Wert würde x jetzt haben? Richtig: "Hello, world". Warum? Weil die Funktion beim Aufruf die Werte für fun1 und fun2 aus der augenblicklichen Umgebung nimmt, und nicht von da, wo die Funktion erzeugt wurde. Um eine funktionierende Lösung zu bekommen, brauchen wir nur das zu tun, was ich weiter oben schon beschrieben habe:

    def compose(fun1, fun2):
def inner(x, fun1=fun1, fun2=fun2):
return fun1(fun2(x))
return inner

Jetzt müssen wir nur noch hoffen, dass niemand die Funktion mit mehr als einem Argument aufruft, weil das nicht funktionieren würde :). Und übrigens, weil wir den Namen inner eigentlich gar nicht brauchen und er auch nur einen Ausdruck enthält, können wir genausogut eine anonyme Funktion verwenden, indem wir das Schlüsselwort lambda benutzen:

    def compose(f1, f2):
return lambda x, f1=f1, f2=f2: f1(f2(x))

Knapp, aber klar. Du wirst es lieben :)

(Und falls du von all dem überhaupt nichts verstanden hast, keine Sorge. Zumindest hoffe ich dich überzeugt zu haben, dass Python mehr ist als "nur eine Skriptsprache"... :))

Eine Bemerkung über Python 2.1 und geschachtelte Gültigkeitsbereiche

Mit dem Erscheinen von Python 2.1 hat die Sprache jetzt (wahlfrei) statisch geschachtelte Gültigkeitsbereiche oder Namensbereiche. Das heißt, man kann all die in diesem Abschnitt beschriebenen Sachen ohne orgendwelche Tricksereien machen. Jetzt kann man einfach das folgende schreiben:

    # In Python 2.2 wird das nicht mehr nötig sein: 
    from __future__ import nested_scopes

    def compose(fun1, fun2):
        def inner(x):
            return fun1(fun2(x))
        return inner
            

... und es wird funktionieren wie es soll.

Und Jetzt...

Nur ein paar Sachen noch zum Schluss. Sehr nützliche Funktionen und Klassen befinden sich in Modulen, die in Wirklichkeit Textdateien mit Python-Code sind. Man kann die importieren und in den eigenen Programmen benutzen. Um z.B. die Methode split aus dem Modul string zu benutzen, kann man entweder so vorgehen:

    import string

x = string.split(y)

Oder so...

    from string import split

x = split(y)

Für mehr Information über die Module der Standardbibliothek kann man einen Blick auf www.python.org/doc/lib werfen. Dort findet sich eine Menge nützlichen Materials.

Der gesamte Code im Modul/Skript wird abgearbeitet, wenn es importiert wird. Falls man möchte, dass das eigene Programm sowohl ein importierbares Modul als auch ein lauffähiges Programm ist, kann man etwa so etwas ans Ende schreiben:

    if __name__ == "__main__": go()

Das ist eine spezielle Möglichkeit, zu sagen, dass, wenn dieses Modul als ausführbares Skript abgearbeitet wird (also nicht in ein anderes Skript importiert wird), die Funktion go aufgerufen werden soll. Natürlich kann man nach dem Doppelpunkt auch irgendetwas anderes machen... :)

Und diejenigen, die ein ausführbares Skript unter UN*X machen möchten, können das folgende als erste Zeile schreiben, damit das Skript direkt abgearbeitet wird:

    #!/usr/bin/env python
            

Und am Schluss noch die kurze Erwähnung eines wichtigen Konzeptes: Ausnahmen. Einige Operationen (wie die Division durch null oder das Lesen von einer nicht existierenden Datei) führen zu Fehlerbedingungen oder Ausnahmen. Man kann sogar eigene Ausnahmen definieren und sie im entsprechenden Zeitpunkt aktivieren.

Wenn auf eine Ausnahme nicht reagiert wird, endet das Programm und druckt eine Fehlermeldung aus. Vermeiden kann man das mit einer try/except-Anweisung. Z.B.:

    def sichere_division(a,b):
try:
return a/b
except ZeroDivisionError:
return None

ZeroDivisionError ist eine Standardausnahme. In diesem Fall hätte man zwar selbst vorher testen können, ob b Null ist, aber in vielen anderen Fällen ist das nicht möglich. Und außerdem, falls wir keine try-Klausel in safe_division haben, die die riskante Funktion aufrufen kann, können wir immer noch so etwas machen:

    try:
unsichere_division(a,b)
except ZeroDivisionError:
print "In unsichere_division wurde etwas durch null geteilt"

In Fällen, wo man normalerweise kein bestimmtes Problem hat, aber eines auftauchen könnte, können mittels Ausnahmen teure Tests usw. vermieden werden.

So, das ist es. Hoffentlich hast du einiges gelernt. Nun fang an und spiele damit. Und denke an das Python-Motto zum Lernen: "Nutze die Quelle, Luke." (Übersetzung: Lies allen Code, den du in die Hände bekommen kannst :)) Um dir beim Start zu helfen ist hier ein Beispiel. Es ist Hoares bekannter QuickSort-Algorithmus. Eine Version mit eingefärbter Syntax ist hier zu finden.

Eine Sache in diesem Beispiel ist erwähnenswert. Die Variable done steuert, ob partition komplett über alle Elemente erledigt wurde. Wenn also eine der zwei inneren Schleifen das gesamte Austauschverfahren beenden möchte, setzt sie done auf 1 und beendet sich dann mittels break. Warum benutzen die inneren Schleifen done? Weil dann, wenn die erste innere Schleife mit einem  break endet, der Start einer nächsten Schleife davon abhängt, ob die Hauptschleife beendet ist oder nicht, d.h. ob  done auf 1 gesetzt wurde, oder nicht:

    while not done:
while not done:
# Iteriert bis zu einem break while not done: # Wird nur ausgefuehrt, wenn die erste Schleife "done" nicht auf 1 gesetzt hat

Ein Äquivalent, das vielleicht etwas klarer, nach meiner Meinung aber nicht so schön ist, kann so aussehen:

    while not done:
while 1:
# Iteriert bis zu einem break if not done: while 1: # Wird nur ausgefuehrt, wenn die erste Schleife "done" nicht auf 1 gesetzt hat

Der einzige Grund dafür, dass ich die Variable done in der ersten Schleife benutzt habe, war, dass ich gern die Symmetrie zwischen den beiden Schleifen beibehalten wollte. Auf die Weise könnte man ihre Reihenfolge ändern, und der Algorithmus würde immer noch funktionieren.

Einige weitere Beispiele finden sich auf Joe Strouts Seite tidbit.


Copyright © Magnus Lie Hetland

Deutsche Übersetzung: Hartmut Pfüller (letzte Änderung: 14. Oktober 2009)


Zurück zu Hartmut Pfüllers Heimseite