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() .
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]
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]
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)
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
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
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)
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:
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:
- 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.)
- Methoden werden so aufgerufen:
objekt.methode(arg1,arg2) .
- 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.
- 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.
- "Kurzschlusslogik." Das ist ziemlich pfiffig ... siehe
unten.
- 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.)
- 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()
x = 1
y = orangensaft()
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()
x = 1
y = apfelsaft()
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
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)
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:
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:
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:
while not done:
Ein Äquivalent, das vielleicht etwas klarer, nach meiner Meinung
aber nicht so schön ist, kann so aussehen:
while not done: while 1:
if not done:
while 1:
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: 5. Oktober 2014)
|