Stefan Schwarzer: workshop Python

Werbung
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
Herunterladen