3 3.1 Funktionen Funktionen erstellen mit def Unterprogramme werden in Python normalerweise mit dem Schlüsselwort def definiert.1 Die folgende Funktion berechnet die Summe aller Zahlen in einer Sequenz. (Ich verrate Ihnen nicht, dass Sie mit dieser Funktion auch in einer Sequenz gespeicherte Zeichenketten hintereinander hängen können. ;−) Allerdings geht das mit Pythons »Bordmitteln« viel eleganter.) def seq_sum(sequence): '''Calculate the sum of all elements in a sequence.''' sum = 0 for item in sequence: sum += item # ab Python 2.0, davor: sum = sum + item return sum Das Unterprogramm wird eingeleitet mit dem Schlüsselwort def, das im Allgemeinen ganz links in der Zeile steht. (Die häufigste Ausnahme ist die Definition von Methoden in einer Klasse, dazu mehr im Kapitel 6.) Danach folgt der Name und schließlich – in Klammern – eine Parameterliste. Hier enthält sie nur ein Argument. Wichtig ist der Doppelpunkt, der den Funktionsrumpf einleitet. Danach wird um eine Ebene eingerückt. Die in der nächsten Zeile folgende Zeichenkette ist ein sog. »Docstring« (kurz für »Documentation string«). Hier sollten Sie möglichst allgemein beschreiben, was die Funktion tut, aber nicht, wie sie das macht. Der Unterschied zwischen einem Docstring und einem mit # eingeleiteten Kommentar besteht darin, dass Sie den Docstring in einem Programm abfragen können. >>> def seq_sum(sequence): ... '''Calculate the sum of all elements in a sequence.''' ... sum = 0 ... for item in sequence: ... sum += item ... return sum ... >>> print seq_sum.__doc__ Calculate the sum of all elements in a sequence. 1 Einfache Funktionen kann man mit dem Schlüsselwort lambda erstellen; ich komme darauf zurück. 47 3 Funktionen Das Durchlaufen der Schleife über die Sequenz ist nichts Neues, wohl aber die return-Anweisung am Ende der Funktion. Hiermit geben Sie den aufsummierten Wert an den Aufrufer zurück. Ein Beispiel: >>> seq_sum( (1, −3, −2.1, 9) ) 4.9000000000000004 Sie mögen sich über den krummen Wert wundern. Dieser kommt dadurch zustande, dass sich das Ergebnis 4.9 nicht exakt im binären System darstellen lässt. 4.9000000000000004 ist die bestmögliche Näherung bei der von Python verwendeten Zahl von Dezimalstellen. Nehmen Sie sich bei Verwendung von Fließkommazahlen vor Rundungsfehlern in Acht. Eine Kostprobe: >>> sum = 0.0 >>> for i in range(10000): ... sum += 0.2 ... >>> sum 2000.0000000003176 >>> 10000 * 0.2 2000.0 Bevorzugen Sie im Zusammenhang mit Gleitkommazahlen »direkte« Rechnungen mit möglichst wenigen Zwischenergebnissen. Damit können Sie Abweichungen verringern, wenn auch nicht ganz vermeiden. Übrigens hat es meistens keinen Sinn, zwei Fließkommazahlen auf Gleichheit zu testen, auch, wenn sie »eigentlich« gleich sein müssten (s. o.). Wenn Sie einen solchen Test als Abbruchbedingung einer Schleife benutzen, bekommen Sie eine Endlosschleife. 3.2 Vorgabeargumente Sie können einen oder mehrere der Parameter in der Parameterliste mit Standardwerten vorbelegen. Das heißt, wenn Sie für die betreffenden Parameter beim Aufruf der Funktion keine Werte angeben, werden die Standardvorgaben benutzt. Das folgende Beispiel sucht ein gegebenes Element in einer Sequenz und liefert dessen Index zurück. Außerdem können Sie einen Startund einen Endindex angeben, zwischen denen gesucht werden soll. Die obere Grenze soll sich konform zum Spleißen und zur rangeFunktion verhalten, d. h. der Grenzwert selbst gehört nicht zum Bereich. 001 >>> def index(sequenz, gesucht, start=0, ende=None): 002 ... '''Gib den ersten Index fuer den gesuchten Wert.''' 48 Vorgabeargumente 003 004 005 006 007 ... ... ... ... ... if ende is None: ende = len(sequenz) for position in range(start, ende): if sequenz[position] == gesucht: return position Aufgerufen wird diese Funktion bespielsweise so: >>> >>> 1 >>> 5 >>> 1 >>> >>> >>> T = (1, 'Python', −2, 7, 9, 'Python', −3) index(T, 'Python') # durchsuche ganzes Tupel index(T, 'Python', 2) # Start bei Wert −2 im Tupel index(T, 'Python', 1, 4) # Ende der Suche bei Wert 7 index(T, 'Python', 2, 4) index(T, 5, 2) # kein Treffer # dito In Zeile 1 der Funktionsdefinition werden die möglichen Parameter der Funktion angegeben. Als Erstes wird die Sequenz, dann der gesuchte Wert übergeben. Danach folgen bis zu zwei weitere Argumente für den Bereich, in dem gesucht werden soll. Der Vorgabewert für start leuchtet ein; die Suche beginnt am Anfang der Sequenz. Für den Parameter ende ist der Standardwert nicht so klar festzulegen. Es wäre nahe liegend, ende=len(sequenz) zu schreiben. Der Versuch: >>> def index(sequenz, gesucht, start=0, ende=len(sequenz)): ... '''Gib den ersten Index fuer den gesuchten Wert.''' ... for position in range(start, ende): ... if sequenz[position] == gesucht: ... return position ... Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'sequenz' is not defined Dieser Weg funktioniert also nicht, weil der Bezeichner sequenz in der Kopfzeile noch nicht bekannt bzw. nicht auf der rechten Seite des Gleichheitszeichens verwendbar ist. Daher verwenden Sie den speziellen Wert None. Wenn Sie im Unterprogramm feststellen, dass ende identisch mit None ist, wissen Sie, dass beim Aufruf kein Wert angegeben wurde. Sie verwenden also einen Standardwert, um ggf. den »eigentlichen« Standardwert zu ermitteln. 49 3 Funktionen Wenn Sie eine Funktion mit oder ohne einen Parameter aufrufen wollen, ist der Wert None als Vorgabewert oft eine gute Wahl, weil er nahe legt, dass im Aufruf kein Wert angegeben wurde. Hätten Sie z. B. ende=−1 geschrieben, könnte ein flüchtiger Leser Ihrer Funktion denken, Sie wollen nur bis zum vorletzten Element suchen. (Es mag Situationen geben, wo dies nicht abwegig ist.) Manchmal gibt es allerdings auch Situationen, wo None als Parameter in einen Funktionsaufruf gehört, aber etwas anderes auslösen soll, als Sie mit dem Fehlen des Parameters verbinden würden. Zurück zur Funktion. Die for-Schleife in den Zeilen 5 bis 7 untersucht jedes Element im – ggf. eingeschränkten – Bereich darauf, ob es gleich dem gesuchten ist. Wird die gesuchte Komponente gefunden, wird ihr Index an den Aufrufer zurückgegeben. Sie können mit return übrigens beliebige Arten von Objekten zurückgeben: Zahlen, Listen, Klassen, Klassenobjekte usw. Aber was passiert, wenn das gesuchte Element nicht in der Sequenz, bzw. dem durchsuchten Teil davon, vorhanden ist? Wenn Sie eine Funktion nicht explizit mit return verlassen, läuft sie bis zum Ende durch. Dann wird sie beendet, wobei der Wert None an den Aufrufer zurückgeliefert wird. Vorgabeargumente sind vor allem aus zwei Gründen nützlich. Der offensichtliche ist, dass Sie Tipparbeit sparen, wenn Sie in den meisten Funktionsaufrufen ohnehin die Standardwerte wollen. Der zweite Grund ist, dass Sie einer Funktion einen zusätzlichen Parameter hinzufügen können, ohne alle bisher geschriebenen Funktionsaufrufe ergänzen zu müssen. (Manchmal kann es aber auch sinnvoller sein, eine neue Funktion mit einem anderen Namen zu definieren als ein Vorgabeargument einzuführen.) 3.3 Benannte Argumente »Normalerweise« werden beim Aufruf einer Funktion die Variablen der Parameterliste in derselben Reihenfolge belegt, wie die Funktionswerte im Aufruf angegeben werden. Mit der oben angegebenen Definition der Funktion index bewirkt der Aufruf index(T, 'Python', 2) die impliziten Zuweisungen sequenz=T, gesucht='Python' und start=2. Sie können die Parameter im Aufruf aber auch in einer anderen Reihenfolge angeben, wenn Sie wissen, wie die formalen Parameter in 50 Funktionen definieren mit lambda der Funktionsdefinition heißen. Die folgenden Aufrufe sind gleichbedeutend: >>> index(T, 'Python', 2) # s. o. 5 >>> index(gesucht='Python', start=2, sequenz=T) 5 (In der englischen Terminilogie zu Python werden benannte Argumente normalerweise als »keyword arguments« bezeichnet, was mitunter mit »Schlüsselwortargumente« übersetzt wird.) 3.4 Funktionen definieren mit lambda Die lambda-Anweisung ist eine seltenere Art, eine Funktion zu definieren. Als ein Beispiel betrachten Sie eine Funktion, um Linien beliebiger Länge auszugeben: def linie(laenge=5): '''Gib eine Linie, die aus Strichen besteht.''' return laenge * '−' (Wie Sie sehen, können Sie den Multiplikationsoperator auch auf eine Kombination von Ganzzahl und Zeichenkette anwenden.) Dieselbe Funktion (bis auf den Docstring) erzeugen Sie mit rule = lambda laenge=5: laenge * '−' Dies ist etwas kürzer. Allerdings ist das nicht die Hauptanwendung der lambda-Anweisung. Ihr Vorteil ist nämlich, dass Sie damit anonyme Funktionen »an Ort und Stelle« (in-place) definieren können. (Und Sie sollten sie am besten auch nur dafür verwenden!) Wollen Sie beispielsweise aus einer Liste von Zahlen nur die positiven haben, schreiben Sie >>> zahlen = [23, −1, 2, 0, −3, 99] >>> positive = filter(lambda x: x > 0, zahlen) >>> positive [23, 2, 99] Ohne die lambda-Anweisung hätten Sie dafür schreiben müssen: >>> zahlen = [23, −1, 2, 0, −3, 99] >>> def ist_positiv(x): ... return x > 0 ... >>> positive = filter(ist_positiv, zahlen) >>> positive [23, 2, 99] 51 3 Funktionen Ähnliche eingebaute Funktionen von Python, die eine Funktion auf die Elemente einer Liste anwenden, sind map, reduce und die Listenmethode sort. Seit Python 2.0 können Sie fast immer auf die Funktionen map und filter verzichten. Statt lambda können Sie nun sogenannte »List comprehensions« verwenden. Die Ermittlung der positiven Zahlen sieht damit wie folgt aus: >>> zahlen = [23, −1, 2, 0, −3, 99] >>> positive = [x for x in zahlen if x > 0] >>> positive [23, 2, 99] Das ist vor allem bei komplexeren Ausdrücken viel lesbarer. Allerdings gibt es keine »Tuple comprehensions«, aber Sie können den entsprechenden Effekt erreichen, indem Sie die gewonnene Liste mit der eingebauten Funktion tuple(liste) in ein Tupel umwandeln. 3.5 Eingebaute Funktionen Python besitzt einige »eingebaute« Funktionen, die Sie ohne das explizite Laden von Modulen verwenden können. Einige dieser Funktionen haben Sie schon kennen gelernt; einige andere spreche ich in diesem Abschnitt »Eingebaute Funktionen« kurz an. Weitere Informationen finden Sie in Van Rossum und Drake (2001b) unter dem Thema »Built-in functions«. Es gibt mehrere Funktionen, um Objekte eines Typs in einen anderen Typ umzuwandeln, z. B. eine Liste in ein Tupel und umgekehrt. int bzw. long erzeugen jeweils einen entsprechenden ganzzahligen Wert aus dem angegebenen Argument. float konvertiert z. B. eine Ganzzahl in eine Gleitkommazahl. complex wandelt das Argument, wenn möglich, in eine komplexe Zahl um. hex und oct, deren jeweiliges Argument eine ganze Zahl sein muss, wandeln dieses in eine Zeichenkette um: >>> hex(14) '0xe' >>> oct(14) '016' >>> hex(14.0) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: hex() argument can't be converted to hex 52 Eingebaute Funktionen list wandelt ein Tupel in eine Liste um; wird als Argument eine Liste angegeben, wird eine Kopie bis zur »ersten Ebene« erzeugt; L2 = list(L1) und L2 = L1[:] sind gleichbedeutend. tuple macht aus einer Liste ein Tupel. str stellt ein Objekt als Zeichenkette dar. repr funktioniert ähnlich, nur wird im Allgemeinen eine »implementierungsnähere« Ausgabe erzeugt. chr erzeugt aus einem Zeichencode das entsprechende Zeichen, z. B. ergibt chr(65) das Zeichen »A«. ord wandelt umgekehrt ein Zeichen in einen Zeichencode. Das sind Funktionen, die mit der Ein- und Ausgabe zu tun haben: open öffnet eine Datei zum Lesen, Schreiben oder Anhängen im Text- oder Binärmodus. Seit Python 2.2 gibt es zusätzlich die Funktion file. Dahinter verbirgt sich lediglich die open-Funktion mit einem treffenderen Namen: Genauso, wie list aussagt, dass eine Liste erzeugt wird, erhält man mit file ein Dateiobjekt. raw_input liest eine Zeichenfolge von der Standardeingabe. Eine Gruppe von Funktionen erlaubt es, Informationen über Objekte einzuholen: dir fragt die zu einem Namensraum gehörenden Attribute ab; das wird in späteren Kapiteln behandelt. hasattr ermittelt, ob ein Namensraum ein bestimmtes Attribut enthält. globals liefert ein Dictionary der globalen Variablen (bezogen auf den gerade ausgeführten Code). locals dito, jedoch lokale Variablen type erfragt den Objekttyp. id ergibt einen Wert für die Objektidentität; a is b ist gleichbedeutend mit id(a) == id(b) . Zu diesen weiteren Funktionen kommt mir keine bessere Eingliederung in den Sinn: abs liefert den Absolutbetrag einer Zahl. len ermittelt die Anzahl der Elemente in einer Sequenz. max berechnet das Maximum mehrerer Werte. 53 3 Funktionen min ermittelt das Minimum mehrerer Werte. cmp vergleicht zwei Werte. Sind beide gleich, ist das Ergebnis 0. Ist das erste Argument größer, ist der Rückgabewert der Funktion positiv; ist der zweite Parameter größer, ergibt sich ein negativer Zahlenwert. apply verwendet ein Tupel und ein Dictionary als Parameter in einem Funktionsaufruf. iter liefert ein Iteratorobjekt, um über ein zugehöriges Objekt zu iterieren. 3.6 Übungen Übung 1 Erstellen Sie eine Funktion zur Berechnung des Skalarprodukts zweier Vektoren. Ja, das hat etwas mit Mathematik zu tun. Das Skalarprodukt der beiden Vektoren x und y ist definiert als s( x, y) = N ∑ xi yi = x 1 y 1 + x 2 y 2 + · · · + x N y N i =1 Falls das furchterregend für Sie aussieht, hier ein Beispiel. Das Skalarprodukt der beiden Vektoren x = (1.2, −3.7, 4.8, 0) und y = (23.05, 2.6, −1, 3) ist s( x, y) = 1.2 · 23.05 + (−3.7) · 2.6 + 4.8 · (−1) + 0 · 3 = 13.24 Gehen Sie für die Programmierung davon aus, dass die beiden Vektoren als gleich lange Python-Sequenzen in die Funktion gegeben werden. Übung 2 Verwenden Sie die in der vorherigen Aufgabe geschriebene Funktion, um das Produkt aus einer Matrix und einem Vektor zu ermitteln.2 Die Formel dafür lautet: a11 a12 · · · a1n s1 x1 a21 a22 · · · a2n x2 s2 p( A, x) = A x = . . = . .. .. .. .. . . . . . . . am1 am2 · · · amn xn sm 2 Falls Sie sich nie mit linearer Algebra befasst haben, erschrecken Sie bitte nicht. (Wenn Sie sich schon mal damit befasst haben, erschrecken Sie bitte auch nicht.) 54 Übungen Darin ist A eine Matrix mit m Zeilen und n Spalten; x ist ein Vektor mit n Elementen. Das Matrixelement mit dem Index i j steht in der i-ten Zeile und der j-ten Spalte. Der Ergebnisvektor s hat m Zeilen, also dieselbe Zeilenzahl wie die der Matrix. Die Elemente von s ergeben sich durch si = n ∑ ai j x j = ai1 x1 + ai2 x2 + · · · + ain xn j=1 Mit dieser Formel können Sie eine Funktion matrix_vector_product(matrix, vector) schreiben, um das Produkt der Matrix und des Vektors zu ermitteln. Gehen Sie dabei davon aus, dass die Spaltenzahl der Matrix n gleich der Zeilenzahl des Vektors ist (vgl. die genannte Formel). Die Matrix soll in Python so aufgebaut sein, dass man mit matrix[i−1][j−1] das Matrixelement ai j anspricht. (Der erste Index, i, bezeichnet die Zeile, der zweite, j, die Spalte.) Die Einsen werden deshalb abgezogen, weil in Python Listen beginnend mit dem Index 0 indiziert werden, während man in der Mathematik die Zählung in Vektoren sowie in Zeilen und Spalten von Matrizen mit 1 beginnt. Übung 3 Ergänzen Sie die Datei aus den vorherigen Übungen um eine Testfunktion für die enthaltenen Funktionen scalar_product und matrix_vector_product. Die Tests sollen nur ausgeführt werden, wenn das Script von der Kommandozeile gestartet wird, sonst nicht. Übung 4 Schreiben Sie eine Funktion, die mit einer Liste von Zeichenketten als dem einzigen Argument aufgerufen wird. Als Ergebnis soll das Unterprogramm die nummerierten Zeilen, analog zu den Listings in diesem Buch, zurückliefern. Ein Beispiel: >>> number( ['Dies ist', ' ein kleiner', ' ['001 Dies ist', '002 ein kleiner', '003 Test'] ) Test'] Die ursprüngliche Liste soll dabei unverändert bleiben. Übung 5 Ahmen Sie die String-Methode split, die Sie bereits in Übungen im vorherigen Kapitel kennen gelernt haben, als eine Funktion nach. 55 3 Funktionen Verwenden Sie dazu die am Anfang des Kapitels vorgestellte Funktion index. Die Syntax von split soll split(string, separator) sein, der in einem Aufruf übergebene trennende String soll genau ein Zeichen lang sein; ansonsten soll sich die Funktion so wie die Methode verhalten (s. Van Rossum und Drake, 2001b, Thema »String methods«). Übung 6 Nicht immer ist die Wiederverwendung von Code, wie in den Aufgaben zu Matrizen und Vektoren, günstig. Sie können die vorhergehende Aufgabe, die Programmierung der Funktion split, einfacher lösen, wenn Sie auf die Funktion index verzichten. 3.7 Hinweise Hinweis zu Übung 2 Das i-te Element des Ergebnisvektors s ist das Skalarprodukt der i-ten Zeile der Matrix und des Vektors x. Hinweis zu Übung 4 '%03d' % 5 liefert 005. Hinweis zu Übung 5 Auch ein String ist eine Sequenz. Probieren Sie mit der neuen Funktion index mal index('Test', 's') aus. 3.8 Lösungen Lösung zu Übung 1 Das Skalarprodukt lässt sich mit diesem Unterprogramm berechnen: funktionen/ scalar_product.py def scalar_product(x, y): '''Return the scalar product of the vectors x and y.''' sum = 0.0 for i in range( len(x) ): sum += x[i] * y[i] return sum Um die Produkte der Komponenten der Vektoren aufsummieren zu können, wird eine Summenvariable benötigt. Die for-Schleife läuft 56 Lösungen über die Liste der Indices, die für die beiden gleich langen Vektoren möglich sind. Der Ausdruck range(len(...)) wird Ihnen noch oft begegnen. Die Summe muss explizit an die aufrufende Funktion zurückgeliefert werden. Die Routine in Aktion: >>> scalar_product( (1.2, −3.7, 4.8, 0), (23.05, 2.6, −1, 3) ) 13.239999999999998 Lösung zu Übung 2 Das Matrix-Vektor-Produkt kann man mit der folgenden Funktion berechnen. Vielleicht haben Sie wie ich den Eindruck, dass das Verstehen der Aufgabe viel schwieriger ist bzw. war als deren Lösung. 001 def matrix_vector_product(matrix, vector): 002 '''Return the product of the matrix and the vector.''' 003 result = [] 004 for row in matrix: 005 result.append( scalar_product(row, vector) ) 006 return result funktionen/ linalg.py In Zeile 3 wird eine leere Liste erzeugt, die das Ergebnis der Berechnung aufnehmen soll. In Zeile 4 wird eine for-Schleife gestartet, die die Matrix zeilenweise durchläuft. Die Forderung in der Aufgabe war, dass matrix[i−1][j−1] sich auf die j-te Spalte der i-ten Zeile bezieht. Daher ist matrix[i−1] die i-te Zeile, eine Python-Liste. Diese kann man wiederum indizieren; matrix[i−1][j−1] liefert also das j-te Element dieser Liste. Ein Beispiel: Die Matrix 1 −1 2 3 9 −2 5 7 −3 2 1 11 würde man in Python als [ [1, −1, 2], [3, 9, −2], [5, 7, −3], [2, 1, 11] ] schreiben.3 Dann bekommt man mit matrix[1] die Liste [3, 9, −2] und mit matrix[1][0] das Element 3. Der Python-Name result bezeichnet eine Liste, deshalb kann man in der Programmzeile 5 mit der Methode append neue Elemente anhängen. Wie in den Hinweisen zur Übung angemerkt, ist das Produkt der jeweiligen Matrixzeile mit dem Vektor das jeweilige Element des Ergebnisvektors. Daher kann man einfach auf die Funktion 3 Zumindest nach der Konvention der Aufgabenstellung. Sie könnten die Matrix auch als Liste von Spalten darstellen, aber dann würde die Übung etwas schwieriger zu lösen sein. Wenn Sie möchten, übernehmen Sie das als Zusatzaufgabe. 57 3 Funktionen scalar_product zurückgreifen, die in der vorherigen Übungsauf- gabe erstellt wurde. In Zeile 6 wird der erzeugte Ergebnisvektor an die aufrufende Funktion zurückgeliefert. Noch eine Anmerkung: Mit List comprehensions kann man die Funktion matrix_vector_product noch kürzer und ohne Hilfsvariable für den Ergebnisvektor schreiben: def matrix_vector_product2(matrix, vector): '''Return the product of the matrix and the vector.''' return [scalar_product(row, vector) for row in matrix] Lösung zu Übung 3 Eine mögliche Testfunktion ist die folgende. Zuerst wird die Funktion für das Skalarprodukt getestet: funktionen/ linalg.py 001 def test(): 002 # test of scalar_product 003 x = [2, −3, 11, −13] 004 y = [−1, 19, 10, −2] 005 expected = −2 − 3*19 + 11*10 + 13*2 006 print scalar_product(x, y) 007 if scalar_product(x, y) == expected: 008 print 'scalar_product ok' 009 else: 010 print 'scalar_product FAILED' In den Zeilen 3 und 4 werden willkürlich Werte für die beiden Vektoren vorgegeben, danach wird in der Programmzeile 5 »zu Fuß« der erwartete Wert berechnet. Anschließend wird in den Zeilen 7 bis 10 das mutmaßliche mit dem tatsächlich berechneten Ergebnis verglichen und eine entsprechende Meldung ausgegeben. Die Tests der Unterprogramme matrix_vector_product und der alternativen Formulierung matrix_vector_product2 funktionieren entsprechend: 011 012 013 014 015 016 017 018 019 020 021 022 023 024 58 # test of matrix_vector_product matrix = [ [1, −1, 2], [3, 9, −2], [5, 7, −3], [2, 1, 11] ] vector = [2, 3, −7] expected = [−15, 47, 52, −70] print matrix_vector_product(matrix, vector) if matrix_vector_product(matrix, vector) == expected: print 'matrix_vector_product ok' else: print 'matrix_vector_product FAILED' print matrix_vector_product2(matrix, vector) if matrix_vector_product2(matrix, vector) == expected: print 'matrix_vector_product2 ok' else: print 'matrix_vector_product2 FAILED' Lösungen Dieser Code sorgt dafür, dass die Funktion test nur beim Aufruf des Programms von der Kommandozeile (bzw. durch Anklicken eines Icons) gestartet wird: 026 if __name__ == '__main__': 027 test() Lösung zu Übung 4 Eine recht nahe liegende Lösung ist diese: 001 def number(lines): 002 '''Return numbered lines in list.''' 003 new_lines = [] 004 for i in range( len(lines) ): 005 new_lines.append( '%03d %s' % (i+1, lines[i]) ) 006 return new_lines funktionen/ number.py Zuerst wird in Zeile 3 eine leere Liste erzeugt, die die nummerierten Zeilen aufnehmen soll. Die Schleife nach dieser Anweisung wird für alle Indices bis zur Anzahl der Zeilen minus 1 durchlaufen. Zeile 5 enthält sowohl die Formatierung der zu erzeugenden Zeichenketten als auch den Befehl append zum Anfügen an die generierte Liste. Da die Zeilennummern bei 1 beginnen sollen, der Index i jedoch ab 0 zählt, wird i+1 verwendet. Die Datei funktionen/number.py enthält noch weitere Varianten. Eine geringe Abwandlung der oben vorgestellten Lösung ist def number(lines): '''Return numbered lines in list.''' new_lines = lines[:] for i in range( len(new_lines) ): new_lines[i] = '%03d %s' % ( i+1, lines[i] ) return new_lines Hier wird keine leere Liste erzeugt, sondern mit lines[:] eine Kopie der Liste lines angelegt. Deren einzelne Elemente werden dann unmittelbar verändert. Die Liste lines bleibt wie sie ist. Leider falsch ist die folgende »Lösung«. def number(lines): '''Return numbered lines in list.''' new_lines = [] for line in lines: new_lines.append( '%03d %s' % ( lines.index(line)+1, line ) ) return new_lines Das Problem tritt zutage, sobald Sie mehrere identische Zeilen im Code haben: 59 3 Funktionen >>> number( ['line 1', '', 'line 3', '', 'line 5'] ) ['001 line 1', '002 ', '003 line 3', '002 ', '005 line 5'] Die Beispieldatei funktionen/number.py enthält den Code if __name__ == '__main__': lines = ['Dies ist', ' ein kleiner', ' print 'Source:', lines print '1:', number1(lines) print '2:', number2(lines) print '3:', number3(lines) print '4:', number4(lines) print '5:', number5(lines) Test', ''] um die fünf Versionen der number-Funktion zu testen. Das erste Zeile davon fragt ab, ob die Datei unmittelbar als Hauptprogramm mit dem Interpreter gestartet wurde. Nur dann wird der Testcode ausgeführt, nicht jedoch, wenn die Datei als Modul in ein anderes Programm geladen wird. Näheres dazu im Kapitel »Module.« Verwenden Sie das Idiom if __name__ == '__main__': für Tests eines Moduls. Bei komplexeren Modulen lohnt sich u. U. ein eigenes Python-Programm, um darin alle Tests unterzubringen. Lösung zu Übung 5 Sie können die Aufgabe unter Verwendung der Funktion index wie folgt lösen. funktionen/split. py 001 def split(string, separator): 002 ''' 003 Split string at occurences of the separator and return 004 a list of substrings. 005 ''' 006 start = 0 007 parts = [] 008 while 1: 009 end_of_part = index(string, separator, start) 010 if end_of_part is not None: 011 # we found a separator 012 parts.append( string[start:end_of_part] ) 013 # advance the starting point for the next 014 # search after the separator 015 start = end_of_part + 1 016 else: 017 # add rest of string 018 parts.append( string[start:] ) 019 break 020 return parts In Zeile 6 und 7 werden die Vorbereitungen getroffen. Die Suche nach Teilzeichenketten beginnt im String beim Index 0; die Liste der gefundenen Teile ist leer. 60 Lösungen Zeile 8 markiert den Beginn der Schleife, die nach vollständiger Durchsuchung des Strings mit break verlassen wird. In Zeile 9 wird mit der bereits vorgestellten Funktion index der Index des ersten Auftretens von separator in der untersuchten Zeichenkette, bei start beginnend, ermittelt. Falls end_of_part nicht None ist, d. h. ein Trennzeichen gefunden wurde, wird in Zeile 12 der Stringteil von start bis zum Trenner in die Liste der abgespaltenen Teile eingefügt. Das Trennzeichen selbst ist nicht Teil der extrahierten Zeichenkette. In Zeile 15 wird dafür gesorgt, dass die Suche beim nächsten Schleifendurchlauf hinter dem vorher gefundenen Trennzeichen anfängt. Wird in Zeile 9 kein weiterer Trenner gefunden, wird mit Zeile 18 der Rest der untersuchten Zeichenkette in die Liste der gefundenen Teile aufgenommen. Anschließend wird die Schleife mit break beendet. Danach wird die erzeugte Liste an den Aufrufer zurückgegeben. Ein Beispiel soll den Ablauf der Funktion erklären helfen. Stellen Sie sich den Funktionsaufruf parts = split('4−Sep−2001', '−') vor. Nach dem Eintritt in die Schleife wird end_of_part, die Fundstelle des ersten Trennzeichens, zu 1. Nach dem Einfügen von string[0:1] = »4« in die Liste der Teilzeichenketten wird die Variable start wird auf 1 + 1 = 2 erhöht, damit die nächste Suche beim Index 2 beginnt. Die Suche im nächsten Schleifendurchlauf findet den Index 5, das Minuszeichen nach »Sep«. Der String string[2:5], also »Sep«, wird an die Liste der Zeichenketten angehängt und der Index für den neuen Suchbeginn auf 6 erhöht. Schließlich wird kein Minuszeichen mehr gefunden; string[6:], »2001«, wird ebenfalls zum Bestandteil der Liste. Lösung zu Übung 6 Die Aufgabe lässt sich weniger kompliziert lösen, wenn die Funktion index nicht verwendet wird. Lesen Sie »kürzer« hier nicht nur als »weniger Zeilen«, sondern auch als »leichter verständlich.« 001 def split(string, separator): 002 ''' 003 Split string at occurences of the separator and return 004 a list of substrings. 005 ''' 006 temp_string = '' 007 parts = [] funktionen/split. py 61 3 Funktionen 008 009 010 011 012 013 014 015 for char in string: if char == separator: parts.append(temp_string) temp_string = '' else: temp_string += char parts.append(temp_string) return parts Hier wird der zu untersuchende String einfach Zeichen für Zeichen betrachtet. Ist das Zeichen char das Trennzeichen, so ist ein Teil komplett und wird an die Liste der Teilzeichenketten gehängt (Zeile 10). Die Hilfsvariable temp_string wird dann in Zeile 11 wieder auf einen leeren String zurückgesetzt. Lässt das Trennzeichen dagegen auf sich warten, wird das aktuelle Zeichen in Zeile 13 an die temporäre Zeichenkette angehängt und mit dem nächsten Durchlauf fortgefahren. Nach der letzten Schleifeniteration muss noch der zuletzt gebildete Teilstring der Liste parts hinzugefügt werden (Zeile 14). 62