Uploaded by Ngocvuminh97

Einführung in Python

advertisement
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
bernd KLEIN
EINFÜHRUNG IN
PYTHON
FÜR EIN- UND UMSTEIGER
3. Auflage
Im Internet: Musterlösungen zu
den Übungen
3
Klein
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Einführung in Python 3
Bleiben Sie auf dem Laufenden!
Unser Computerbuch-Newsletter informiert
Sie monatlich über neue Bücher und Termine.
Profitieren Sie auch von Gewinnspielen und
exklusiven Leseproben. Gleich anmelden unter
www.hanser-fachbuch.de/newsletter
Hanser Update ist der IT-Blog des Hanser Verlags
mit Beiträgen und Praxistipps von unseren Autoren
rund um die Themen Online Marketing, Webentwicklung, Programmierung, Softwareentwicklung
sowie IT- und Projektmanagement. Lesen Sie mit
und abonnieren Sie unsere News unter
www.hanser-fachbuch.de/update
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.

Bernd Klein
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Bernd Klein
Einführung
Python
Einführung ininPython
33
Für Ein- und Umsteiger
Für Ein- und Umsteiger
3., überarbeitete Auflage
3., überarbeitete Auflage
Der Autor:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Bernd Klein, bernd@python-kurs.eu
Alle in diesem Buch enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit
keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autor und Verlag übernehmen
infolgedessen keine juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen – oder
Teilen davon – entsteht.
Ebenso übernehmen Autor und Verlag keine Gewähr dafür, dass beschriebene Verfahren usw.
frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen,
Warenbezeichnungen usw. in diesem Buch berechtigt deshalb auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und MarkenschutzGesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Bibliografische Information der Deutschen Nationalbibliothek:
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
Dieses Werk ist urheberrechtlich geschützt.
Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches,
oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des
Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren) – auch nicht für
Zwecke der Unterrichtsgestaltung – reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.
© 2018 Carl Hanser Verlag München, www.hanser-fachbuch.de
Lektorat: Brigitte Bauer-Schiewek
Herstellung: Irene Weilhart
Copy editing: Jürgen Dubau, Freiburg/Elbe
Layout: le-tex publishing services, Leipzig
Umschlagdesign: Marc Müller-Bremer, www.rebranding.de, München
Umschlagrealisation: Stephan Rönigk
Druck und Bindung: Kösel, Krugzell
Ausstattung patentrechtlich geschützt. Kösel FD 351, Patent-Nr. 0748702
Printed in Germany
Print-ISBN:
978-3-446-45208-4
E-Book-ISBN: 978-3-446-45387-6
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Inhalt
Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XVII
Danksagung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XVIII
1
Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.1
Einfach und schnell zu lernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2
Zielgruppe des Buches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.3
Aufbau des Buches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.4
Programmieren lernen „interaktiv” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.5
Download der Beispiele und Hilfe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.6
Anregungen und Kritik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
Kommandos und Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
Teil I
2
2.1
2.2
Erste Schritte mit Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2.1.1
Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2.1.2
Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.1.3
macOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
Herkunft und Bedeutung des Begriffes interaktive Shell . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2.1
9
Erste Schritte in der interaktiven Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
2.3
Verlassen der Python-Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.4
Benutzung von Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
2.5
Mehrzeilige Anweisungen in der interaktiven Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
2.6
Programme schreiben oder schnell mal der Welt “Hallo” sagen . . . . . . . . . . . . . . . . . .
13
3
Bytecode und Maschinencode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
3.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
3.2
Unterschied zwischen Programmier- und Skriptsprachen . . . . . . . . . . . . . . . . . . . . . . . .
17
3.3
Interpreter- oder Compilersprache. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
VI
Inhalt
4
Datentypen und Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
4.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
4.2
Variablennamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
4.3
4.2.1
Gültige Variablennamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
4.2.2
Konventionen für Variablennamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
4.3.1
Ganze Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
4.3.2
Fließkommazahlen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
4.3.3
Zeichenketten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
4.3.4
Boolesche Werte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
4.3.5
Komplexe Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
4.3.6
Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
4.4
Statische und dynamische Typdeklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
4.5
Typumwandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
4.6
Datentyp ermitteln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
5
Sequentielle Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
5.1
Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
5.1.1
Zeichenketten oder Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
5.1.2
Listen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
5.1.3
Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
5.2
Indizierung von sequentiellen Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
5.3
Teilbereichsoperator. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
5.4
Die len-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
5.5
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
6
Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
6.1
Dictionaries und assoziative Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
6.2
Definition und Benutzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
6.3
Fehlerfreie Zugriffe auf Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
6.4
Zulässige Typen für Schlüssel und Werte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
6.5
Verschachtelte Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
6.6
Methoden auf Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
6.7
Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
6.8
Die zip-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
6.9
Dictionaries aus Listen erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
6.10
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
Inhalt
7
Mengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
7.1
Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
7.2
Mengen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
7.3
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
7.4
7.2.1
Sets erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
7.2.2
Mengen von unveränderlichen Elementen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
Frozensets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
Operationen auf „set”-Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
7.4.1
add(element). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
7.4.2
clear() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
7.4.3
copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
7.4.4
difference() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
7.4.5
difference_update() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
7.4.6
discard(el) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
7.4.7
remove(el) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
7.4.8
intersection(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
7.4.9
isdisjoint() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
7.4.10 issubset() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
7.4.11 issuperset() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
7.4.12 pop() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
8
Eingaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
8.1
Eingabe mittels input. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
9
Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
9.1
Anweisungsblöcke und Einrückungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
9.2
Bedingte Anweisungen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
9.2.1
Einfachste if-Anweisung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
9.2.2
if-Anweisung mit else-Zweig. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
9.2.3
elif-Zweige . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
9.3
Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
9.4
Zusammengesetzte Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
9.5
Wahr oder falsch: Bedingungen in Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
9.6
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
10
Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
10.1
Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
10.2
while-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
10.3
break und continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
77
10.4
die Alternative im Erfolgsfall: else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78
10.5
For-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
80
10.6
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83
VII
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
VIII
Inhalt
11
Dateien lesen und schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
87
11.1
Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
87
11.2
Text aus einer Datei lesen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
87
11.3
Schreiben in eine Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
89
11.4
In einem Rutsch lesen: readlines und read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
89
11.5
with-Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
90
11.6
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
91
12
Formatierte Ausgabe und Strings formatieren . . . . . . . . . . . . . . . . . . . . . . . . . . .
93
12.1
Wege, die Ausgabe zu formatieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
93
12.2
print-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
93
12.3
Stringformatierung im C-Stil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
95
12.4
Der pythonische Weg: Die String-Methode „format” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
99
12.5
Benutzung von Dictionaries beim Aufruf der „format”-Methode . . . . . . . . . . . . . . . .
102
12.6
Benutzung von lokalen Variablen in „format” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
103
12.7
Formatierte Stringliterale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
104
12.8
Weitere String-Methoden zum Formatieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
104
13
Flaches und tiefes Kopieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
13.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
107
13.2
Kopieren einer Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
108
13.3
Flache Kopien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
110
13.4
Kopieren mit deepcopy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
111
13.5
Deepcopy für Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
112
14
Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
14.1
Allgemein . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
113
14.2
Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
113
14.3
Docstring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
115
14.4
Standardwerte für Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
117
14.5
Schlüsselwortparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
118
14.6
Funktionen ohne oder mit leerer return-Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
118
14.7
Mehrere Rückgabewerte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
119
14.8
Lokale und globale Variablen in Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
120
14.9
Parameterübergabe im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
122
14.10 Effekte bei veränderlichen Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
124
14.11 Kommandozeilenparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
125
14.12 Variable Anzahl von Parametern / Variadische Funktionen . . . . . . . . . . . . . . . . . . . . . . .
126
14.13 * in Funktionsaufrufen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
128
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Inhalt
14.14 Beliebige Schlüsselwortparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
129
14.15 Doppeltes Sternchen im Funktionsaufruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
129
14.16 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
130
15
Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
15.1
Definition und Herkunft des Begriffs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
133
15.2
Definition der Rekursion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
134
15.3
Rekursive Funktionen in Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
134
15.4
Die Tücken der Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
135
15.5
Fibonacci-Folge in Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
136
15.6
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
140
16
Listen und Tupel im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
16.1
Stapelspeicher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
143
16.2
Stapelverarbeitung in Python: pop und append. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
144
16.3
extend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
144
16.4
,+’-Operator oder append . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
145
16.5
Entfernen eines Wertes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
147
16.6
Prüfen, ob ein Element in Liste enthalten ist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
147
16.7
Finden der Position eines Elementes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
147
16.8
Einfügen von Elementen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
148
16.9
Besonderheiten bei Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
148
16.9.1 Leere Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
149
16.9.2 1-Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
149
16.9.3 Mehrfachzuweisungen, Packing und Unpacking . . . . . . . . . . . . . . . . . . . . . . . . . .
149
16.10 Die veränderliche Unveränderliche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
151
16.11 Sortieren von Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
151
16.11.1 „sort” und „sorted” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
151
16.11.2 Umkehrung der Sortierreihenfolge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
152
16.11.3 Eigene Sortierfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
152
16.12 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
155
17
Modularisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
17.1
Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
157
17.1.1 Namensräume von Modulen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
158
17.1.2 Namensräume umbenennen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
159
17.1.3 Modularten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
159
17.1.4 Suchpfad für Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
160
17.1.5 Inhalt eines Modules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
161
IX
X
Inhalt
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
17.2
17.1.6 Eigene Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
161
17.1.7 Dokumentation für eigene Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
162
Pakete. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
163
17.2.1 Einfaches Paket erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
164
17.2.2 Komplexeres Paket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
165
17.2.3 Komplettes Paket importieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
168
18
Globale und lokale Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
18.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
171
18.2
Globale und lokale Variablen in Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
172
19
Alles über Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
19.1
... fast alles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
175
19.2
Aufspalten von Zeichenketten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
176
19.2.1 split . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
176
19.2.2 Standardverhalten und „maxsplit” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
178
19.2.3 rsplit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
179
19.2.4 Folge von Trennzeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
181
19.2.5 splitlines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
182
19.2.6 partition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
183
19.3
Zusammenfügen von Stringlisten mit join . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
183
19.4
Suchen von Teilstrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
183
19.4.1 „in” oder „not in” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
183
19.4.2 s.find(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
184
19.4.3 s.rfind(substring[, start[, end]]). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
184
19.4.4 s.index(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
185
19.4.5 s.rindex(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
185
19.4.6 s.count(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
185
19.5
Suchen und Ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
186
19.6
Nur noch Kleinbuchstaben oder Großbuchstaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
186
19.7
capitalize und title . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
186
19.8
Stripping Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
187
19.9
Strings ausrichten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
187
19.10 String-Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
188
19.11 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
190
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Inhalt
20
Ausnahmebehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
20.1
Abfangen mehrerer Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
195
20.2
except mit mehrfachen Ausnahmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
196
20.3
Die optionale else-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
196
20.4
Fehlerinformationen über sys.exc_info . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
197
20.5
Exceptions generieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
197
20.6
Finalisierungsaktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
198
Teil II
Objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
21
Grundlegende Aspekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
21.1
Bibliotheksvergleich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
201
21.2
Objekte und Instanzen einer Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
203
21.3
Kapselung von Daten und Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
204
21.4
Eine minimale Klasse in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
204
21.5
Eigenschaften und Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
205
21.6
Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
208
21.7
Instanzvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
209
21.8
Die __init__-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
209
21.9
Destruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
211
21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip . . . . . . . . . . . . . . . . . . . . . . .
212
21.10.1 Definitionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
212
21.10.2 Zugriffsmethoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
214
21.10.3 Properties. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
215
21.10.4 Public-, Protected- und Private-Attribute. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
217
21.10.5 Weitere Möglichkeiten der Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
218
21.10.6 Properties mit Dekorateuren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
221
21.11 Die __str__- und die __repr__-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
222
21.12 Klassenattribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
227
21.13 Statische Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
229
21.13.1 Einleitendes Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
230
21.13.2 Getter und Setter für private Klassenattribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
231
21.14 Public-Attribute statt private Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
232
21.15 Magische Methoden und Operatorüberladung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
233
21.15.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
233
21.15.2 Übersicht magische Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
235
21.15.3 Beispielklasse: Length . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
236
XI
XII
Inhalt
22
Bruchklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
22.1
Brüche à la 1001 Nacht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
241
22.2
Zurück in die Gegenwart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
242
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
22.3
22.4
Rechenregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
244
22.3.1 Multiplikation von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
244
22.3.2 Division von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
245
22.3.3 Addition von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
246
22.3.4 Subtraktion von Brüchen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
246
22.3.5 Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
247
Integer plus Bruch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
247
22.4.1 Die Bruchklasse im Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
248
22.5
Fraction-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
250
23
Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
23.1
Oberbegriffe und Oberklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
251
23.2
Ein einfaches Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
252
23.3
Überladen, Überschreiben und Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
253
23.4
Vererbung in Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
256
23.5
Klassenmethoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
259
23.6
Standardklassen als Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
261
24
Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
24.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
263
24.2
Beispiel: CalendarClock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
264
24.3
Diamand-Problem oder „deadly diamond of death” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
272
24.4
super und MRO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
273
25
Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
25.1
Erzeugung von dynamischen Attributen verhindern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
Dynamische Erzeugung von Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
26.1
Beziehung zwischen „class” und „type” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
Metaklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
27.1
Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27.2
Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
290
27.3
Definition von Metaklassen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
290
27.4
Singletons mit Metaklassen erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
292
279
281
285
Inhalt
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
27.5
Beispiel – Methodenaufrufe zählen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
293
27.5.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
293
27.5.2 Vorbereitungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
293
27.5.3 Ein Dekorateur, um Funktionsaufrufe zu zählen . . . . . . . . . . . . . . . . . . . . . . . . . .
295
27.5.4 Die Metaklasse „Aufrufzähler” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
295
28
Abstrakte Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
29
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
Teil III
Fortgeschrittenes Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
30
lambda, map, filter und reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
30.1
lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
307
30.2
map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
310
30.3
Filtern von sequentiellen Datentypen mittels „filter” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
312
30.4
reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
313
30.5
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
314
31
Listen-Abstraktion/List Comprehension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
31.1
Die Alternative zu Lambda und Co. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
315
31.2
Syntax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
316
31.3
Weitere Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
316
31.4
Die zugrunde liegende Idee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
317
31.5
Anspruchsvolleres Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
318
31.6
Mengen-Abstraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
318
31.7
Rekursive Primzahlberechnung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
319
31.8
Generatoren-Abstraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
319
31.9
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
320
32
Generatoren und Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
32.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
323
32.2
Iteration in for-Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
323
32.3
Generatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
325
32.4
Generatoren zähmen mit firstn und islice. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
327
32.5
32.6
Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
328
32.5.1 Permutationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
328
32.5.2 Variationen und Kombinationen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
329
Generator-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
331
XIII
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
XIV
Inhalt
32.7
return-Anweisungen in Generatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
332
32.8
send-Methode / Koroutinen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
332
32.9
Die throw-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
334
32.10 Dekoration von Generatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
336
32.11 yield from . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
337
32.12 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
339
33
33.1
Dekorateure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
Einführung Dekorateure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
341
33.1.1 Verschachtelte Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
342
33.1.2 Funktionen als Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
343
33.1.3 Funktionen als Rückgabewert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
344
33.1.4 Fabrikfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
345
33.2
Ein einfacher Dekorateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
346
33.3
@-Syntax für Dekorateure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
347
33.4
Anwendungsfälle für Dekorateure. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
350
33.4.1 Überprüfung von Argumenten durch Dekorateure . . . . . . . . . . . . . . . . . . . . . . .
350
33.4.2 Funktionsaufrufe mit einem Dekorateur zählen. . . . . . . . . . . . . . . . . . . . . . . . . . .
351
33.5
Dekorateure mit Parametern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
352
33.6
Benutzung von Wraps aus functools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
354
33.7
Klassen statt Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
355
33.7.1 Die __call__-Methode. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
355
33.8
Eine Klasse als Dekorateur benutzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
357
33.9
Memoisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
358
33.9.1 Bedeutung und Herkunft des Begriffs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
358
33.9.2 Memoisation mit Dekorateur-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
358
33.9.3 Memoisation mit einer Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
359
33.9.4 Memoisation mit functools.lru_cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
360
Teil IV
Weiterführende Themen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
34
Tests und Fehler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
34.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
365
34.2
Modultests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
367
34.3
Modultests unter Benutzung von __name__ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
368
34.4
doctest-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
370
34.5
Testgetriebene Entwicklung oder „Im Anfang war der Test” . . . . . . . . . . . . . . . . . . . . . . .
373
34.6
unittest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
375
34.7
Methoden der Klasse TestCase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
377
34.8
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
380
Inhalt
35
Daten konservieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
35.1
Persistente Speicherung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
381
Pickle-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
382
35.2.1 Daten „einpökeln” mit pickle.dump . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
382
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
35.2
35.2.2 pickle.load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
383
35.3
Ein persistentes Dictionary mit shelve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
383
36
Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
36.1
Ursprünge und Verbreitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
387
36.2
Stringvergleiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
387
36.3
Überlappungen und Teilstrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
389
36.4
Das re-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
389
36.5
Matching-Problem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
390
36.6
Syntax der regulären Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
392
36.6.1 Beliebiges Zeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
392
36.7
Zeichenauswahl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
393
36.8
Endliche Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
394
36.9
Anfang und Ende eines Strings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
394
36.10 Vordefinierte Zeichenklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
397
36.11 Optionale Teile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
398
36.12 Quantoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
399
36.13 Gruppierungen und Rückwärtsreferenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
401
36.13.1 Match-Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
401
36.14 Umfangreiche Übung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
404
36.15 Alles finden mit findall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
406
36.16 Alternativen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
407
36.17 Compilierung von regulären Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
407
36.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck . . . . . . . . . . . . . . . . . . . . . .
409
36.18.1 split-Methode der String-Klasse. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
409
36.18.2 split-Methode des re-Moduls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
410
36.18.3 Wörter filtern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
412
36.19 Suchen und Ersetzen mit sub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
413
36.20 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
414
37
Typanmerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
37.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
417
37.2
Einfaches Beispiel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
418
37.3
Variablenanmerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
419
37.4
Listenbeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
420
37.5
typing-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
421
XV
XVI
Inhalt
38
Systemprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425
38.1
Systemprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
425
38.2
Häufig falsch verstanden: Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
425
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
38.3
os-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
426
38.3.1 Vorbemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
427
38.3.2 Umgebungsvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
427
38.3.3 Dateiverarbeitung auf niedrigerer Ebene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
429
38.3.4 Die exec-„Familie” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
434
38.3.5 Weitere Funktionen im Überblick. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
441
38.3.6 os.path - Arbeiten mit Pfaden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
455
38.4
shutil-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
463
39
Forks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469
39.1
Fork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
469
39.2
Fork in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
469
Teil V
Lösungen zu den Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
40
Lösungen zu den Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
40.1
Lösungen zu Kapitel 5 (Sequentielle Datentypen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
475
40.2
Lösungen zu Kapitel 6 (Dictionaries) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
478
40.3
Lösungen zu Kapitel 9 (Verzweigungen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
480
40.4
Lösungen zu Kapitel 10 (Schleifen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
482
40.5
Lösungen zu Kapitel 11 (Dateien lesen und schreiben) . . . . . . . . . . . . . . . . . . . . . . . . . . . .
485
40.6
Lösungen zu Kapitel 16 (Listen und Tupel im Detail) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
487
40.7
Lösungen zu Kapitel 14 (Funktionen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
490
40.8
Lösungen zu Kapitel 15 (Rekursive Funktionen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
495
40.9
Lösungen zu Kapitel 19 (Alles über Strings . . . ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
499
40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
502
40.11 Lösungen zu Kapitel 34 (Tests und Fehler) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
512
40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
512
40.13 Lösungen zu Kapitel 30 (lambda, map, filter und reduce) . . . . . . . . . . . . . . . . . . . . . . . . .
518
40.14 Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension) . . . . . . . . . . . . . .
519
40.15 Lösungen zu Kapitel 32 (Generatoren und Iteratoren) . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
520
Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Vorwort
Ist es wirklich so, dass Vorwörter – ähnlich wie Bedienungsanleitungen – meistens nicht
gelesen werden? Auch wenn dies sicherlich für viele zutreffen mag, so gibt es Situationen,
in denen gerade ein Vorwort wertvolle Dienste leisten kann. Zum Beispiel, um die Kaufentscheidung für ein Buch zu erleichtern. So stehen auch Sie jetzt vielleicht am Regal einer
guten Buchhandlung und werden möglicherweise von zwei Fragen bewegt: Sie brauchen
noch eine Bestätigung, dass Python die richtige Programmiersprache für Sie ist, und möchten wissen, wie Ihnen dieses Buch helfen wird, die Sprache schnell und effizient zu erlernen.
Für Python spricht der traumhafte Anstieg der Bedeutung in Wissenschaft, Forschung und
Wirtschaft in den letzten Jahren. Im renommierten PYPL-Index stand Python im Juli 2017
auf dem zweiten Platz hinter Java. Während Java im Vergleich zum Vorjahr jedoch -1,1 %
Anteil verloren hatte, gewann Python +4,0 % hinzu. Weitere wichtige Gründe, Python zu
lernen, sind: Python ist mit seiner einfachen natürlichen Syntax sehr einfach zu lernen.
Python läuft auf allen Plattformen und erfreut sich auch beim Raspberry Pi größter Beliebtheit. Außerdem ist Python eine universell einsetzbare Programmiersprache, die sich
bei den Aufgaben der Systemadministration ebenso effektiv einsetzen lässt wie im Maschinenbau, der Linguistik, der Pharmaindustrie, in der Banken- und Finanzwelt, der Physik
und vielen anderen Bereichen. Weil Firmen wie Google Python lieben und unterstützen,
wird Python auch kontinuierlich weiterentwickelt.
Dieses Buch erscheint nun in der dritten, weitgehend überarbeiteten Ausgabe. Es eignet
sich ebenso für Programmieranfänger als auch für Umsteiger von anderen Programmiersprachen. Behandelt werden nicht nur alle grundlegenden Sprachelemente von Python,
sondern auch weiterführende Themen wie Generatoren, Dekorateure, Systemprogrammierung, Threads, Forks, Ausnahmebehandlungen und Modultests. Auf über Hundert
Seiten wird anschaulich und mit zahlreichen Beispiele auf die vielfältigen Aspekte der
Objektorientierung eingegangen.
Brigitte Bauer-Schiewek, Lektorin
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Danksagung
Zum Schreiben eines Buches benötigt es neben der nötigen Erfahrung und Kompetenz im
Fachgebiet vor allem viel Zeit. Zeit außerhalb des üblichen Rahmens. Zeit, die vor allem die
Familie mitzutragen hat. Deshalb gilt mein besonderer Dank meiner Frau Karola, die mich
während dieser Zeit tatkräftig unterstützt hat.
Außerdem danke ich den zahlreichen Teilnehmerinnen und Teilnehmern an meinen
Python-Kursen, die mir geholfen haben, meine didaktischen und fachlichen Kenntnisse kontinuierlich zu verbessern. Ebenso möchte ich den Besucherinnen und Besuchern
meiner Online-Tutorials unter www.python-kurs.eu und www.python-course.eu danken,
vor allem jenen, die sich mit konstruktiven Anmerkungen bei mir gemeldet haben. Wertvolle Anregungen erhielt ich auch von denen, die das Buch vorab Korrektur gelesen hatten:
Stefan Günther für die Erstauflage des Buches. Für die Hilfe zur zweiten – weitestgehend
überarbeiteten – Auflage möchte ich im besonderen Maße Herrn Jan Lendertse und Herrn
Vincent Bermel Dank sagen. Für die dritte – wiederum stark veränderte – Auflage danke
ich Herrn Wilhelm Wall für seine wertvolle Hilfe, insbesondere Tests unter macOS.
Zuletzt danke ich auch ganz herzlich dem Hanser Verlag, der dieses Buch – nun auch in
der dritten Auflage – ermöglicht hat. Vor allem danke ich Frau Brigitte Bauer-Schiewek,
Programmplanung Computerbuch, für die kontinuierliche ausgezeichnete Unterstützung.
Für die technische Unterstützung bei LaTeX-Problemen danke ich Herrn Stephan Korell
und Frau Irene Weilhart. Herrn Jürgen Dubau danke ich fürs Lektorat.
Bernd Klein, Singen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
1
1.1
Einleitung
Einfach und schnell zu lernen
Python ist eine Programmiersprache, die in vielerlei Hinsicht begeistert. Ein riesiger Vorteil im Vergleich zu anderen Sprachen liegt in der leichten Erlernbarkeit der Sprache. In
der ersten Auflage führte das Buch den Untertitel „In einer Woche programmieren lernen”.
Kann man wirklich in so kurzer Zeit programmieren lernen? Das ist möglich! Vor allem mit
Python. Wir erfahren es mehrmals im Monat in unseren meist fünftägigen Python-Kursen.
Sie werden zum einen von totalen Programmieranfängern und zum anderen von Programmierern mit Erfahrungen in C, C++, Java, Perl und anderen Programmiersprachen besucht.
Manche haben auch schon vorher Erfahrungen in Python gesammelt. Aber eines ist in allen Gruppen gleich: Wir haben es immer geschafft, dass jeder programmieren gelernt hat,
und vor allen Dingen – was uns am wichtigsten ist – konnten wir immer die Begeisterung
für die Sprache Python entfachen.
So ist es auch eines der wichtigsten Ziele dieses Buches, die Leserinnen und Leser möglichst schnell und mit viel Freude zum selbständigen Programmieren zu bringen. Komplexe Sachverhalte werden in einfachen leicht verständlichen Diagrammen veranschaulicht
und an kleinen Beispielen eingeübt, die sich im Laufe vieler Schulungen herausgebildet
und bewährt haben.
1.2
Zielgruppe des Buches
Beim Schreiben eines Buches hat man ein Gegenüber vor sich, eine Person, die das Geschriebene lesen, mögen und verstehen soll. Diese Person kann sowohl eine Frau als auch
ein Mann sein, dennoch werden wir im Text meistens zum Beispiel nur vom „Benutzer”1
sprechen. Wörter wie „BesucherInnen” sehen unserer Meinung nach hässlich aus, und objektiv betrachtet erschweren sie die Lesbarkeit eines Textes. Ebenso hemmen Formulierungen der Art „als Benutzerin oder Benutzer gilt .... er oder sie .... sollten sein oder ihr ...”
den Lesefluss und lenken vom eigentlichen Ziel ab, also Python schnell und problemlos
1
als Übersetzung des englischen Fachbegriffs user
2
1 Einleitung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
zu erlernen. Dennoch versuchen wir, wo immer möglich, geschlechtsspezifische Formulierungen zu vermeiden, weil wir niemanden ausschließen wollen.
Natürlich hatten wir beim Schreiben aber nicht nur eine Person im Blickfeld, sondern eine
ganze Leserschar. Da sind zum einen diejenigen, die noch nie programmiert haben und
Sachverhalte erklärt haben wollen, die Leute mit Programmiererfahrung in anderen Sprachen vielleicht als „trivial” oder „selbstverständlich” bezeichnen würden. Aber hier ist ein
Buch wohl dem Präsenzunterricht, also wenn Lehrende und Lernende am gleichen Ort zusammen sind, deutlich überlegen: Ist einem der Stoff eines Abschnitts oder sogar Kapitels
bereits vertraut, kann man es einfach überspringen, bevor man sich zu langweilen beginnt.
Ebenso können, falls keine Kenntnisse in anderen Programmiersprachen vorhanden sind,
Abschnitte übersprungen werden, in denen wir Ähnlichkeiten, aber auch generelle Unterschiede in der Vorgehensweise von Python im Vergleich zu anderen Sprachen herausarbeiten.
In der obigen Aufzählung fehlt aber noch eine wichtige Gruppe, nämlich diejenigen, die
schon Erfahrungen mit Python haben. Dies ist eine Gruppe mit einer breiten Streuung:
angefangen bei denjenigen, die bereits ein wenig reingeschnuppert haben, gefolgt von solchen, die bereits kleine oder auch größere Programme geschrieben haben, bis hin zu jenen,
die sich als Experten bezeichnen. Unsere Erfahrungen zeigen, dass auch diese neue Einblicke und Sachverhalte in diesem Buch finden werden, die sich positiv auf deren zukünftige
Programmierung mit Python auswirken.
Ansonsten kann dieses Buch auch bestens als Nachschlagewerk benutzt werden. Der umfangreiche Index in diesem Buch macht das Auffinden besonders einfach und erlaubt es
damit, dieses Buch außerdem als Referenz zu verwenden, auch wenn es keinesfalls unter
diesem Aspekt geschrieben worden ist.
1.3
Aufbau des Buches
Dieses Buch besteht aus vier Teilen:
Teil I:
In ersten Teil behandeln wir die Grundlagen der Sprache. Wir lernen Variablen
und deren Besonderheit in Python kennen. In diesem Zusammenhang lernen
wir auch die Datenstrukturen wie ganze Zahlen (Integers), Fließkommazahlen
(Floats), Zeichenketten (Strings), Listen, Tupel, Dictionaries und Mengen kennen. Wie beschäftigen uns eingehend mit allen Anweisungsarten von Python,
d.h. Zuweisungen, Kontrollstrukturen wie bedingten Anweisungen und Schleifen
sowie Möglichkeiten der Ein- und Ausgabe. Außerdem behandeln wir eingehend
Funktionen, Module und Pakete.
Teil II:
Den zweiten Teil des Buches haben wir – entsprechend ihrer Bedeutung – ganz
der Objektorientierung gewidmet. In anschaulichen Beispielen führen wir sanft
in das objektorientierte Denken und Programmieren ein. Danach fahren wir mit
den fortgeschrittenen Themen der Objektorientierung fort und behandeln neben
Vererbung, Mehrfachvererbung und Slots auch extrem fortgeschrittene Themen
wie Metaklassen und abstrakte Klassen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
1.4 Programmieren lernen „interaktiv”
Teil III: Im dritten Teil geht es um äußerst wichtige Konzepte der Programmiersprache
Python, die alle kennen sollten, die Python wirklich verstehen wollen. Lernende
können diese Kapitel jedoch erst mal ignorieren, weshalb wir sie in einem separaten Teil zusammengefasst haben. Die Listenabstraktionen bieten ebenso wie
die lambda-, map-, filter- und reduce-Funktionen eine faszinierende Möglichkeit,
die Programmierung auf ein abstrakteres Level zu bringen. Dadurch kann man
komplexere Probleme mit geringerem Programmieraufwand lösen, was außerdem
zu einer besseren Verständlichkeit und einfacheren Wartbarkeit der Programme
führt. Dennoch kann man die gleichen Programme auch ohne diese Techniken
schreiben. Eingehend befassen wir uns auch mit dem Themenkomplex „Generatoren und Iteratoren” sowie „Dekoration und Dekorateure”!
Teil IV: In dem Teil mit dem Titel „Weiterführende Themen ” verlassen wir das „eigentliche” Python und wenden uns weiterführenden Themen der Programmierung zu.
Dennoch dürften vor allen Dingen die ersten drei Kapitel dieses Buchteils von
allgemeinem Interesse sein. Testverfahren und Debugging gehört zu den Fragestellungen, die alle Programmiererinnen und Programmierer bewegen sollten. Im
Prinzip hätten wir dieses Kapitel ebenso gut in den ersten Teil nehmen können.
Außerdem behandeln wir in einem Kapitel „Reguläre Ausdrücke”, die nahezu unerlässlich sind, wenn man Textverarbeitung effizient betreiben will. Man braucht
sie auch, wenn man aus Log- oder Parameterdateien bestimmte Informationen
herausfiltern will. Den ab Python 3.5 eingeführten Typanmerkungen (type annotations) haben wir auch ein eigenes Kapitel gewidmet. In einem weiteren Kapitel zeigen wir außerdem, wie man Daten strukturerhaltend übers Programmende
hinaus konservieren kann. Das Kapitel zur Systemprogrammierung dürfte von besonderer Bedeutung für Systemprogrammierer sein, die ihre Systemprogramme
zukünftig unter Python und nicht mehr mit Shell-Skripting verfassen wollen.
Teil V:
Programmieren lernen ist vor allen Dingen eine aktive Tätigkeit. Nur ein Buch
zu lesen und Beispiele nachzuvollziehen genügt nicht. Deshalb finden Sie zu den
meisten Kapiteln interessante und lehrreiche Übungsaufgaben, die den Stoff vertiefen. In diesem Teil des Buches finden Sie nun die ausführlichen Musterlösungen
mit Erläuterungen zu diesen Aufgaben.
1.4
Programmieren lernen „interaktiv”
Wie bereits erwähnt, floss in dieses Buch die jahrelange Erfahrung sowohl in der Theorie
und Praxis des Programmierens allgemein, aber vor allem auch die Vermittlung des Stoffes in zahlreichen kleinen und großen Kursen mit unterschiedlichsten Besuchertypen ein.
Aber ein Buch zu schreiben, stellt dennoch eine neue Herausforderung dar. Beim Buch
fehlt leider die direkte Interaktion zwischen dem, der das Wissen vermittelt, und dem Lernenden. Vor Ort kann man sofort sehen, wenn sich bei einem Teilnehmer die Stirn runzelt, große Fragezeichen erscheinen oder wenn sich jemand aus Stress das Ohrläppchen
zu reiben beginnt. Dann weiß man als erfahrener Dozent, dass es höchste Zeit ist, ein paar
zusätzliche Beispiele und Analogien zu verwenden, oder dass man den Stoff nochmals in
anderen – möglicherweise auch einfacheren – Worten erklären sollte. Beim Schreiben ei-
3
4
1 Einleitung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
nes Buches muss man diese möglichen Klippen vorhersehen und die nötigen zusätzlichen
Übungen und Beispiele an den entsprechenden Stellen bereitstellen. Aber was bei vielen
Lesern hilft, diese Klippen zu umgehen, führt bei anderen nun vielleicht zu Langeweile und
Ungeduld, denn sie empfinden diese zusätzlichen Erklärungen, Übungen oder Beispiele
möglicherweise als Zeit- oder Platzvergeudung.
Das Grundproblem ist, dass es sich bei einem Buch nicht um ein interaktives Medium handelt. Aber dank des Internets können wir Ihnen diese Interaktivität dennoch bieten. Im
nächsten Abschnitt finden Sie die Adressen, wo Sie Hilfe und zusätzliche Informationen
zum Buch finden.
1.5
Download der Beispiele und Hilfe
Alle im Buch verwendeten Beispiele finden Sie zum Download unter
http://www.python-kurs.eu/buch/beispiele/
Auch wenn wir das Buch so geschrieben haben, dass Sie ohne zusätzliche Hilfe auskommen sollten, wird es dennoch hier und da mal ein Problem geben, wo Sie sich vielleicht
festgebissen haben. Wenn das Glück es so will, dass Sie in einer Umgebung arbeiten, in
der es andere Python-Programmierer gibt, haben Sie es natürlich gut. Aber viele Leserinnen und Leser genießen diesen Vorteil nicht. Dann könnte sich ein Besuch unserer Website
besonders lohnen:
http://www.python-kurs.eu/buch/
Dort finden Sie ein Korrekturverzeichnis, zusätzliche bzw. aktualisierte Übungen und sonstige Hilfestellungen. Ansonsten bleibt natürlich immer noch der Einsatz einer Suchmaschine!
1.6
Anregungen und Kritik
Falls Sie glauben, eine Ungenauigkeit oder einen Fehler im Buch gefunden zu haben, können Sie auch gerne eine E-Mail direkt an den Autor schicken: klein@python-kurs.eu.
Natürlich gilt dies auch, wenn Sie Anregungen oder Wünsche zum Buch geben wollen. Leider können wir jedoch – so gerne wir es auch tun würden – keine individuellen Hilfen zu
speziellen Problemen geben.
Wir werden versuchen, Fehler und Anmerkungen in kommenden Auflagen zu berücksichtigen. Selbstverständlich aktualisieren wir damit auch unsere Informationen unter
http://www.python-kurs.eu/buch/
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Teil I
Grundlagen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
2
2.1
2.1.1
Kommandos und
Programme
Erste Schritte
Linux
Bei den meisten Linux-Distributionen ist Python bereits vorinstalliert, und damit ist auch
die interaktive Python-Shell direkt verfügbar. Der Interpreter wird üblicherweise im Verzeichnis /usr/bin/ (aber manchmal auch in /usr/local/bin) installiert, aber diese Information benötigen wir jetzt noch nicht.
Der einfachste Weg, um mit Python unter Linux zu arbeiten, besteht darin, dass man zuerst
ein Terminal startet wie beispielsweise ein „xterm” oder ein „gnome-terminal”.
Bild 2.1 Gnome-Terminal
In diesem Terminal – im Bild sehen Sie ein Gnome-Terminal – tippen Sie „python3” ein und
drücken die Eingabetaste. Damit starten Sie die interaktive Python-Shell. Python meldet
sich mit Informationen über die installierte Version. Der Interpreter steht nun mit dem sogenannten Eingabeprompt (>>>) für Eingaben bereit. Man kann jetzt beliebigen PythonCode eingeben, der nach dem Quittieren mit der Eingabetaste (Return-Taste) sofort ausgeführt wird.
8
2 Kommandos und Programme
deeplearning@deep-learning-virtual-machine:~$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Bitte beachten Sie, dass Sie keinesfalls die Ziffer 3 hinter „python” vergessen dürfen. Wenn
Sie nur „python” eingeben, starten Sie Python in einer 2er-Version.
deeplearning@deep-learning-virtual-machine:~$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
Um die Beispiele in unserem Buch nachvollziehen zu können, benötigen Sie jedoch Python
in einer 3er-Version.1
2.1.2
Windows
Unter Windows muss Python erst installiert werden. Die neuesten Versionen findet man
unter www.python.org/downloads.
Bild 2.2 Python starten unter Windows
1
Also eine Version 3.x, d.h. Python 3.0, 3.1, 3.2, 3.3 usw.
2.2 Herkunft und Bedeutung des Begriffes interaktive Shell
Nachdem Python installiert worden ist, findet man unter „Alle Programme” einen Eintrag
„Python 3.5” oder „Python 3.6”2 , unter dem sich die Untereinträge „IDLE (Python GUI)”
und „Python 3.6 (interaktive Python-Konsole, auch Shell genannt) befinden – siehe Bild 2.2.
Um die nachfolgenden Beispiele und Übungen nachvollziehen zu können, starten Sie bitte
die Python-Konsole, also „Python 3.6” (bzw. Python 3.5).
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Alternativ kann man auch mit IDLE arbeiten. Dabei handelt es sich um eine Mini-Entwicklungsumgebung, auf die wir hier jedoch nicht eingehen wollen.
2.1.3
macOS
Die Installation von Python auf einem Macintosh unter macOS verläuft ähnlich wie bei
anderen Unix- und Linux-Plattformen. Die neueren macOS-Versionen werden bereits mit
einem vorinstallierten Python ausgeliefert. Allerdings handelt es sich dabei um eine 2.7Version. Für das Buch benötigen wir jedoch eine 3er-Version von Python. Die neueste Version kann man sich auch von der python.org-Website unter www.python.org/downloads
herunterladen.
Nach der Installation von Python3 kann man, wie bei Linux auch, ein Terminal starten und
dort mit der Eingabe von python3 die interaktive Shell starten.3
Bild 2.3 Python starten unter macOS
2.2
Interaktive Shell
Wenn die Aktivitäten des vorigen Kapitels erfolgreich waren, sollten Sie sich nun sowohl
unter Linux als auch unter Windows in der interaktiven Python-Shell befinden.
Der Begriff „interaktiv” kommt vom lateinischen „inter agere”. Das Verb „agere” bedeutet
unter anderem „tun”, „treiben”, „betreiben”, „handeln”, und „inter” bezeichnet die zeitliche und örtliche Position zu Dingen und Ereignissen, also „zwischen” zwei oder „inmitten”
2
3
Je nach Installation könnte hier auch 3.4 oder eine ältere Version stehen!
Wie man auf dem Bild sieht, haben wir das Anaconda-Paket der Firma Continuum installiert, da es noch
über viele zusätzliche Pakete verfügt. Diese Pakete werden allerdings im Rahmen dieses Buches nicht benötigt.
9
10
2 Kommandos und Programme
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
(oder „unter”) vielen Objekten, Personen oder Ereignissen sein. Also heißt „inter agere” dazwischen handeln. In diesem Sinne steht die interaktive Shell zwischen dem Anwender und
dem Betriebssystem (Linux, Unix, Windows oder anderen) bzw. dem zu interpretierenden
Programm (z.B. Python). Die interaktive Shell steht aber auch zeitlich zwischen den einzelnen Aktionen, d.h. zwischen den einzelnen Befehlen und Kommandos. Der Benutzer gibt
einen Befehl ein, die Shell führt diesen unverzüglich aus, liefert sofort das Ergebnis und
wartet dann auf den nächsten Befehl.
Im Englischen steht das Wort shell für eine Schale, einen Panzer oder ganz allgemein eine
Hülle oder Schutzhülle. shell bezeichnet auch ein Schneckenhaus und das Gehäuse, das eine Muschel umschließt. Ebenso liegt eine Shell auch zwischen einem Betriebssystem und
dem Benutzer. Wie eine Muschelschale schützt sie einerseits das Betriebssystem vor dem
Benutzer, und andererseits erspart sie dem Benutzer die Benutzung der „primitiven” und
schwer verständlichen Basisfunktionen, indem es ihm komfortable Befehle zur Kommunikation mit dem Computer zur Verfügung stellt.
Auch die Programmiersprache Python bietet dem Anwender eine komfortable Kommandozeilenschnittstelle, die sogenannte Python-Shell, manchmal auch als interaktive
Python-Shell bezeichnet. Man könnte meinen, dass es sich bei dem Begriff „interaktive
Shell” um eine Tautologie handelt, da ja, so wie oben beschrieben, Shells immer interaktiv
sind. Dies ist jedoch nicht so: Es gibt auch vor allem im Umfeld von Linux und Unix Shells,
die als Subshell aufgerufen und nicht interaktiv ausgeführt werden.
2.2.1
Erste Schritte in der interaktiven Shell
Wir nehmen nun an, dass Sie entweder unter Linux oder unter Windows vor einer lauffähigen Python-Shell sitzen, die mit blinkendem Cursor hinter dem Prompt „>>>” auf Ihre
Eingabe wartet.
Unsere Experimente mit der Python-Shell starten wir mit der Eingabe eines beliebigen
arithmetischen Ausdrucks wie z.B. „4 * 5.2” oder „4 * 12 / 3”. Ein Ausdruck wird mit dem
Drücken der Eingabetaste (Return) sofort ausgeführt, und das Ergebnis wird in der nächsten Zeile ausgegeben:
>>> 4 * 5.3
21.2
>>> 4 * 12 / 3
16.0
Die interaktive Shell erlaubt eine Bequemlichkeit, die innerhalb eines Python-Skripts nicht
zulässig ist. In einem Programm hätten wir das Ergebnis des obigen Ausdrucks nur mittels
einer print-Anweisung ausgeben können:
>>> print(4 * 5.3)
21.2
Python kennt wie die meisten anderen Programmiersprachen die Regel „Punktrechnung
geht vor Strichrechnung”, wie wir im folgenden Beispiel sehen können:
2.3 Verlassen der Python-Shell
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> 1 + (42 * 2)
85
>>> 1 + 42 * 2
85
>>>
>>> (1 + 42) * 2
86
Der jeweils letzte Ausgabewert wird vom Interpreter in einer speziellen Variablen automatisch gespeichert. Der Name der Variable ist einfach ein Unterstrich, also „_”. Das Ergebnis
der letzten Berechnung kann man sich also damit wieder ausgeben lassen:
>>> _
86
Der Unterstrich kann im Prinzip wie eine normale Variable benutzt werden:
>>> _ * 3
258
Dies gilt allerdings nicht in einem Python-Skript oder Python-Programm! In einem PythonProgramm hat der Unterstrich nicht diese Bedeutung.
Auch Strings lassen sich mit oder ohne print in der interaktiven Shell ausgeben:
>>> print("Hello World")
Hello World
>>> "Hello World"
'Hello World'
Die Python-Shell bietet noch einen Komfort, den man von den Linux-Shells gewöhnt ist.
Es gibt einen Befehlsstack, in dem alle Befehle der aktuellen Sitzung gespeichert werden.
Mit den Tasten „↑” und „↓” kann man sich in diesem Stack nach oben („↑”), also zu älteren
Befehlen, und nach unten („↓”), also wieder in Richtung neuester Befehl, bewegen.
2.3
Verlassen der Python-Shell
Wir haben zwar gerade erst damit begonnen, in
der Python-Shell „herumzuspielen”, dennoch wollen sicherlich einige das Programm bereits verlassen. Ctrl-C – was vielen Linux- und Unix-Benutzern
häufig als Erstes einfällt – funktioniert nicht! Den
Python-Interpreter kann man per Kontrollsequenz
nur mit Ctrl-D wieder verlassen. Alternativ dazu
kann man die Python-Shell mittels Eingabe der
Funktion exit() verlassen. Die Klammern hinter exit
sind ab Python 3 zwingend notwendig.
Bild 2.4 Emergency Exit
11
12
2 Kommandos und Programme
Alle Einstellungen, wie beispielsweise Variablen oder eingegebene Funktionsdefinitionen,
gehen beim Verlassen der Python-Shell verloren und sind beim nächsten Start nicht mehr
vorhanden.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
2.4
Benutzung von Variablen
In der Python-Shell kann man auch ganz einfach Variablen benutzen. Wenn Sie genau wissen wollen, was es mit Variablen und Datentypen auf sich hat, empfehlen wir, in Kapitel 4
(Datentypen und Variablen) weiterzulesen.
Das folgende Beispiel wendet sich an Benutzer, die bereits ein wenig Erfahrung mit Programmierung haben. Man kann also ganz einfach Werte in Variablen speichern. Variablennamen bedürfen keiner besonderen Kennzeichnung wie in anderen Sprachen wie zum Beispiel das Dollarzeichen in Perl:
>>> maximal = 124
>>> breite = 94
>>> print(maximal - breite)
30
2.5
Mehrzeilige Anweisungen
Bis jetzt haben wir noch nicht über mehrzeilige Anweisungen gesprochen. Anfänger werden hier vielleicht Probleme haben, da noch einige Grundlagen zum Verständnis fehlen,
und sollten deshalb besser mit dem folgenden Kapitel weitermachen.
Wir demonstrieren, wie die interaktive Shell mit mehrzeiligen Anweisungen wie zum Beispiel For-Schleifen umgeht.
>>> l = ["A",42,78,"Just a String"]
>>> for character in l:
...
print(character)
...
A
42
78
Just a String
Achtung: Nachdem man die Zeile „for character in l:” eingetippt hat, wartet die interaktive Shell im Folgenden auf eingerückte Zeilen. Dies teilt uns die Shell mit einem neuen
Prompt mit: Statt dem gewohnten „>>>” erhalten wir nun eine Folge von drei Punkten „...”.
Wir müssen also alle Anweisungen, die mit der Schleife ausgeführt werden sollen, um die
gleiche Anzahl von Leerzeichen einrücken, mindestens jedoch ein Leerzeichen! Wenn wir
fertig sind, müssen wir nach der letzten Code-Zeile zweimal die Eingabetaste drücken, bevor die Schleife wirklich ausgeführt wird.
2.6 Programme schreiben oder schnell mal der Welt “Hallo” sagen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
2.6
Programme schreiben
Wir haben bisher ein wenig mit der interaktiven Python-Shell herumprobiert,
aber noch kein richtiges Programm geschrieben. Bei der Programmierung ist
es generell wichtig, möglichst in kleinen
Schritten vorzugehen. Deshalb wird unser erstes Programm in Python auch nur
sehr wenig tun. Das Programm soll lediglich einen kurzen Text ausgeben, nachdem es gestartet worden ist. Für diesen Text wird in fast allen Einführungen
zu Programmiersprachen die Zeichenkette4 „Hallo Welt” oder auf Englisch „Hello
World” verwendet. Wir wollen dieser Tradition folgen.
Bild 2.5 Hallo Welt
Im Folgenden wollen wir zeigen, was in der Programmiersprache C++ nötig ist, um ein so
kleines Programm zum Laufen zu bringen. Sie können diesen kurzen Exkurs in die Programmiersprache C++ bedenkenlos überspringen. Wir bringen dieses Beispiel nur, damit
man später erkennen kann, wie leicht es ist, in Python im Vergleich zu C++ zu programmieren.
Folgender Programmcode ist in C++ nötig, um ein Programm zu schreiben, was nach dem
Start nur den Text „Hello World” in einer Zeile ausgibt:
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!\n";
return 0;
}
Wenn man dieses Programm in einer Datei gespeichert hat, also z.B. „hello.cpp”, muss man
das Programm compilieren, um ein lauffähiges Programm zu erhalten.5 Dies geschieht mit
folgendem Befehl, der ein lauffähiges Programm unter dem Namen „hello” erzeugt:
bernd@saturn:~/bodenseo/python/beispiele$ g++ -o hello hello.cpp
Im Folgenden starten wir das Programm „hello” und erhalten die gewünschte Ausgabe:
bernd@saturn:~/bodenseo/python/beispiele$ ./hello
Hello World!
bernd@saturn:~/bodenseo/python/beispiele$
4
5
Eine Folge von Zeichen wird als „Zeichenkette” oder meistens als „String” bezeichnet.
Näheres zu Compiler, Interpreter, Maschinen und Skriptsprachen erfahren Sie in Kapitel 3 (Bytecode und
Maschinencode).
13
14
2 Kommandos und Programme
Deutlich einfacher können wir ein solches Programm unter Python erstellen.
Schreiben Sie in einem Editor6 Ihrer Wahl die folgende Zeile und speichern Sie sie unter
dem Namen „hello.py” ab:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
print("Hello World")
Unter Linux oder Unix starten wir dieses Programm am einfachsten, wenn wir uns in einem
Terminal in das Verzeichnis begeben, in dem wir die Datei hello.py abgespeichert haben.
Dort können wir dann das Programm mit Kommando „python3 hello.py” ausführen. Im
Folgenden sehen wir dieses Vorgehen inklusive Ergebnis. Dabei nehmen wir an, dass Sie
die Datei in einem Unterverzeichnis python_beispiele Ihres Homeverzeichnisses abgespeichert hatten:
bernd@venus:~$ cd ~/python_beispiele/
bernd@venus:~/python_beispiele$ python3 hello.py
Hallo Welt
Unter Windows können wir ähnlich vorgehen. Wir starten erst das Programm „Eingabeaufforderung”. Dort bewegen wir uns in unser Verzeichnis mit den Python-Programmen,
in das wir unser hello.py abgespeichert haben.
Bild 2.6 Hallo Welt unter Python in der Windows-Eingabeaufforderung
Wir haben gesehen, dass die nötige Syntax7 unter Python deutlich geringer ist als unter
C++, d.h. man muss lediglich den Text mit einer print-Funktion aufrufen. Anschließend ist
auch kein explizites Compilieren8 nötig. Man kann das Programm einfach so starten.
6
7
8
Unter Windows könnte das beispielsweise „Notepad” sein, unter Linux oder einem anderem Unix-System
empfiehlt sich der vi, emacs, kate oder gedit.
Etymologisch leitet sich das Wort Syntax aus dem altgriechischen Wort „syntaksis” ab. „syn” bedeutet „zusammen”, und „taksis” steht für „Reihenfolge” oder „Ordnung”. Unter Syntax versteht man in der Linguistik
die Lehre vom Satzbau, also sozusagen die „zusammen”- oder „gesamt”-Reihenfolge der einzelnen Satzteile. In der Informatik versteht man unter der Syntax einer Programmiersprache die Regeln oder das System
von Regeln, mit denen man gültige Programme der Sprache beschreiben kann.
Man beachte das Wort „explizit”. Beim Start des Programmes wird nämlich implizit gegebenenfalls eine
Compilierung vorgenommen. Auf diese gehen wir im nächsten Kapitel näher ein.
2.6 Programme schreiben oder schnell mal der Welt “Hallo” sagen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Fragen zum Verständnis:
■
Wie kann man ein Python-Programm schreiben?
■
Wie startet man ein Python-Programm?
■
Wie kann man das Produkt aus 3.5 und 7.8 am einfachsten mit Python bezeichnen?
15
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
3
3.1
Bytecode und
Maschinencode
Einführung
Dieses Kapitel können Sie beim Einstieg in die Materie auch gerne überspringen, da das
Verständnis für die folgenden Kapitel nicht dringend notwendig ist. Hier geht es um allgemeine Fragen: Handelt es sich bei Python um eine Programmier- oder eine Skriptsprache?
Wird ein Python-Programm übersetzt oder interpretiert? Kann der gleiche Python-Code
auf verschiedenen Rechnern oder Betriebssystemen laufen? Worin liegt der Zusammenhang zwischen Python, Jython oder Java?
3.2
Unterschied zwischen Programmierund Skriptsprachen
Die Frage, worin der Unterschied zwischen Programmier- und Skriptsprachen liegt, lässt
sich nicht einfach beantworten. Sehr häufig werden die Begriffe Skript und Programm nahezu synonym verwendet. Es gibt Fälle, in denen es eindeutig ist: Hat man beispielsweise
ein Problem in C gelöst, so wird allgemein das Ergebnis als „Programm” bezeichnet. Kaum
jemand würde ein C-Programm als Skript bezeichnen. Bei Shells wie z.B. der Bourne- oder
der Bash-Shell spricht man nahezu immer von Skripten und nicht von Programmen. Würde man von einem Bourne-Shell-Programm sprechen, so klingt das mindestens so falsch,
als bezeichnete man ein Fahrrad als Moped. Worin der Unterschied zwischen einem ShellSkript und einem Programm liegt, wird in Tabelle 3.1 auf der nächsten Seite gezeigt.
3.3
Interpreter- oder Compilersprache
Unter einer Compilersprache versteht man eine Programmiersprache, deren Programme
vor ihrer Ausführung vollständig in Maschinencode, also Binärcode, übersetzt werden.
Dies kann leicht zu Verwechslungen führen, da manchmal auch die Sprache, in der ein
Compiler geschrieben wird, als Compilersprache bezeichnet wird. Nehmen wir an, dass
18
3 Bytecode und Maschinencode
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Tabelle 3.1 Unterschiede zwischen Skripten und Programmen
Skripte
Programme
Ein Skript besteht typischerweise
nur aus „wenigen” Zeilen Code,
häufig weniger als 100 Zeilen.
Programme können sehr klein sein, also beispielsweise
weniger als Hundert Zeilen Code, aber sie können auch
aus mehreren Tausend oder gar Millionen Codezeilen
bestehen.
Skripte werden unmittelbar
ausgeführt. Sie werden nicht
übersetzt (bzw. compiliert),
sondern interpretiert.
Programme in Programmiersprachen wie C und C++
müssen immer in Maschinencode übersetzt werden,
bevor sie ausgeführt werden können.
wir eine Programmiersprache XY1 hätten, für die es einen Compiler cXY gibt. Dann bezeichnet man XY als eine Compilersprache, weil sie mittels cXY in Maschinencode übersetzt wird. Wurde nun der Compiler2 cXY in der Programmiersprache C geschrieben, dann
sagt man „C ist die Compilersprache von XY”.
Bei einer Interpretersprache werden die Programme Befehl für Befehl interpretiert und
ausgeführt.
Die Programmiersprachen C und C++ sind reine Compilersprachen, d.h. jedes Programm
muss zuerst in Maschinencode übersetzt werden, bevor es ausgeführt werden kann. Sprachen wie Basic und VB werden interpretiert.
Man liest leider allzu häufig, dass es sich bei Python um eine interpretierte Sprache handelt. Dies ist nicht richtig. Aber bei Python handelt es sich auch nicht um eine Compilersprache im „klassischen” Sinne. Python-Programme werden sowohl übersetzt als auch
interpretiert. Startet man ein Python-Programm, wird zuerst der Programmcode in Bytecode übersetzt. Bei Bytecode handelt es sich um Befehle für eine virtuelle Maschine. Bei
Python spricht man von der PVM (Python Virtual Machine).3 Dieser Code ist unabhängig
von realer Hardware, aber kommt dem Code von Maschinensprachen schon relativ nahe.
Dieser Bytecode wird dann für einen bestimmten Maschinencode interpretiert. Das bedeutet, dass der Bytecode plattformunabhängig ist.
Bild 3.1 Python: Compiler und Interpreter
Importiert man ein Python-Modul, so wird dieses nicht nur für diesen Lauf kompiliert, sondern auch dauerhaft gespeichert. Dadurch kann Python in einem späteren Lauf auf bereits
compilierten Code zurückgreifen. Dies natürlich nur, falls sich der Quellcode des Moduls
nicht verändert hat. Nun stellt sich die Frage, wo – also in welchem Verzeichnis – Python
1
2
3
Nach unserem Kenntnisstand gibt es keine Programmiersprache mit diesem Namen.
Beim Compiler handelt es sich auch um ein Programm.
Dieses Konzept entspricht der Vorgehensweise bei Java. Auch dort wird der Code zuerst in Bytecode übersetzt, d.h. Code für die JVM (Java Virtual Machine).
3.3 Interpreter- oder Compilersprache
den Bytecode speichert. Sie werden in dem Verzeichnis, in dem sich Ihr Modul befindet, ein
Verzeichnis __pycache__ finden. Innerhalb dieses Programms finden Sie dann die compilierten Module, die Sie an der Endung .pyc erkennen können.4
Es ist einfach, obiges selbst zu testen. Dazu erzeugen wir eine Datei „beispiel.py” mit folgender Zeile als Inhalt:
print("Irgendetwas muss ich halt sagen!")
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Geben Sie dann im Python-Interpreter folgende Zeile ein:
deeplearning@deep-learning-virtual-machine:~/tmp$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import beispiel
Irgendetwas muss ich halt sagen!
In dem Unterverzeichnis __pycache__ befindet sich nun eine Datei
beispiel.cpython-35.pyc.
4
In den Python 2.x-Versionen wurden die compilierten Versionen im gleichen Verzeichnis wie die Module
abgespeichert!
19
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
4
4.1
Datentypen und Variablen
Einführung
Sie glauben, weil Sie bereits in Sprachen wie C und C++ programmiert haben, dass Sie
bereits genug über Datentypen und Variablen wissen? Vorsicht ist geboten! Sie wissen sicherlich viel, aber wahrscheinlich nicht genug, wenn es um Python geht. Deshalb lohnt es
sich auf jeden Fall, hier weiterzulesen. Es gibt gravierende Unterschiede zwischen Python
und anderen Programmiersprachen in der Art, wie Variablen behandelt werden. Vertraute Datentypen wie Ganzzahlen (Integer), Fließkommazahlen (floating point numbers) und
Strings sind zwar in Python vorhanden, aber auch hier gibt es wesentliche Unterschiede zu
C/C++ und Java. Dieses Kapitel ist also sowohl für Anfänger als auch für fortgeschrittene
Programmierer zu empfehlen.
Eine Variable ist ein Name bzw. ein Bezeichner, mit dem man auf ein Objekt zugreifen kann.1 Ein Objekt steht zwar an
einem bestimmten Speicherplatz, aber in
Python spricht und denkt man nicht in
physikalischen Speicherplätzen. Eine Variable ist nicht einem festen Objekt oder
Speicherplatz zugeordnet. Während des
Programmablaufs können einem Variablennamen neue beliebige Objekte zugewiesen werden, d.h. die Variable referenziert nach jeder Zuweisung in den meisten Bild 4.1 Variablen sind Referenzen auf Objekte
Fällen einen neuen Speicherort. Diese Objekte können beliebige Datentypen sein. Eine Variable referenziert also ein Objekt in Python.
1
In den meisten Programmiersprachen ist es so, dass eine Variable einen festen Speicherplatz bezeichnet,
in dem Werte eines bestimmten Datentyps abgelegt werden können. Während des Programmlaufs kann
sich der Wert der Variable ändern, aber die Wertänderungen müssen vom gleichen Typ sein. Also kann
man nicht in einer Variable zu einem bestimmten Zeitpunkt eine Integer-Zahl gespeichert haben und dann
diesen Wert durch eine Fließkommazahl überschreiben. Ebenso ist der Speicherort der Variablen während
des gesamten Laufs konstant, kann also nicht mehr geändert werden.
22
4 Datentypen und Variablen
Im Folgenden kreieren wir eine Variable x in der interaktiven Python-Shell, indem wir ihr
einfach den Wert 42 unter Verwendung eines Gleichheitszeichens zuweisen:
>>> x = 42
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Man bezeichnet dies als Zuweisung oder auch als Definition einer Variablen. Genau genommen müsste man sagen, dass wir ein Integer-Objekt „42” erzeugen und es mit dem
Namen x referenzieren.
Anmerkung: Obige Anweisung darf man nicht als mathematisches Gleichheitszeichen sehen, sondern als „der Variablen x wird der Wert 42 zugewiesen”, d.h. der Inhalt von x ist
nach der Zuweisung 42. Man kann die Anweisung also wie folgt „lesen”: „x soll sein 42”.
Wir können nun diese Variable zum Beispiel benutzen, indem wir ihren Wert mit der Funktion print ausgeben lassen:
>>> print("Wert von x: ", x)
Wert von x: 42
Wir können sie aber auch auf der rechten Seite einer Zuweisung verwenden:
>>> y = x
Beide Variablen zeigen nun auf das gleiche Objekt, d.h. das int-Objekt 42:
Woher wissen wir oder noch besser, wie kann man beweisen, dass beide Variablen auf das
gleiche Objekt zeigen? Dazu bietet sich die id-Funktion an. Dies ist eine Funktion, die für
ein Objekt die Identität eines Objektes zurückgibt. Erhält man für zwei Variablennamen die
gleiche Identität, so weiß man, dass sie das gleiche Objekt referenzieren. Wir demonstrieren dies im Folgenden:
>>> x = "Ich bin ein String"
>>> y = "Ich auch und ich habe nichts mit x zu tun!"
>>> id(x)
139774036111128
>>> id(y)
139774048560016
>>>
>>> y = x
>>> id(y)
139774036111128
4.2 Variablennamen
Auf der rechten Seite einer Zuweisung kann natürlich auch ein zu berechnender Ausdruck
stehen, dessen Ergebnis dann der Variablen auf der linken Seite zugewiesen wird:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> y = y + 36
>>> print(y)
78
Wie man im letzten Beispiel
sehen kann, ist es möglich,
eine Variable gleichzeitig auf
der linken und rechten Seite einer Zuweisung zu benutzen.
Für
die
Schreibweise
„y = y + 36”
verwendet
man üblicherweise die sogenannte erweiterte Zuweisung „y += 36”.
Vom Ergebnis her gesehen
sind die beiden Möglichkeiten identisch. Allerdings gibt
es einen Implementierungsunterschied. Bei der erweiterten Zuweisung muss die Referenz der Variablen y nur einmal ausgewertet werden.2
Erweiterte Zuweisungen gibt es auch für die meisten anderen Operatoren wie „-”, „*”, „/”,
„**”, „//” (ganzzahlige Division) und „%” (Modulo).
4.2
Variablennamen
Wenn wir uns beim Programmieren Variablennamen ausdenken, muss man zweierlei beachten: Zum einen muss der Name den syntaktischen Anforderungen der Programmiersprache genügen, und zum anderen muss er den üblichen Gepflogenheiten und Konventionen entsprechen.
4.2.1
Gültige Variablennamen
Variablennamen müssen mit einem Buchstaben oder Unterstrich „_” beginnen. Die folgenden Zeichen dürfen sich aus einer beliebigen Folge von Buchstaben, Ziffern und dem
Unterstrich zusammensetzen. Variablennamen sind case-sensitive.3 Dies bedeutet, dass
Python zwischen Groß- und Kleinschreibung unterscheidet:
2
3
Bei Datentypen wie Listen kann es zu extremen Laufzeitproblemen kommen, wenn man keine erweiterte
Zuweisung verwendet. Darauf gehen wir jedoch erst später ein.
Für diesen Begriff gibt es keine deutsche Übersetzung.
23
24
4 Datentypen und Variablen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> person_42 = "Marvin"
>>> Person_42 = "Arthur"
>>> print(person_42)
Marvin
>>> print(Person_42)
Arthur
4.2.2
Konventionen für Variablennamen
Man bevorzugt in der Python-Community Kleinschreibung bei Variablennamen, z.B.
minimum statt Minimum. Außerdem werden Variablennamen, die aus mehreren Wörtern
bestehen, mittels Unterstrich (_) separiert, also beispielsweise minimale_breite. Variablennamen mit Binnenversalien – bei Programmierern besser als „camel case” oder
„CamelCase” bekannt – sind in der Python-Welt nicht beliebt: MinimaleBreite oder
minimaleBreite.
Ansonsten zeichnet sich ein guter Programmierstil dadurch aus, dass man möglichst sprechende Variablennamen verwendet. Also beispielsweise aktueller_kontostand oder
maximale_beschleunigung statt nichtssagender Namen wie ak oder mb.
4.3
4.3.1
Datentypen
Ganze Zahlen
Ganze Zahlen werden in der Informatik üblicherweise als Integer bezeichnet. Es handelt
sich dabei um die Zahlen ... -3, -2, -1, 0, 1, 2, 3, ...
Während Integer-Zahlen in den meisten Programmiersprachen durch einen Maximalwert
begrenzt sind, sind sie in Python unbegrenzt, d.h. sie können beliebig groß bzw. beliebig
klein werden. Die Variable unsigned_long_max bezeichnet in der folgenden interaktiven
Python-Session den maximalen Integer-Wert in C für „unsigned long”. Ein Wert, der in C
für diesen Datentyp nicht überschritten werden kann. Wir sehen, dass wir in Python diesen
Wert sogar ohne Fehler beispielsweise quadrieren können.
>>> unsigned_long_max = 4294967295
>>> unsigned_long_max * unsigned_long_max
18446744065119617025
>>> x = 7876473829887890878787823666656543423341430089091000654
>>> x * x
6203883999290881978134855892120878218991606511525246666222414202883
0910197615930689481485927796837531028427716
Bei Integer-Zahlen muss man beachten, dass sie nicht mit einer 0 beginnen dürfen, wenn
es sich um eine Zahl im Dezimalsystem handeln soll:
4.3 Datentypen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> 42
42
>>> 042
File "<stdin>", line 1
042
^
SyntaxError: invalid token
Die führende Null wird benötigt, wenn man Binär-, Oktal- oder Hexadezimalzahlen schreiben möchte. Literale für Binärzahlen beginnen mit der Sequenz „0b” oder „0B” und werden
dann von der eigentlichen Binärzahl gefolgt. Die Binärzahl „101010” schreibt man also wie
folgt:
>>> x = 0b101010
>>> x
42
Man sieht, dass dies keine Auswirkung auf die Interndarstellung der Zahl hat.
Literale für Oktalzahlen beginnen mit der Sequenz „0o” oder „0O” und werden dann von
der eigentlichen Oktalzahl gefolgt:
>>> x = 0o10
>>> x
8
Literale für Hexadezimalzahlen beginnen mit der Sequenz „0x” oder „0X” und werden
dann von der eigentlichen Hexzahl gefolgt:
>>>
>>>
16
>>>
>>>
26
x = 0x10
x
x = 0x1A
x
Mit den Funktionen hex, bin, oct kann man eine Integer-Zahl in einen String wandeln, der
der Stringdarstellung der Zahl in der entsprechenden Basis entspricht:
>>> x = hex(19)
>>> x
'0x13'
>>> type(x)
<class 'str'>
>>> bin(65)
'0b1000001'
>>> oct(65)
'0o101'
>>> oct(0b101101)
'0o55'
25
26
4 Datentypen und Variablen
4.3.2
Fließkommazahlen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Fließkommazahlen4 entsprechen dem Datentyp float in Python. Es handelt sich dabei um
Zahlen der Art 2.34, 27.87878 oder auch 3.15e2:
>>> x = 2.34
>>> y = 3.14e2
>>> y
314.0
Achtung: In deutschsprachigen Ländern werden die Nachkommastellen im täglichen Leben mit einem Komma eingeleitet. So schreibt man beispielsweise 1,99, wenn ein Artikel
1,99 € kostet. In Python und in anderen Programmiersprachen wird statt des Kommas immer ein Punkt, der sogenannte Dezimalpunkt, benutzt! Wie man aus obigem Beispiel ersehen kann, entspricht die Zahl „3.14e2” dem Ausdruck 3.14 · 102 .
4.3.3
Zeichenketten
Wir werden im nachfolgenden Kapitel weiter auf Strings eingehen.
4.3.4
Boolesche Werte
Bei den booleschen Werten handelt es sich um einen Datentyp, der nur die zwei Werte True5 (wahr) oder False (falsch) annehmen kann. Allerdings entspricht das im Prinzip
den numerischen Werten 1 für True und 0 für False. Damit lässt sich mit booleschen Werten auch „numerisch” rechnen, auch wenn das nicht immer gutem Programmierstil entspricht:
>>> x = True
>>> x * 4
4
Für zwei boolesche Variablen x und y gilt Folgendes:
Tabelle 4.1 Logisches UND und ODER
4
5
Operator
Erklärung
not y
Negierung von y
x and y
Logisches UND von x und y
x or y
Logisches ODER von x und y
Sie werden manchmal auch als Gleitpunktzahlen bezeichnet.
Ein beliebter Anfangsfehler besteht darin, dass man nicht auf die Großschreibung der beiden Werte achtet!
4.3 Datentypen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispielanwendungen:
>>> x = True
>>> not x
False
>>> y = False
>>> x and y
False
>>> x or y
True
>>> x and not y
True
4.3.5
Komplexe Zahlen
Python bietet einen Datentyp „complex” für komplexe Zahlen, den man bei den meisten
Programmiersprachen vergeblich sucht. Allerdings würden ihn die meisten Anwender von
Python wohl auch nicht vermissen. Wenn Sie komplexe Zahlen nicht kennen oder nichts
mit ihnen zu tun haben, können Sie das Folgende gerne überspringen. Mathematisch gesehen erweitern die komplexen Zahlen die reellen Zahlen derart, dass die Gleichung x 2 +1 = 0
lösbar wird. In der Mathematik werden komplexe Zahlen meist in der Form a + b · i dargestellt, wobei a und b reelle Zahlen sind und i die imaginäre Einheit ist. In Python benutzt
man, der Konvention in der Elektrotechnik folgend, ein „j” als imaginäre Einheit.
>>> x = 3 + 4j
>>> y = 2 - 4.5j
>>> x + y
(5-0.5j)
>>> x * y
(24-5.5j)
4.3.6
Operatoren
„//” bezeichnet die ganzzahlige Division, d.h. 11 // 4 ergibt nicht den exakten Floatwert
2.75 , sondern den Integer-Wert 2, d.h. es wird immer auf die nächste ganzzahlige Integer „abgerundet”. Dies gilt allerdings nur für den Fall, dass beide Operanden ganze Zahlen
(int) sind! Ist mindestens einer der Operanden eine float-Zahl, wird auch auf die nächste
ganzzahlige Integer-Zahl abgerundet, aber dann das Ergebnis in float gewandelt:
>>>
2
>>>
3
>>>
4
>>>
4.0
8 // 3
11 // 3
12 // 3
12.0 // 3
27
28
4 Datentypen und Variablen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Tabelle 4.2 Erklärung zu Operatoren
Operator
Erklärung
x+y
Summe von x und y
x-y
Differenz von x und y
x*y
Produkt von x und y
x/y
Quotient von x und y
x // y
Ganzzahlige Division
x%y
Modulo- oder Restdivision
abs(x)
Betrag von x
x ** y
Potenzieren, also x hoch y
Mit % lässt sich der Rest bei der Integerdivison bestimmen: So ergibt 11 % 4 den Wert 3 und
10 % 4 den Wert 2.
>>> 8 % 3
2
>>> 9 % 3
0
>>> 8.0 % 3
2.0
Es gilt folgender Zusammenhang zwischen der ganzzahligen Division und dem ModuloOperator:
>>> x = 24
>>> y = 7
>>> x == (x // y) * y + (x % y)
True
4.4
Statische und dynamische
Typdeklaration
Wer Erfahrungen mit C, C++, Java oder ähnlichen Sprachen hat, hat dort gelernt, dass man
einer Variablen einen Typ zuordnen muss, bevor man sie verwenden darf. Der Datentyp
muss im Programm festgelegt werden – und zwar, bevor die Variable zum ersten Mal benutzt oder definiert wird. Dies sieht dann beispielsweise in einem C-Programm so aus:
int i, j;
float x;
x = i / 3.0 +5.8;
Während des Programmlaufs können sich dann die Werte für i, j und x ändern, aber ihre Typen sind für die Dauer des Programmlaufs auf int im Falle von i und j und float für
4.4 Statische und dynamische Typdeklaration
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
die Variable x festgelegt. Dies bezeichnet man als „statische Typdeklaration”, da bereits der
Compiler den Typ festlegt und während des Programmablaufs keine Änderungen des Typs
mehr vorgenommen werden können.
In Python sieht dies anders aus, wie wir bereits
die ganze Zeit gesehen haben. Zunächst einmal bezeichnen Variablen in Python keinen bestimmten
Typ, und deshalb benötigt man in Python keine Typdeklaration. Benötigt man im Programm beispielsweise eine Variable i mit dem Wert 42 und dem Typ
Integer, so erreicht man dies einfach mit der Anweisung „i = 42”.
Bild 4.2 Unterschiedliche Typen
Damit hat man automatisch ein Objekt vom Typ Integer angelegt, welches dann von der
Variablen i referenziert wird. Man kann dann im weiteren Verlauf des Programms der Variablen i auch andere Objekte mit anderen Datentypen zuweisen:
>>> i = 42
>>> i = "Hallo"
>>> i = [3,9,17]
Dennoch ordnet Python in jedem Fall einen speziellen Typ oder genauer gesagt eine spezielle Klasse der Variablen zu. Dieser Datentyp wird aber automatisch von Python erkannt.
Diese automatische Typzuordnung, die auch während der Laufzeit erfolgen kann, bezeichnet man als „dynamische Typdeklaration”. Mit der Funktion type können wir uns den jeweiligen Typ ausgeben lassen:
>>> i = 42
>>> type(i)
<class 'int'>
>>> i = "Hallo"
>>> type(i)
<class 'str'>
>>> i = [3,9,17]
>>> type(i)
<class 'list'>
Während sich bei statischen Typdeklarationen, also bei Sprachen wie C und C++, nur der
Wert, aber nicht der Typ einer Variablen während eines Laufs ändert, kann sich bei dynamischer Typdeklaration, also in Python, sowohl der Wert als auch der Typ einer Variablen
ändern.
Aber Python achtet auf Typverletzungen zur Laufzeit eines Programms. Man spricht von
einer Typverletzung6 , wenn Datentypen beispielsweise aufgrund fehlender Zuweisungskompatibilität nicht regelgemäß verwendet werden. In Python kann es nur bei Ausdrücken
zu Problemen kommen, da man ja einer Variablen einen beliebigen Typ zuordnen kann.
Eine Typverletzung liegt beispielsweise vor, wenn man eine Variable vom Typ int zu einer
Variablen vom Typ str addieren will. Es wird in diesem Fall ein TypeError generiert:
6
engl. type conflict
29
30
4 Datentypen und Variablen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> x = "Ich bin ein String"
>>> y = 42
>>> z = x + y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly
Allerdings kann man Integer- und Float-Werte – auch wenn es sich um unterschiedliche
Datentypen handelt – in einem Ausdruck mischen. Der Wert des Ausdrucks wird dann ein
Float.
>>> x = 12
>>> y = 3.5
>>> z = x * y
>>> z
42.0
>>> type(x)
<class 'int'>
>>> type(y)
<class 'float'>
>>> type(z)
<class 'float'>
4.5
Typumwandlung
In der Programmierung bezeichnet man die Umwandlung eines Datentyps in einen anderen als Typumwandlung7 . Man benötigt Typumwandlungen beispielsweise, wenn man
Strings und numerische Werte zu einem Ausgabestring zusammenpacken will:
>>> first_name = "Henry"
>>> last_name = "Miller"
>>> age = 20
>>> print(first_name + " " + last_name + ": " + str(age))
Henry Miller: 20
In dem Beispiel haben wir den Wert von „age” explizit mit der Funktion str in einen String
gewandelt. Man bezeichnet dies als explizite Typumwandlung. Hätten wir in obigem Beispiel nicht den Integer-Wert age in einen String gewandelt, hätte Python einen TypeError
generiert:
>>> print(first_name + " " + last_name + ": " + age)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly
7
engl. type conversion oder cast
4.6 Datentyp ermitteln
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Der Text der Fehlermeldung besagt, dass Python keine implizite Typumwandlung von „int”
nach „str” vornehmen kann. Prinzipiell unterstützt Python keine impliziten Typumwandlungen, wie sie in Perl oder PHP möglich sind. Dennoch gibt es Ausnahmen so wie unser
Beispiel, in dem wir Integer- und Float-Werte in einem Ausdruck gemischt hatten. Dort
wurde der Integer-Wert implizit in einen Float-Wert gewandelt.
4.6
Datentyp ermitteln
In vielen Anwendungen muss man für ein Objekt bestimmen, um welchen Typ bzw. um
welche Klasse es sich handelt. Dafür bietet sich meistens die eingebaute Funktion „type”
an. Man übergibt ihr als Argument einen Variablennamen und erhält den Typ des Objekts
zurück, auf das die Variable zeigt:
>>> l = [3,5,4]
>>> type(l)
<class 'list'>
>>> x = 4
>>> type(x)
<class 'int'>
>>> x = 4.5
>>> type(x)
<class 'float'>
>>> x = "Ein String"
>>> type(x)
<class 'str'>
Als Alternative zur Funktion „type” gibt es noch die eingebaute Funktion „isinstance”, die
einen Wahrheitswert „True” oder „False” zurückgibt.
isinstance(object, ct)
„object” ist das Objekt, das geprüft werden soll, und „ct” entspricht der Klasse oder dem
Typ, auf den geprüft werden soll. Im folgenden Beispiel prüfen wir, ob es sich bei dem Objekt x um ein Tupel handelt:
>>> x = (3,89,67)
>>> isinstance(x, tuple)
True
In Programmen kommt es häufig vor, dass wir für ein Objekt wissen wollen, ob es sich um
einen von vielen Typen handelt. Also zum Beispiel die Frage, ob eine Variable ein Integer
oder ein Float ist. Dies könnte man mit der Verknüpfung „or” lösen:
>>> x = 4
>>> isinstance(x, int) or isinstance(x, float)
True
>>> x = 4.8
>>> isinstance(x, int) or isinstance(x, float)
True
31
32
4 Datentypen und Variablen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Allerdings bietet isinstance hierfür eine bequemere Möglichkeit. Statt eines Typs gibt man
als zweites Argument ein Tupel von Typen an:
>>> x = 4.8
>>> isinstance(x,
True
>>> x = (89, 123,
>>> isinstance(x,
True
>>> isinstance(x,
False
(int, float))
898)
(list, tuple))
(int, float))
Möchte man in einem Programm auf einen bestimmten Typ prüfen, so kann man obiges
Vorgehen wählen, aber häufig lässt sich dies eleganter mit Ausnahmehandlungen lösen,
die wir in Kapitel Ausnahmebehandlung, Seite 20 behandelt.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
5
5.1
Sequentielle Datentypen
Übersicht
Auch andere Programmiersprachen wie
beispielsweise Perl und Java kennen Datentypen für Strings und Listen, aber die
Leistung von Python besteht darin, dass
man solche Datentypen zu einer logischen Basisklasse „Sequentielle Datentypen” zusammenfasst. Eines haben nämlich Strings, Listen und Tupel gemeinsam:
Es handelt sich jeweils um Datentypen, deren Elemente fortlaufend, also sequentiell, angeordnet sind. Bei Strings handelt
es sich dabei um gleichartige Objekte, d.h.
Zeichen, und bei Listen oder Tupel handelt
es sich um beliebige Objekte.
In Python gibt es deshalb gleichnamige
Methoden oder Zugriffsmethoden für sequentielle Datentypen, wenn eine solche
Operation für einen Typ möglich ist. Beispielsweise kann man, wie wir in diesem
Kapitel kennenlernen, mit obj[0] auf das Bild 5.1 Fotos aus dem Film „The Kiss” von
erste Element eines sequentiellen Daten- 1896
typs zugreifen, egal ob es sich um einen
String, eine Liste oder ein Tupel handelt. Aber man kann obj[0] nicht verändern, falls „obj”
ein String ist.
Python stellt die folgenden sequentiellen Datentypen zur Verfügung:
34
5 Sequentielle Datentypen
5.1.1
Zeichenketten oder Strings
Neben numerischen Datentypen kennt Python auch Zeichenketten, die häufig mit dem
englischen Ausdruck „Strings” bezeichnet werden. Zeichenketten bestehen aus einer unveränderlichen Folge – also Sequenz – von beliebigen Zeichen.
String-Literale kann man auf verschiedene Arten schreiben:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
mit einzelnen Anführungszeichen (’):
’Dies ist ein String mit einfachen Quotes’
■
mit doppelten Anführungszeichen ("):
"Mayers’ Dackel heißt Waldi"
■
mit dreifachen Anführungszeichen (’’’) oder ("""):
’’’Er sagte "Herr Meyers’ Dackel bellt immer!"’’’
Zur Generierung von mehrzeiligen Strings gibt es zwei Möglichkeiten:
■
Die Zeile mit dem unvollendeten String mit einem Rückwärtsschrägstrich1 „\” beenden:
>>>
...
...
>>>
Ich
s = "Ich bin über mehrere Zeilen \
definiert, genau gesagt \
vier Zeilen"
print(s)
bin über mehrere Zeilen definiert, genau gesagt vier Zeilen
Man kann erkennen, dass es nicht zu Zeilenumbrüchen kommt. Dazu muss man ein
„\n” verwenden.
■
Strings können auch mit einem Paar von dreifachen Anführungszeichen umgeben werden, also """ oder ’’’. Hierbei gibt es jedoch einen Unterschied zum vorigen Vorgehen.
Jetzt werden Newlines in den String übernommen, wenn wir in eine neue Zeile wechseln.
Will man dies nicht, muss man einen Backslash verwenden:
>>> s = """Erste Zeile
... Zweite Zeile
... Dritte Zeile \
... und ich gehöre auch noch zur 3. Zeile"""
>>> print(s)
Erste Zeile
Zweite Zeile
Dritte Zeile und ich gehöre auch noch zur 3. Zeile
Diese Form von Zeichenketten findet vor allen Dingen bei der Dokumentation von Funktionen, Methoden und Modulen Verwendung, wie wir in späteren Kapiteln kennenlernen werden.
1
Wir benutzen hier die vielen wohl ungewohnte deutsche Übersetzung für Backslash.
5.1 Übersicht
Wir haben vorhin gesagt, dass man einen Zeilenvorschub mit der Zeichenfolge „\n” erzeugen kann:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> s = "Eine Zeile\nund noch eine"
>>> print(s)
Eine Zeile
und noch eine
Es gibt noch weitere Escape-Zeichen. Einige wichtige haben wir in der folgenden Liste zusammengefasst:
Sequenz
Beschreibung
\\
Ersatzdarstellung für ein Backslash
\’
Hochkomma
\"
Anführungszeichen
\b
Rückschritt (backspace)
\f
Seitenumbruch (form feed)
\n
Zeilenumbruch (line feed)
\NNAME
Unicode-Zeichen NAME
Beispiel: print(”\N{GREEK SMALL LETTER PI}”) druckt das griechische „pi”
\t
Horizontaler Tabulator
\uXXXX
16-Bit-Unicode-Zeichen
\uXXXXXXXX
32-Bit-Unicode-Zeichen
\v
Vertikaler Tabulator
\ooo
ASCII-Zeichen oktal
\xhh
ASCII-Zeichen hexadezimal
Manchmal, wie zum Beispiel bei regulären Ausdrücken, will man nicht, dass Python diese
Escape-Zeichen auswertet. Dies kann man dadurch verhindern, dass man einen String als
„raw”-String definiert.
Im folgenden Beispiel definieren wir erst einen „normalen” String, der Zeilenumbrüche
enthält. Dann definieren wir den gleichen String als raw-String. Wir schreiben den RawString einmal mit kleinem und einmal mit großem R, um zu demonstrieren, das es keinen
Unterschied gibt:
>>> s = "\nEine Zeile\nund noch eine"
>>> print(s)
Eine Zeile
und noch eine
>>> s = r"\nEine Zeile\nund noch eine"
>>> print(s)
\nEine Zeile\nund noch eine
>>> s = R"\nEine Zeile\nund noch eine"
>>> print(s)
\nEine Zeile\nund noch eine
35
36
5 Sequentielle Datentypen
5.1.2
Listen
Listen stellen wie Strings eine sequentielle Anordnung dar. Während Strings jedoch aus
einer Sequenz von beliebigen Zeichen aufgebaut sind, bestehen Listen aus einer Sequenz
von beliebigen Objekten, also z.B. Integer-Zahlen, Float-Zahlen, Strings oder wieder Listen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiele:
x = [3, 99, "Ein Text"]
y = [42, 65 , [45, 89], 88]
Wie wir in dem Beispiel gesehen haben, werden Listen mit eckigen Klammern generiert.
Die Elemente einer Liste werden durch Kommata getrennt. Im Beispiel können wir auch
erkennen, dass nach und vor der eckigen Klammer auch Leerzeichen stehen können, ebenso wie vor und nach einem Komma. Listen können verschachtelt sein, das heißt, dass sie
andere Listen als Unterlisten enthalten können. Im Folgenden werden wir sehen, dass sie
auch beliebige andere Objekte enthalten können, beispielsweise Tupel und Dictionaries.
5.1.3
Tupel
Rein äußerlich unterscheiden sich Listen und Tupel durch die Art der Klammerung, d.h.
Listen werden von eckigen Klammern und Tupel von runden Klammern eingefasst. Die
vorigen Listenbeispiele sehen in Tupelschreibweise wie folgt aus:
x = (3, 99, "Ein Text")
y = (42, 65 , [45, 89], 88)
Der wesentliche Unterschied zwischen Listen und Tupel besteht jedoch darin, dass Tupel
nicht mehr verändert werden können. Formal kann man sagen, dass Tupel aus einer mit
Kommata getrennten Folge von Referenzen auf Python-Objekte bestehen. Es können keine
Referenzen aus dieser Folge entfernt, verändert oder hinzugefügt werden.
Bei der Definition eines Tupels können die runden Klammern auch weggelassen werden:
>>> x = 3, 99, "Ein Text"
>>> x
(3, 99, 'Ein Text')
Tupel können auch entpackt werden, d.h. statt eines Variablennamens führt man auf der
linken Seite einer Zuweisung genauso viele Variablennamen auf, wie das Tupel Elemente
hat. Dies entspricht dann einer Mehrfachzuweisung:
>>> minimum, maximum, text = 3, 99, "Ein Text"
>>> minimum
3
>>> maximum
99
>>> text
'Ein Text'
5.2 Indizierung von sequentiellen Datentypen
5.2
Indizierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Betrachten wir den String "Hello World": Man sieht, dass die Zeichen eines Strings von
links nach rechts mit 0 beginnend nummeriert sind. Von hinten (rechts) beginnt man mit
-1 zu zählen. Jedes Zeichen eines Strings kann so eindeutig angesprochen werden. Dazu
werden eckige Klammern benutzt, wie man im folgenden Beispiel sehen kann:
>>> text = "Hallo Kaa"
>>> print(text[0])
H
>>> print(text[6])
K
Bild 5.2 Indizierung eines
Strings
Mithilfe von negativen Indizes kann man die Zeichen auch von hinten aus ansprechen,
also -1 für das letzte Zeichen, -2 für das vorletzte usw.
>>> text = "Es geht auch von hinten!"
>>> text[-1]
'!'
>>> text[-2]
'n'
Dies funktioniert bei allen Objekten von sequentiellen Datentypen, also auch bei Listen
und Tupel:
>>> l = [42, 65 , [45, 89], 88]
>>> l[0]
42
>>> l[2]
[45, 89]
>>> l[2][1]
89
>>> l[0] = "Ein neuer Wert"
>>> l
['Ein neuer Wert', 65, [45, 89], 88]
>>>
>>>
>>> t = (42, 65 , (45, 89), 88)
>>> t[0]
42
>>> t[2][0]
45
>>> t[0] = "Tupel lassen sich nicht ändern!"
37
38
5 Sequentielle Datentypen
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im obigen Beispiel konnten wir sehen, dass wir nur Listen, aber keine Tupel ändern können.
5.3
Teilbereichsoperator
Man kann auch Teile eines sequentiellen Datentyps ausschneiden. Im Falle eines Strings
erhält man dann einen Teilstring oder bei Listen wieder eine Liste. Im Englischen wird dieses Ausschneiden als „slicing” bezeichnet. Der Begriff „Slicing” wird auch in der deutschen
Python-Literatur häufig benutzt. Deshalb werden wir die beiden Begriffe im Folgenden
synonym benutzen.
Wie bei der Indizierung benutzt der Slicing-Operator eckige Klammern, aber nun werden
statt eines Werts mindestens zwei Werte erwartet: Anfangswert und Endwert. Anfangs- und
Endwert werden durch einen Doppelpunkt getrennt.
Man versteht dies am besten an einem Beispiel:
>>> txt = "Hello World"
>>> txt[1:5]
'ello'
>>> txt[0:5]
'Hello'
Lässt man den Anfangswert weg (z.B. [:5] ), beginnt das Ausschneiden am Anfang des
Strings (oder der Liste). Analog kann man auch den Endwert weglassen, um alles bis zum
Ende zu übernehmen ( z.B. [6:] ). Lässt man Anfangs- und Endwert weg, erhält man den
ganzen String (oder entsprechend die ganze Liste oder Tupel) zurück:
'Hello'
>>> txt[0:-6]
'Hello'
>>> txt[:5]
'Hello'
>>> txt[6:]
'World'
>>> txt[:]
'Hello World'
Das folgende Beispiel zeigt, wie sich dies bei Listen auswirkt:
>>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot']
>>> einkaufsliste[1:3]
['Bananen', 'Butter']
>>> einkaufsliste[2:]
5.3 Teilbereichsoperator
['Butter', 'Äpfel', 'Brot']
>>> einkaufsliste[:2]
['Käse', 'Bananen']
Der Slicing-Operator funktioniert auch mit drei Argumenten. Das dritte Argument gibt
dann an, das wievielte Argument jedes Mal genommen werden soll, d.h.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
s[Anfang, Ende, Schrittweite]2
Ausgegeben wird eine Liste mit den folgenden Elementen von s:
s[Anfang], s[Anfang + 1 * Schrittweite], . . . s[Anfang + i *
Schrittweite],
solange (Anfang + i * Schrittweite) < Ende ist. txt[::3] gibt jeden dritten Buchstaben eines Strings txt aus.
Beispiele:
>>> w = [3, 8, 12, 4, 9, 14, 23, 7, 21, 37]
>>> w[::3]
[3, 4, 23, 37]
>>>
>>> txt = "Python ist ganz toll"
>>> txt[2:15:3]
'tnsgz'
>>> txt[::3]
'Ph ta l'
Man kann auch einen negativen Wert für die Schrittweite wählen. In diesem Fall ändert
sich auch die Bedeutung der beiden ersten Werte wie folgt:
s[Ende, Anfang, negative Schrittweite]
Dann werden alle Werte vom Index Ende (inklusive) bis zum Index Anfang (exklusive) in
dieser Reihenfolge ausgegeben. Werden für Ende und Anfang keine Werte angegeben, wird
die Liste oder der String in umgekehrter Reihenfolge ausgegeben.
Beispiele für negative Schrittweite:
>>> x = [3, 6, 9, 12, 7]
>>> x[3:1:-1]
[12, 9]
>>> x[4:1:-2]
[7, 9]
>>> x[::-1]
[7, 12, 9, 6, 3]
>>> s = "To be or not to be"
>>> s[::-1]
'eb ot ton ro eb oT'
2
In englischer Notation: s[begin, end, step]
39
40
5 Sequentielle Datentypen
>>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot']
>>> einkaufsliste[::-1]
['Brot', 'Äpfel', 'Butter', 'Bananen', 'Käse']
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im ersten Beispiel kann man erkennen, dass die Ergebnisliste mit dem Endwert-Index „3”,
also dem Wert 12, startet, aber der Startwert x[1] ist nicht im Ergebnis enthalten, da der
Startwert laut Definition nie dabei ist.
Auf diese Art lässt sich auch eine flache Kopie einer Liste erzeugen. Wir erkennen dies daran, dass im folgenden Beispiel einkaufsliste und einkaufsliste2 verschiedene ids haben:
>>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot']
>>> einkaufsliste2 = einkaufsliste[::-1]
>>> einkaufsliste2
['Brot', 'Äpfel', 'Butter', 'Bananen', 'Käse']
>>> id(einkaufsliste), id(einkaufsliste2)
(139688189461128, 139688189461064)
5.4
Die len-Funktion
Eine wichtige Größe gilt es häufig zu bestimmen, egal ob es sich um Strings, Listen oder
Tupel handelt: die Anzahl der Elemente. Man könnte auch sagen: die Länge eines sequentiellen Datentyps.
Dazu bietet Python die len-Funktion. Wendet man diese auf einen beliebigen sequentiellen Datentyp an, liefert sie die Anzahl der Elemente zurück. Bei Strings erhalten wir damit
die Anzahl der Zeichen zurück und bei Listen und Tupel die Anzahl der Elemente.
>>>
>>>
18
>>>
>>>
4
>>>
>>>
3
>>>
>>>
>>>
3
>>>
hamlet = "to be or not to be"
len(hamlet)
cities = ["Berlin", "München", "Hamburg", "Köln"]
len(cities)
coordinates = (4,8,10)
len(coordinates)
mischmasch = [(3,8,"Blabla"), "Eine Zeichenkette", [(3.8, 9), 19]]
len(mischmasch)
5.5 Aufgaben
5.5
Aufgaben
1. Aufgabe:
Welche sequentiellen Datentypen haben Sie kennengelernt?
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 475
2. Aufgabe:
Wie erhält man das erste Element einer Liste?
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 475
3. Aufgabe:
Wie kann man am einfachsten das letzte Zeichen einer Liste ausgeben?
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 476
4. Aufgabe:
Überlegen Sie sich, welche der folgenden Anweisungen korrekt sind. Wir haben die
Ergebnisse, die von der interaktiven Python-Shell ausgeben wurden, gelöscht:
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
t = (4,7,9)
s = "Ich bin ein String"
l = [45,98,"787",[3,4]]
t2 = (4,8,[45,98])
t[0]
t[3]
t(3)
s[4]
s[4] = "x"
l[2][0] = "g"
l[3][0] = "g"
l
t2[2][0] = 42
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 476
41
42
5 Sequentielle Datentypen
5. Aufgabe:
Welche zwei Sätze verbergen sich hinter dem folgenden scheinbaren Buchstabensalat:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> s = "DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre
lsbiscthh inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg
Mmaaxcihmte.."
>>>
Hinweis: Benutzen Sie Slicing! Beachten Sie bitte außerdem, dass sich zwischen
manchen Wörtern mehr als ein Leerzeichen befindet!
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 477
6. Aufgabe:
Was erhalten wir als Ergebnis, wenn wir die Funktion len auf die folgenden Objekte
anwenden? Versuchen Sie, es zuerst durch Überlegen zu bestimmen. Danach können
Sie es in der interaktiven Shell durch Eintippen überprüfen.
>>>
>>>
>>>
>>>
>>>
x = [4, 8, 9]
title = "The Name of the Rose"
address = ["Frank Fitmann", "Feldstr. 19", "89000 München"]
nested = [(4,8,9)]
book = [title, "Umberto Eco"]
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 478
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
5
5.1
Sequentielle Datentypen
Übersicht
Auch andere Programmiersprachen wie
beispielsweise Perl und Java kennen Datentypen für Strings und Listen, aber die
Leistung von Python besteht darin, dass
man solche Datentypen zu einer logischen Basisklasse „Sequentielle Datentypen” zusammenfasst. Eines haben nämlich Strings, Listen und Tupel gemeinsam:
Es handelt sich jeweils um Datentypen, deren Elemente fortlaufend, also sequentiell, angeordnet sind. Bei Strings handelt
es sich dabei um gleichartige Objekte, d.h.
Zeichen, und bei Listen oder Tupel handelt
es sich um beliebige Objekte.
In Python gibt es deshalb gleichnamige
Methoden oder Zugriffsmethoden für sequentielle Datentypen, wenn eine solche
Operation für einen Typ möglich ist. Beispielsweise kann man, wie wir in diesem
Kapitel kennenlernen, mit obj[0] auf das Bild 5.1 Fotos aus dem Film „The Kiss” von
erste Element eines sequentiellen Daten- 1896
typs zugreifen, egal ob es sich um einen
String, eine Liste oder ein Tupel handelt. Aber man kann obj[0] nicht verändern, falls „obj”
ein String ist.
Python stellt die folgenden sequentiellen Datentypen zur Verfügung:
34
5 Sequentielle Datentypen
5.1.1
Zeichenketten oder Strings
Neben numerischen Datentypen kennt Python auch Zeichenketten, die häufig mit dem
englischen Ausdruck „Strings” bezeichnet werden. Zeichenketten bestehen aus einer unveränderlichen Folge – also Sequenz – von beliebigen Zeichen.
String-Literale kann man auf verschiedene Arten schreiben:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
mit einzelnen Anführungszeichen (’):
’Dies ist ein String mit einfachen Quotes’
■
mit doppelten Anführungszeichen ("):
"Mayers’ Dackel heißt Waldi"
■
mit dreifachen Anführungszeichen (’’’) oder ("""):
’’’Er sagte "Herr Meyers’ Dackel bellt immer!"’’’
Zur Generierung von mehrzeiligen Strings gibt es zwei Möglichkeiten:
■
Die Zeile mit dem unvollendeten String mit einem Rückwärtsschrägstrich1 „\” beenden:
>>>
...
...
>>>
Ich
s = "Ich bin über mehrere Zeilen \
definiert, genau gesagt \
vier Zeilen"
print(s)
bin über mehrere Zeilen definiert, genau gesagt vier Zeilen
Man kann erkennen, dass es nicht zu Zeilenumbrüchen kommt. Dazu muss man ein
„\n” verwenden.
■
Strings können auch mit einem Paar von dreifachen Anführungszeichen umgeben werden, also """ oder ’’’. Hierbei gibt es jedoch einen Unterschied zum vorigen Vorgehen.
Jetzt werden Newlines in den String übernommen, wenn wir in eine neue Zeile wechseln.
Will man dies nicht, muss man einen Backslash verwenden:
>>> s = """Erste Zeile
... Zweite Zeile
... Dritte Zeile \
... und ich gehöre auch noch zur 3. Zeile"""
>>> print(s)
Erste Zeile
Zweite Zeile
Dritte Zeile und ich gehöre auch noch zur 3. Zeile
Diese Form von Zeichenketten findet vor allen Dingen bei der Dokumentation von Funktionen, Methoden und Modulen Verwendung, wie wir in späteren Kapiteln kennenlernen werden.
1
Wir benutzen hier die vielen wohl ungewohnte deutsche Übersetzung für Backslash.
5.1 Übersicht
Wir haben vorhin gesagt, dass man einen Zeilenvorschub mit der Zeichenfolge „\n” erzeugen kann:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> s = "Eine Zeile\nund noch eine"
>>> print(s)
Eine Zeile
und noch eine
Es gibt noch weitere Escape-Zeichen. Einige wichtige haben wir in der folgenden Liste zusammengefasst:
Sequenz
Beschreibung
\\
Ersatzdarstellung für ein Backslash
\’
Hochkomma
\"
Anführungszeichen
\b
Rückschritt (backspace)
\f
Seitenumbruch (form feed)
\n
Zeilenumbruch (line feed)
\NNAME
Unicode-Zeichen NAME
Beispiel: print(”\N{GREEK SMALL LETTER PI}”) druckt das griechische „pi”
\t
Horizontaler Tabulator
\uXXXX
16-Bit-Unicode-Zeichen
\uXXXXXXXX
32-Bit-Unicode-Zeichen
\v
Vertikaler Tabulator
\ooo
ASCII-Zeichen oktal
\xhh
ASCII-Zeichen hexadezimal
Manchmal, wie zum Beispiel bei regulären Ausdrücken, will man nicht, dass Python diese
Escape-Zeichen auswertet. Dies kann man dadurch verhindern, dass man einen String als
„raw”-String definiert.
Im folgenden Beispiel definieren wir erst einen „normalen” String, der Zeilenumbrüche
enthält. Dann definieren wir den gleichen String als raw-String. Wir schreiben den RawString einmal mit kleinem und einmal mit großem R, um zu demonstrieren, das es keinen
Unterschied gibt:
>>> s = "\nEine Zeile\nund noch eine"
>>> print(s)
Eine Zeile
und noch eine
>>> s = r"\nEine Zeile\nund noch eine"
>>> print(s)
\nEine Zeile\nund noch eine
>>> s = R"\nEine Zeile\nund noch eine"
>>> print(s)
\nEine Zeile\nund noch eine
35
36
5 Sequentielle Datentypen
5.1.2
Listen
Listen stellen wie Strings eine sequentielle Anordnung dar. Während Strings jedoch aus
einer Sequenz von beliebigen Zeichen aufgebaut sind, bestehen Listen aus einer Sequenz
von beliebigen Objekten, also z.B. Integer-Zahlen, Float-Zahlen, Strings oder wieder Listen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiele:
x = [3, 99, "Ein Text"]
y = [42, 65 , [45, 89], 88]
Wie wir in dem Beispiel gesehen haben, werden Listen mit eckigen Klammern generiert.
Die Elemente einer Liste werden durch Kommata getrennt. Im Beispiel können wir auch
erkennen, dass nach und vor der eckigen Klammer auch Leerzeichen stehen können, ebenso wie vor und nach einem Komma. Listen können verschachtelt sein, das heißt, dass sie
andere Listen als Unterlisten enthalten können. Im Folgenden werden wir sehen, dass sie
auch beliebige andere Objekte enthalten können, beispielsweise Tupel und Dictionaries.
5.1.3
Tupel
Rein äußerlich unterscheiden sich Listen und Tupel durch die Art der Klammerung, d.h.
Listen werden von eckigen Klammern und Tupel von runden Klammern eingefasst. Die
vorigen Listenbeispiele sehen in Tupelschreibweise wie folgt aus:
x = (3, 99, "Ein Text")
y = (42, 65 , [45, 89], 88)
Der wesentliche Unterschied zwischen Listen und Tupel besteht jedoch darin, dass Tupel
nicht mehr verändert werden können. Formal kann man sagen, dass Tupel aus einer mit
Kommata getrennten Folge von Referenzen auf Python-Objekte bestehen. Es können keine
Referenzen aus dieser Folge entfernt, verändert oder hinzugefügt werden.
Bei der Definition eines Tupels können die runden Klammern auch weggelassen werden:
>>> x = 3, 99, "Ein Text"
>>> x
(3, 99, 'Ein Text')
Tupel können auch entpackt werden, d.h. statt eines Variablennamens führt man auf der
linken Seite einer Zuweisung genauso viele Variablennamen auf, wie das Tupel Elemente
hat. Dies entspricht dann einer Mehrfachzuweisung:
>>> minimum, maximum, text = 3, 99, "Ein Text"
>>> minimum
3
>>> maximum
99
>>> text
'Ein Text'
5.2 Indizierung von sequentiellen Datentypen
5.2
Indizierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Betrachten wir den String "Hello World": Man sieht, dass die Zeichen eines Strings von
links nach rechts mit 0 beginnend nummeriert sind. Von hinten (rechts) beginnt man mit
-1 zu zählen. Jedes Zeichen eines Strings kann so eindeutig angesprochen werden. Dazu
werden eckige Klammern benutzt, wie man im folgenden Beispiel sehen kann:
>>> text = "Hallo Kaa"
>>> print(text[0])
H
>>> print(text[6])
K
Bild 5.2 Indizierung eines
Strings
Mithilfe von negativen Indizes kann man die Zeichen auch von hinten aus ansprechen,
also -1 für das letzte Zeichen, -2 für das vorletzte usw.
>>> text = "Es geht auch von hinten!"
>>> text[-1]
'!'
>>> text[-2]
'n'
Dies funktioniert bei allen Objekten von sequentiellen Datentypen, also auch bei Listen
und Tupel:
>>> l = [42, 65 , [45, 89], 88]
>>> l[0]
42
>>> l[2]
[45, 89]
>>> l[2][1]
89
>>> l[0] = "Ein neuer Wert"
>>> l
['Ein neuer Wert', 65, [45, 89], 88]
>>>
>>>
>>> t = (42, 65 , (45, 89), 88)
>>> t[0]
42
>>> t[2][0]
45
>>> t[0] = "Tupel lassen sich nicht ändern!"
37
38
5 Sequentielle Datentypen
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im obigen Beispiel konnten wir sehen, dass wir nur Listen, aber keine Tupel ändern können.
5.3
Teilbereichsoperator
Man kann auch Teile eines sequentiellen Datentyps ausschneiden. Im Falle eines Strings
erhält man dann einen Teilstring oder bei Listen wieder eine Liste. Im Englischen wird dieses Ausschneiden als „slicing” bezeichnet. Der Begriff „Slicing” wird auch in der deutschen
Python-Literatur häufig benutzt. Deshalb werden wir die beiden Begriffe im Folgenden
synonym benutzen.
Wie bei der Indizierung benutzt der Slicing-Operator eckige Klammern, aber nun werden
statt eines Werts mindestens zwei Werte erwartet: Anfangswert und Endwert. Anfangs- und
Endwert werden durch einen Doppelpunkt getrennt.
Man versteht dies am besten an einem Beispiel:
>>> txt = "Hello World"
>>> txt[1:5]
'ello'
>>> txt[0:5]
'Hello'
Lässt man den Anfangswert weg (z.B. [:5] ), beginnt das Ausschneiden am Anfang des
Strings (oder der Liste). Analog kann man auch den Endwert weglassen, um alles bis zum
Ende zu übernehmen ( z.B. [6:] ). Lässt man Anfangs- und Endwert weg, erhält man den
ganzen String (oder entsprechend die ganze Liste oder Tupel) zurück:
'Hello'
>>> txt[0:-6]
'Hello'
>>> txt[:5]
'Hello'
>>> txt[6:]
'World'
>>> txt[:]
'Hello World'
Das folgende Beispiel zeigt, wie sich dies bei Listen auswirkt:
>>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot']
>>> einkaufsliste[1:3]
['Bananen', 'Butter']
>>> einkaufsliste[2:]
5.3 Teilbereichsoperator
['Butter', 'Äpfel', 'Brot']
>>> einkaufsliste[:2]
['Käse', 'Bananen']
Der Slicing-Operator funktioniert auch mit drei Argumenten. Das dritte Argument gibt
dann an, das wievielte Argument jedes Mal genommen werden soll, d.h.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
s[Anfang, Ende, Schrittweite]2
Ausgegeben wird eine Liste mit den folgenden Elementen von s:
s[Anfang], s[Anfang + 1 * Schrittweite], . . . s[Anfang + i *
Schrittweite],
solange (Anfang + i * Schrittweite) < Ende ist. txt[::3] gibt jeden dritten Buchstaben eines Strings txt aus.
Beispiele:
>>> w = [3, 8, 12, 4, 9, 14, 23, 7, 21, 37]
>>> w[::3]
[3, 4, 23, 37]
>>>
>>> txt = "Python ist ganz toll"
>>> txt[2:15:3]
'tnsgz'
>>> txt[::3]
'Ph ta l'
Man kann auch einen negativen Wert für die Schrittweite wählen. In diesem Fall ändert
sich auch die Bedeutung der beiden ersten Werte wie folgt:
s[Ende, Anfang, negative Schrittweite]
Dann werden alle Werte vom Index Ende (inklusive) bis zum Index Anfang (exklusive) in
dieser Reihenfolge ausgegeben. Werden für Ende und Anfang keine Werte angegeben, wird
die Liste oder der String in umgekehrter Reihenfolge ausgegeben.
Beispiele für negative Schrittweite:
>>> x = [3, 6, 9, 12, 7]
>>> x[3:1:-1]
[12, 9]
>>> x[4:1:-2]
[7, 9]
>>> x[::-1]
[7, 12, 9, 6, 3]
>>> s = "To be or not to be"
>>> s[::-1]
'eb ot ton ro eb oT'
2
In englischer Notation: s[begin, end, step]
39
40
5 Sequentielle Datentypen
>>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot']
>>> einkaufsliste[::-1]
['Brot', 'Äpfel', 'Butter', 'Bananen', 'Käse']
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im ersten Beispiel kann man erkennen, dass die Ergebnisliste mit dem Endwert-Index „3”,
also dem Wert 12, startet, aber der Startwert x[1] ist nicht im Ergebnis enthalten, da der
Startwert laut Definition nie dabei ist.
Auf diese Art lässt sich auch eine flache Kopie einer Liste erzeugen. Wir erkennen dies daran, dass im folgenden Beispiel einkaufsliste und einkaufsliste2 verschiedene ids haben:
>>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot']
>>> einkaufsliste2 = einkaufsliste[::-1]
>>> einkaufsliste2
['Brot', 'Äpfel', 'Butter', 'Bananen', 'Käse']
>>> id(einkaufsliste), id(einkaufsliste2)
(139688189461128, 139688189461064)
5.4
Die len-Funktion
Eine wichtige Größe gilt es häufig zu bestimmen, egal ob es sich um Strings, Listen oder
Tupel handelt: die Anzahl der Elemente. Man könnte auch sagen: die Länge eines sequentiellen Datentyps.
Dazu bietet Python die len-Funktion. Wendet man diese auf einen beliebigen sequentiellen Datentyp an, liefert sie die Anzahl der Elemente zurück. Bei Strings erhalten wir damit
die Anzahl der Zeichen zurück und bei Listen und Tupel die Anzahl der Elemente.
>>>
>>>
18
>>>
>>>
4
>>>
>>>
3
>>>
>>>
>>>
3
>>>
hamlet = "to be or not to be"
len(hamlet)
cities = ["Berlin", "München", "Hamburg", "Köln"]
len(cities)
coordinates = (4,8,10)
len(coordinates)
mischmasch = [(3,8,"Blabla"), "Eine Zeichenkette", [(3.8, 9), 19]]
len(mischmasch)
5.5 Aufgaben
5.5
Aufgaben
1. Aufgabe:
Welche sequentiellen Datentypen haben Sie kennengelernt?
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 475
2. Aufgabe:
Wie erhält man das erste Element einer Liste?
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 475
3. Aufgabe:
Wie kann man am einfachsten das letzte Zeichen einer Liste ausgeben?
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 476
4. Aufgabe:
Überlegen Sie sich, welche der folgenden Anweisungen korrekt sind. Wir haben die
Ergebnisse, die von der interaktiven Python-Shell ausgeben wurden, gelöscht:
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
t = (4,7,9)
s = "Ich bin ein String"
l = [45,98,"787",[3,4]]
t2 = (4,8,[45,98])
t[0]
t[3]
t(3)
s[4]
s[4] = "x"
l[2][0] = "g"
l[3][0] = "g"
l
t2[2][0] = 42
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 476
41
42
5 Sequentielle Datentypen
5. Aufgabe:
Welche zwei Sätze verbergen sich hinter dem folgenden scheinbaren Buchstabensalat:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> s = "DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre
lsbiscthh inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg
Mmaaxcihmte.."
>>>
Hinweis: Benutzen Sie Slicing! Beachten Sie bitte außerdem, dass sich zwischen
manchen Wörtern mehr als ein Leerzeichen befindet!
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 477
6. Aufgabe:
Was erhalten wir als Ergebnis, wenn wir die Funktion len auf die folgenden Objekte
anwenden? Versuchen Sie, es zuerst durch Überlegen zu bestimmen. Danach können
Sie es in der interaktiven Shell durch Eintippen überprüfen.
>>>
>>>
>>>
>>>
>>>
x = [4, 8, 9]
title = "The Name of the Rose"
address = ["Frank Fitmann", "Feldstr. 19", "89000 München"]
nested = [(4,8,9)]
book = [title, "Umberto Eco"]
Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 478
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
6
6.1
Dictionaries
Dictionaries und assoziative Felder
In den vorigen Kapiteln haben wir die sequentiellen Datentypen wie Listen, Strings
und Tupel eingeführt. Nun wollen wir eine weitere Kategorie von Datentypen genauer untersuchen, den Datentyp „Mapping”. In dieser Kategorie gibt es allerdings
zur Zeit nur einen implementierten Typ,
das Dictionary. Beim Dictionary handelt es
sich um ein assoziatives Feld. Assoziative
Felder werden in verschiedenen ProgramBild 6.1 Dictionary
miersprachen mit unterschiedlichen Namen versehen. So spricht man in Java und
C++ von einer Map, in Perl und Ruby von einem Hash und wie in Python bezeichnen auch
Smalltalk, Objective-C und C# assoziative Arrays als Dictionaries.
Ein Dictionary besteht aus Schlüssel-Werte-Paaren. Man könnte es sich als eine Menge von
Schlüssel-Werte-Paaren vorstellen. Jedem Schlüssel-Objekt ist ein Wert-Objekt zugeordnet. Mathematisch kann man auch sagen, dass die Schlüssel auf die Werte-Objekte abgebildet werden, also eine Abbildung aus der Menge der Schlüssel in die Menge der Werte. So
kann man sich auch den Kategoriennamen „Mapping” erklären, denn das englische Wort
für Abbildung lautet „Mapping”.
Dictionaries gehören zu den wichtigsten Datentypen von Python. Kaum ein Programm
oder Skript kommt ohne Dictionaries aus. Wie Listen können Dictionaries leicht verändert
werden, außerdem können sie während der Laufzeit beliebig wachsen und schrumpfen.
In diesem Kapitel geht es aber nicht nur um Dictionaries, sondern am Ende beschäftigen
wir uns auch noch mit dem Zusammenhang zwischen Listen und Dictionaries, d.h. wir
zeigen, wie man aus Dictionaries Listen erzeugt und umgekehrt aus bestimmten Listen
Dictionaries.
44
6 Dictionaries
6.2
Definition und Benutzung
Wir beginnen mit dem wohl einfachsten Fall eines Dictionarys, dem leeren:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> empty = {}
>>> empty
{}
Zu Ehren des „Paten” von Python, „Monty Python”, definieren wir nun ein spezielles kulinarisches Dictionary. Was wäre Python und auch das Internet ohne „ham”, „egg” und „spam”?
>>> food = {"ham" : "yes", "egg" : "yes", "spam" : "no"}
>>> print(food)
{'egg': 'yes', 'ham': 'yes', 'spam': 'no'}
>>> food["ham"]
'yes'
>>> food["spam"]
'no'
>>> food["spam"] = "yes"
>>> print(food)
{'egg': 'yes', 'ham': 'yes', 'spam': 'yes'}
Im vorigen Beispiel können wir ein paar interessante Dinge über Dictionaries lernen. Ein
Dictionary wird mit geschweiften Klammern eingegeben. Ein Schlüssel-Wert-Paar wird mit
Doppelpunkt zwischen Schlüssel und Wert eingegeben. Die verschiedenen Paare werden
mit Komma getrennt eingegeben.
Man kann sich ein ganzes Dictionary mit print ausgeben lassen. Dabei ist es zunächst einmal irritierend, dass die Reihenfolge – so wie auch in unserem Beispiel – meist nicht mit der
Reihenfolge in der Definition des Dictionary übereinstimmt. In unserer Definition stand
als erstes „ham”, während unsere Ausgabe mit „egg” beginnt. Dies ist in Ordnung, da auf
dem Dictionary keine Ordnung definiert ist. Python kann also die Schlüssel-Werte-Paare
in einer beliebigen Reihenfolge ausgeben.
Als Nächstes erkennen wir, dass wir zum Zugriff auf einen bestimmten Schlüsselwert eckige Klammern benutzen. So liefert food["ham"] den Wert für den Schlüssel "ham" zurück,
d.h. ’yes’.
Die Zuweisung eines neuen Werts zu einem bestehenden Schlüssel erfolgt ähnlich wie bei
den Listen, nur dass wir keinen Index, sondern einen Schlüssel benutzen. So weisen wir
mit der Anweisung food["spam"] = "yes" dem Schlüssel "spam" den neuen Wert ’yes’
zu.
Im nächsten Beispiel kreieren wir ein einfaches deutsch-englisches Wörterbuch als Dictionary:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow
":"gelb"}
>>> print(en_de)
{'blue': 'blau', 'green': 'grün', 'yellow': 'gelb', 'red': 'rot'}
>>>
6.2 Definition und Benutzung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> en_de["blue"]
'blau'
>>> en_de["green"]
'grün'
In obigem Dictionary können wir nur über die englischen Schlüssel zugreifen, also beispielsweise en_de["blue"]. Möchten wir beispielsweise wissen, wie „grün” auf Englisch
heißt, so geht dies nicht.1 Um nun auch über deutsche Schlüssel zuzugreifen, müssen wir
ein neues Dictionary definieren, in dem die deutschen Wörter als Schlüssel gespeichert
sind und die englischen als Werte:
>>> de_en = {"rot" : "red", "grün" : "green", "blau" : "blue", "gelb
":"yellow"}
Wie wäre es mit einem weiteren Dictionary, zum Beispiel in Deutsch-Französisch? Versuchen Sie es doch einmal zur Übung. Falls Sie kein Französisch können: Die Farben lauten:
„rouge”, „vert”, „bleu” und „jaune”.
Ein deutsch-französisches Dictionary sieht nun wie folgt aus:
>>> de_fr = {"rot" : "rouge", "grün" : "vert", "blau" : "bleu", "gelb
":"jaune"}
Nun sind wir in der Lage, mit der Verknüpfung der beiden Dictionaries de_fr und en_de
von Englisch nach Französisch zu übersetzen, obwohl wir gar kein Englisch-FranzösischWörterbuch haben. So gibt uns de_fr[en_de["red"]] das französische Wort für „red”, also
„rouge”:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow
":"gelb"}
>>> de_fr = {"rot" : "rouge", "grün" : "vert", "blau" : "bleu", "gelb
":"jaune"}
>>> print("The French word for 'red' is " + de_fr[en_de['red']])
The French word for 'red' is rouge
Was passiert, wenn wir auf eine Farbe zugreifen wollen, die gar nicht existiert? Wir probieren dies im Folgenden:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow
":"gelb"}
>>> en_de["brown"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'brown'
Wir generieren also einen Fehler, den sogenannten KeyError, wenn wir auf einen nicht existierenden Schlüssel zuzugreifen versuchen.
Um solche Fehler zu vermeiden, werden häufig bedingte Anweisungen verwendet. Auf
diese gehen wir erst in Kapitel 9 (Verzweigungen) ein. Dennoch möchten wir Ihnen das
1
Nicht ohne Programmierung jedenfalls
45
46
6 Dictionaries
typische Vorgehen hier nicht vorenthalten. Anfänger, die noch nie programmiert haben,
können die folgenden Abschnitte getrost übergehen, damit sie keine Verständnisprobleme
haben.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
6.3
Fehlerfreie Zugriffe auf Dictionaries
Mit dem Schlüsselwort „in” kann geprüft werden, ob ein Index in einem Dictionary vorkommt:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"}
>>> "red" in en_de
True
>>> "brown" in en_de
False
Damit kann man mittels einer bedingten Anweisung prüfen, ob eine Farbe bereits als
Schlüssel enthalten ist oder nicht. In dem folgenden kleinen Programm benutzen wir
noch eine weitere Anweisung, auf die wir auch erst später wieder eingehen: die inputAnweisung. Das Programm bleibt bei einer input-Anweisung stehen und druckt den
Text aus, der als Argument angegeben wird, also in unserem ersten Aufruf „Farbe?”.
Dann kann der Benutzer des Programms eine Eingabe vornehmen, die von input als
String zurückgeliefert wird. In unserem Beispiel speichern wir den String in der Variablen
„colour”:
en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"}
colour = input("Farbe? ")
if colour in en_de:
print("Die Farbe " + colour + " ist ein Schlüssel")
print("Der deutsche Wert für " + colour + " ist " + en_de[colour])
else:
print("Die Farbe " + colour + " ist noch kein Schlüssel")
colour_de = input("Deutsch für " + colour + "? ")
en_de[colour] = colour_de
print("Nun kennen wir auch " + colour + ":")
print(en_de)
Wir starten dieses Programm zweimal: einmal mit einer Farbe, die bereits im Dictionary
enthalten ist, und einmal mit einer, die noch nicht enthalten ist:
$ python3 guarded_dictionary_access.py
Farbe? red
Die Farbe red ist ein Schlüssel
Der deutsche Wert für red ist rot
$ python3 guarded_dictionary_access.py
6.4 Zulässige Typen für Schlüssel und Werte
Farbe? brown
Die Farbe brown ist noch kein Schlüssel
Deutsch für brown? braun
Nun kennen wir auch brown:
{'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'red': 'rot'}
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Alternativ zu obigem Vorgehen, also dem Zugriff auf einen Schlüssel über den Zugriffsoperator, kann man auch die Listen-Methode get benutzen.
Ruft man D.get(key[, default])2 für ein Dictionary D auf, wird D[key] zurückgeliefert,
falls key ein Schlüssel von D ist. Falls key kein Schlüssel von D ist, wird der Default-Wert
„default” zurückgeliefert, falls get mit einem Default-Wert aufgerufen worden ist, ansonsten wird der spezielle Wert None zurückgeliefert. Dies bedeutet, dass man bei der Benutzung der Methode get keinen Fehler erzeugt, falls ein Schlüssel nicht existiert.
Beispiel:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow
":"gelb"}
>>> en_de.get("red")
'rot'
>>> en_de.get("brown")
>>> en_de.get("brown","spam")
'spam'
Dictionaries, deren Schlüssel aus Strings bestehen, die den Konventionen für Variablennamen entsprechen – also nur aus Buchstaben, Ziffern oder dem Unterstrich („_”) bestehen,
aber nicht mit einer Ziffer beginnen –, können auch einfacher definiert werden. Wir zeigen
dies an dem vorher definierten Dictionary „en_de”:
>>> en_de = dict(red="rot", green="grün", blue="blau", yellow="gelb")
>>> en_de
{'yellow': 'gelb', 'blue': 'blau', 'green': 'grün', 'red': 'rot'}
6.4
Zulässige Typen für Schlüssel
und Werte
In unseren bisherigen Beispielen zu Dictionaries haben wir nur Strings als Schlüssel verwendet, sodass leicht der Eindruck entstehen könnte, dass nur Strings als Schlüssel zulässig sind. Als Schlüssel können jedoch Instanzen aller unveränderlichen (immutable) Datentypen verwendet werden, d.h. Integer, Floats, Strings oder Tupel, jedoch keine veränderlichen Objekte wie beispielsweise Listen oder Dictionaries.
2
Die eckigen Klammen haben in dieser Definition nichts mit Listenbegrenzern zu tun. Vielmehr bedeuten
sie, dass der Parameter „default” optional ist. In anderen Worten: „get” kann man entweder mit einem oder
zwei Argumenten aufrufen, d.h. D.get(key) oder D.get(key, default).
47
48
6 Dictionaries
>>> staedte_GPS = { (52.520007, 13.404954):"Berlin", (47.767097,
8.872239):"Singen am Hohentwiel" }
>>> ports = {21:"File Transfer Protocol (FTP)", 22:"Secure Shell (SSH)
", 23:"Telnet remote login service"}
>>> adressen = {"Henry":[ ("Henry", "Peterson"), 20016, "Hamburg"]}
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Verwendet man veränderliche Werte als Schlüssel, erhält man eine Fehlermeldung:
>>> dic = { [1,2,3]:"abc"}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Tupel als Schlüssel sind in Ordnung, wie wir im folgenden Beispiel sehen:
>>> dic = { (1,2,3):"abc", 3.1415:"abc"}
>>> dic
{(1, 2, 3): 'abc'}
6.5
Verschachtelte Dictionaries
Wir zeigen nun, wie man Dictionaries verschachtelt. Verschachtelte Dictionaries werden
häufig auch mit ihrem englischen Begriff als „Nested Dictionaries” bezeichnet. Um dieses Verfahren zu demonstrieren, peppen wir unsere Wörterbücher für natürliche Sprachen
noch ein wenig auf. Dazu definieren wir ein Dictionary von Dictionaries:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow
":"gelb"}
>>> de_fr = {"rot" : "rouge", "grün" : "vert", "blau" : "bleu", "gelb
":"jaune"}
>>>
>>> dictionaries = {"en_de" : en_de, "de_fr" : de_fr }
>>>
>>> print(dictionaries["de_fr"]["blau"])
bleu
6.6
Methoden auf Dictionaries
Im Folgenden bezeichnet D ein Dictionary, wenn wir es nicht anders vermerkt haben.
clear(...) Löscht alle Einträge eines Dictionarys.
Beispiel:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "
yellow":"gelb"}
6.6 Methoden auf Dictionaries
>>> en_de.clear()
>>> en_de
{}
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
copy(...) D.copy() erzeugt eine flache Kopie von D.3 Anfänger machen häufig den Fehler
zu glauben, dass die Zuweisung eines Dictionarys an eine Variable bereits einer Kopie
entspricht. Dass dies nicht so ist, demonstrieren wir im folgenden Beispiel:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "
yellow":"gelb"}
>>> d = en_de
>>> d["yellow"] = "gelblich"
>>> d
{'blue': 'blau', 'green': 'grün', 'yellow': 'gelblich', 'red': 'rot
'}
>>> en_de
{'blue': 'blau', 'green': 'grün', 'yellow': 'gelblich', 'red': 'rot
'}
Wäre „d” in obigem Beispiel eine Kopie, hätte sich der Wert von „en_de” nicht ändern
dürfen.
Eine flache Kopie erhalten wir mit der Methode copy, wie wir im folgenden Beispiel
sehen:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "
yellow":"gelb"}
>>> d = en_de.copy()
>>> d["yellow"] = "gelblich"
>>> d
{'blue': 'blau', 'green': 'grün', 'red': 'rot', 'yellow': 'gelblich
'}
>>> en_de
{'blue': 'blau', 'green': 'grün', 'yellow': 'gelb', 'red': 'rot'}
fromkeys(...) dict.fromkeys(S[,v]) liefert ein Dictionary zurück mit den Werten des unveränderlichen sequentiellen Datentyps S als Schlüssel und dem optionalen Wert „v”, der
jedem Schlüssel automatisch zugeordnet wird. Wird „v” nicht angegeben, werden alle
Werte auf None gesetzt:
Beispiel:
>>> food = ("ham", "eggs", "spam")
>>> d = dict.fromkeys(food)
>>> d
{'eggs': None, 'ham': None, 'spam': None}
>>> d = dict.fromkeys(food, "enjoy")
>>> d
{'eggs': 'enjoy', 'ham': 'enjoy', 'spam': 'enjoy'}
3
Auf die Unterschiede zwischen flacher und tiefer Kopie gehen wir in Kapitel 13 (Flaches und tiefes Kopieren)
ein.
49
50
6 Dictionaries
items(...) D.items() liefert ein Mengen-ähnliches Objekt vom Type „dict_item” zurück, was
einer View der Schlüssel-Werte-Paare entspricht.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiel:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "
yellow":"gelb"}
>>> x = en_de.items()
>>> type(x)
<class 'dict_items'>
>>> x
dict_items([('blue', 'blau'), ('green', 'grün'), ('yellow', 'gelb'),
('red', 'rot')])
keys(...) D.keys() liefert ein Mengen-ähnliches Objekt vom Type „dict_item” zurück, was
einer View der Schlüssel entspricht.
Beispiel:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "
yellow":"gelb"}
>>> x = en_de.keys()
>>> type(x)
<class 'dict_keys'>
>>> x
dict_keys(['blue', 'green', 'yellow', 'red'])
pop(...) D.pop(k[,d]) entfernt den Schlüssel „k” zusammen mit seinem Wert aus dem Dictionary. Die Methode liefert den Wert D[k] zurück. Entspricht „k” keinem Schlüssel,
dann wird ein KeyError generiert, außer pop wurde mit dem optionalen Parameter „d”
aufgerufen. In diesem Fall liefert D.pop(k,d) den Wert „d” zurück.
Beispiel:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "
yellow":"gelb"}
>>> x = en_de.pop("red")
>>> x
'rot'
>>> en_de
{'blue': 'blau', 'green': 'grün', 'yellow': 'gelb'}
>>> x = en_de.pop("brown")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'brown'
>>> x = en_de.pop("brown","spam")
>>> x
'spam'
popitem(...) D.popitem() wird ohne Parameter aufgerufen und liefert ein beliebiges
Schlüssel-Wert-Paar (k, v) zurück, was dann aus dem Dictionary entfernt wird. Falls
das Dictionary leer ist, wird ein KeyError generiert.
6.6 Methoden auf Dictionaries
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiel:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "
yellow":"gelb"}
>>> en_de.popitem()
('blue', 'blau')
>>> en_de.popitem()
('green', 'grün')
>>> en_de.popitem()
('yellow', 'gelb')
>>> en_de.popitem()
('red', 'rot')
>>> en_de.popitem()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'popitem(): dictionary is empty'
setdefault(...) D.setdefault(k[,d]) setzt D[k] auf den Wert d, falls der Schlüssel k noch nicht
in D enthalten ist. Falls k bereits in D enthalten ist, verändert diese Methode das Dictionary D nicht. Falls der optionale Parameter „d” nicht angeben wird, wird D[k] auf den
Wert None gesetzt, falls der Schlüssel k noch nicht in D enthalten ist. Die Methode liefert
den Wert von D[k] zurück.
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"}
>>> en_de.setdefault("brown", "braun")
'braun'
>>> en_de
{'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'red': 'rot'}
>>> en_de.setdefault("green", "verde")
'grün'
>>> en_de
{'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'red': 'rot'}
>>> en_de.setdefault("yellow")
>>> en_de
{'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'yellow': None,
'red': 'rot'}
update(...) Fügt ein Dictionary d2 zu d hinzu und überschreibt gegebenenfalls die Werte
von bereits vorhandenen Schlüsseln.
Beispiel:
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"}
>>>
>>> en_de2 = {"yellow":"gelb", "red":"rötlich"}
>>> en_de.update(en_de2)
>>> en_de
{'blue': 'blau', 'green': 'grün', 'yellow': 'gelb', 'red': 'rötlich
'}
51
52
6 Dictionaries
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"}
>>> en_de2 = {"yellow":"gelb", "red":"rötlich"}
>>> en_de2.update(en_de)
>>> en_de2
{'blue': 'blau', 'green': 'grün', 'red': 'rot', 'yellow': 'gelb'}
6.7
Operatoren auf Dictionaries
Auch auf Dictionaries ist eine „Länge” definiert. Die Methode len() liefert die Anzahl der
Schlüssel-Werte-Paare zurück, was der Anzahl der verschiedenen Schlüssel entspricht.
>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow
":"gelb"}
>>> len(en_de)
4
6.8
Die zip-Funktion
Diese sehr nützliche Funktion4 kann dazu genutzt werden, aus Listen mit bestimmten Eigenschaften Dictionaries zu erzeugen. Ebenso kann sie dazu benutzt werden, bestimmte
Dictionaries umzukehren. Wir werden dies in diesem Kapitel demonstrieren.
zip wird auf eine beliebige Folge von iterierbaren Objekten, also beispielsweise Strings,
Listen und Tupel, angewandt. zip liefert einen Iterator zurück, der Tupel liefert. Zuerst ein
Tupel mit den ersten Elementen der Eingabeargumente, dann die zweiten, dritten usw. zip
stoppt, sobald eines der iterierbaren Objekte aufgebraucht ist. Am besten kann man sich
die Arbeitsweise von zip mit folgendem Bild veranschaulichen:
Beispiele:
>>> namen = ["Peter", "Sarah", "Maria", "Frank", "Eddie"]
>>> obst = ["Äpfel", "Birnen", "Bananen", "Kirschen", "Orangen"]
>>>
>>> for name, frucht in zip(namen, obst):
4
Eigentlich handelt es sich bei zip um eine Klasse!
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
6.8 Die zip-Funktion
...
print(name + " liebt " + frucht)
...
Peter liebt Äpfel
Sarah liebt Birnen
Maria liebt Bananen
Frank liebt Kirschen
Eddie liebt Orangen
>>>
>>> for zaehler, name, frucht in zip(range(1, 6), namen, obst):
...
print(str(zaehler) + ". " + name + " liebt " + frucht)
...
1. Peter liebt Äpfel
2. Sarah liebt Birnen
3. Maria liebt Bananen
4. Frank liebt Kirschen
5. Eddie liebt Orangen
Für Leute mit Kenntnissen in linearer Algebra ist es meist sehr hilfreich zu wissen bzw. zu
erkennen, dass zip gewissermaßen eine Matrix transponiert. Dazu stellt man sich vor, dass
die iterierbaren Objekte der Eingaben die Zeilen einer Matrix darstellen:
>>> z1 = [11, 12, 13]
>>> z2 = [21, 22, 23]
>>> z3 = [31, 32, 33]
>>>
>>> T = zip(z1, z2, z3)
>>> T
<zip object at 0x911ffac>
>>> list(T)
[(11, 21, 31), (12, 22, 32), (13, 23, 33)]
Aus dem obigen zip-Objekt T kann man nur einmal eine solche Liste erzeugen. Danach ist
das zip-Objekt gewissermaßen „aufgebraucht”, d.h. wir haben alle Elemente iteriert. Versucht man nochmals, eine Liste zu erzeugen, erhält man deshalb eine leere Liste:
>>> list(T)
[]
Eigentlich hatten wir anfangs gesagt, dass man mit zip eine Matrix transponieren kann.
Wir hatten jedoch drei Vektoren an zip als Parameter übergeben. Man könnte sich auch
eine Liste „Z” mit den obigen drei Vektoren z1, z2 und z3 vorstellen. Das entspräche dann
einer Matrix, und die drei Vektoren könnten als Zeilenvektoren aufgefasst werden.
>>>
>>> Z = [ [11, 12, 13],
...
[21, 22, 23],
...
[31, 32, 33]]
53
54
6 Dictionaries
Den *-Operator beim Funktionsaufruf werden wir erst später erläutern5 , aber im folgenden Beispiel bewirkt er ein Entpacken der Liste, d.h. zip wird mit den drei Unterlisten (den
Zeilenvektoren der Matrix) als Parameter aufgerufen.
>>> TZ = zip(*Z)
>>> list(TZ)
[(11, 21, 31), (12, 22, 32), (13, 23, 33)]
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
zip(*Z) entspricht dem Aufruf zip([11, 12, 13], [21, 22, 23], [31, 32, 33])
Besonders interessant im Zusammenhang mit Dictionaries ist der Fall, in dem wir zwei
flache Listen oder Tupel miteinander verknüpfen.
>>> l1 = [11, 12, 13, 14]
>>> l2 = [21, 22, 23, 24]
>>> list(zip(l1,l2))
[(11, 21), (12, 22), (13, 23), (14, 24)]
Sind die Argumente von zip verschieden lang, dann arbeitet zip nur auf den Argumenten
bis zur kleinsten Länge. Alle darüber hinaus existierenden Elemente werden ignoriert.
>>> l1 = [11, 12, 13]
>>> l2 = [21, 22, 23, 24]
>>> list(zip(l1,l2))
[(11, 21), (12, 22), (13, 23)]
6.9
Dictionaries aus Listen erzeugen
Betrachten wir die beiden folgenden Listen:
>>> gerichte = ["Pizza", "Sauerkraut", "Paella", "Hamburger"]
>>> laender = ["Italien","Deutschland","Spanien","USA"]
Auch wenn mittlerweile Hamburger weltweit verbreitet sind, wenn vielleicht mehr Pizzen
in Deutschland als in Italien gegessen werden und Sauerkraut nicht mehr zu den deutschen Lieblingsgerichten gehört, bilden wir dennoch ein Dictionary, was alte Stereotypen
bedient: Bei der Eingabe eines Landes als Schlüssel soll als Wert das landesspezifische Gericht erscheinen. Schauen wir uns zunächst einmal an, was passiert, wenn wir obige Listen
mit zip verknüpfen:
>>> list(zip(laender, gerichte))
[('Italien', 'Pizza'), ('Deutschland', 'Sauerkraut'), ('Spanien', '
Paella'), ('USA', 'Hamburger')]
Wenn Sie im vorigen Kapitel unter der Dictionary-Methode item nachschauen, werden Sie
erkennen, dass die von item zurückgelieferte Liste von 2er-Tupel obiges Aussehen hat. Des-
5
Auf diesen Mechanismus gehen wir in Kapitel Funktionen, Seite 128, näher ein.
6.10 Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
halb ist es nicht verwunderlich, dass ein dict-Casting auf das obige Ergebnis das gewünschte Dictionary zurückliefert:
>>> landesueblich = dict(zip(laender, gerichte))
>>> landesueblich
{'Spanien': 'Paella', 'Italien': 'Pizza', 'Deutschland': 'Sauerkraut',
'USA': 'Hamburger'}
>>> landesueblich["Deutschland"]
'Sauerkraut'
>>> landesueblich["USA"]
'Hamburger'
6.10
Aufgaben
1. Aufgabe:
Bild 6.2 Dateimenü
Stellen Sie sich vor, wir wollen in einem Programm die Bezeichnungen für ein Dateimenü internationalisieren, also z.B. in Deutsch, Englisch, Französisch und Italienisch
anbieten.
Wir wollen ein verschachteltes Dictionary verwenden, in dem wir die Übersetzung für
die Menüpunkte zum Speichern, Speichern unter neuem Namen, Neue Datei öffnen,
Öffnen und Drucken angeben.
Zum Übersetzen der Begriffe:
Englisch – Deutsch – Französisch – Italienisch
File – Datei – Fichier – File
New – Neu – Nouveau – Nuovo
Open – Öffnen – Ouvrir – Apri
Save – Speichern – Enregistrer – Salva
Save as – Speichern unter – Enregistrer sous – Salva come
55
56
6 Dictionaries
Print Preview – Druckansicht – Apercu avant impressioner – Anteprima di stampa
Print – Drucken – Imprimer – Stampa
Close – Schließen – Fermer – Chiudi
Exit – Verlassen – Quitter – Esci
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Lösung: Lösungen zu Kapitel 6 (Dictionaries), Seite 478
2. Aufgabe:
Bild 6.3 Schachbrett
Überlegen Sie sich eine Darstellung des Schachbretts als Dictionary. Die Schlüssel
dieses Dictionaries sollen die Felder des Schachbretts sein und die Werte die Information, welche Figur sich auf einem Feld befindet.
Das Ergebnis könnte also beispielsweise von der Form („König”, „schwarz”) sein.
Ein Schachbrett besteht bekanntlich aus 64 Feldern, die in 8 Zeilen und 8 Spalten
angeordnet sind. Ein Feld des Schachbretts kann mit einem Tupel bezeichnet werden.
Die erste Komponente dieses Tupels entspricht den Spalten des Spielfeldes. Diese
werden mit Buchstaben zwischen „a” und „h” von links nach rechts gekennzeichnet.
Die zweite Komponente stellt eine Zahl zwischen 1 und 8 dar.
Lösung: Lösungen zu Kapitel 6 (Dictionaries), Seite 479
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
7
7.1
Mengen
Übersicht
Auch wenn die Mengenlehre über lange Zeit heftig kritisiert wurde und immer noch kritisiert
wird, ist sie ein wesentliches Gebiet der Mathematik. Die heutige Mathematik ist in der Terminologie der Mengenlehre formuliert und baut auf
deren Axiomen auf. Die Mengenlehre ist noch ein
recht junges Gebiet der Mathematik. Der deutsche Mathematiker Georg Cantor (1845 - 1918)
begründete die Mengenlehre mit seinem 1874 erschienenen Artikel „Über eine Eigenschaft des
Inbegriffes aller reellen algebraischen Zahlen”.
Anfangs, also bis ins Jahr 1877, bezeichnete er
übrigens die Mengenlehre noch als „Mannigfaltigkeitslehre”.
Bild 7.1 Mengen-Diagramme
1895 gab er folgende Definition einer Menge: „Unter einer ,Menge’ verstehen wir jede Zusammenfassung M von bestimmten wohlunterschiedenen Objekten m unserer Anschauung oder unseres Denkens (welche die ,Elemente’ von M genannt werden) zu einem Ganzen.”
Eine Menge kann beliebige Elemente enthalten: zum Beispiel Zahlen, Zeichen, Buchstaben, Wörter, Namen oder sogar andere Mengen. Eine Menge wird in der Mathematik üblicherweise mit einem Großbuchstaben bezeichnet.
7.2
Mengen in Python
Der Datentyp „set”, der ein sogenannter „collection”-Typ ist, ist in Python seit Version 2.4.
enthalten. Ein set enthält eine ungeordnete Sammlung von einmaligen und unveränderlichen Elementen. In anderen Worten: Ein Element kann in einem set-Objekt nicht mehrmals vorkommen, was bei Listen und Tupel jedoch möglich ist. Beim Datentyp set handelt
es sich um die Python-Implementierung von Mengen, wie sie aus der Mathematik bekannt
sind.
58
7 Mengen
7.2.1
Sets erzeugen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Will man eine Menge erzeugen, so ist dies in Python sehr einfach. Man bedient sich der
gewohnten mathematischen Schreibweise.
>>> staedte = {'Hamburg', 'München', 'Frankfurt', 'Berlin'}
>>> print(staedte)
{'München', 'Berlin', 'Frankfurt', 'Hamburg'}
>>> 'Berlin' in staedte
True
>>> 'Köln' in staedte
False
Im obigen Beispiel mag vielleicht verwundern, dass wir die geschweiften Klammern für
Mengen verwenden, da wir diese ja bisher für Dictionaries verwendet hatten.
Nun wollen wir zeigen, was passiert, wenn wir ein Tupel mit wiederholt auftretenden Elementen an die set-Funktion übergeben – in unserem Beispiel tritt die Stadt Paris mehrmals
auf:
>>> cities = set(("Paris", "Lyon", "London","Berlin","Paris","
Birmingham"))
>>> cities
{'Paris', 'Birmingham', 'Lyon', 'London', 'Berlin'}
>>>
Wie erwartet, gibt es keine doppelten Einträge in unserer resultierenden Menge.
Im folgenden Beispiel wird mit dem Casting-Operator set ein String in seine Zeichen vereinzelt, um die Menge der im String enthaltenen Buchstaben zu erhalten:
>>> x = set("A Python Tutorial")
>>> x
{'A', ' ', 'i', 'h', 'l', 'o', 'n', 'P', 'r', 'u', 't', 'a', 'y', 'T'}
>>> type(x)
<class 'set'>
>>>
7.2.2
Mengen von unveränderlichen Elementen
Sets sind so implementiert, dass sie keine veränderlichen (mutable) Objekte erlauben. Das
folgende Beispiel demonstriert, dass wir beispielsweise keine Listen als Elemente haben
können:
>>> cities = set((("Python","Perl"), ("Paris", "Berlin", "London")))
>>> cities = set((["Python","Perl"], ["Paris", "Berlin", "London"]))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
7.3 Frozensets
7.3
Frozensets
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Auch wenn sets keine veränderlichen Elemente enthalten können, sind sie selbst veränderlich. Wir können zum Beispiel neue Elemente einfügen:
>>> cities = {"Frankfurt", "Basel","Freiburg"}
>>> cities.add("Strasbourg")
>>> cities
{'Freiburg', 'Frankfurt', 'Basel', 'Strasbourg'}
Frozensets sind wie sets, aber sie können nicht verändert werden. Sie sind also unveränderlich (immutable):
>>> cities = frozenset(["Frankfurt", "Basel","Freiburg"])
>>> cities.add("Strasbourg")
Traceback (most recent call last):
File "<stdin&module>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
>>>
7.4
7.4.1
Operationen auf „set”-Objekten
add(element)
Mit der Methode add fügt man ein Objekt in eine Menge als neues Element ein, falls es
noch nicht vorhanden ist. Es gilt zu beachten, dass es sich dabei um ein unveränderliches
Element handelt. Im folgenden Beispiel fügen wir einen String ein:
>>> colours = {"red","green"}
>>> colours.add("yellow")
>>> colours
{'green', 'yellow', 'red'}
>>> colours.add(["black","white"])
Traceback (most recent call last):
File "<stdin&module>", line 1, in <module>
TypeError: unhashable type: 'list'
>>>
Selbstverständlich wird ein Objekt nur dann als neues Element eingefügt, wenn es noch
nicht enthalten ist. Ist es bereits enthalten, hat der Aufruf der Methode keine Auswirkungen.
59
60
7 Mengen
7.4.2
clear()
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Alle Elemente einer Menge werden entfernt. Die Menge ist also anschließend leer, wie wir
im folgenden Beispiel sehen:
>>> cities = {"Stuttgart", "Konstanz", "Freiburg"}
>>> cities.clear()
>>> cities
set()
>>>
7.4.3
copy
copy erzeugt eine flache Kopie einer Menge, die zurückgeliefert wird. Wir demonstrieren
die Benutzung anhand eines Beispiels:
>>> more_cities = {"Winterthur","Schaffhausen","St. Gallen"}
>>> cities_backup = more_cities.copy()
>>> more_cities.clear()
>>> cities_backup
{'St. Gallen', 'Winterthur', 'Schaffhausen'}
>>>
Nur für diejenigen, die glauben, dass eine einfache Zuweisung auch genügen könnte:
>>> more_cities = {"Winterthur","Schaffhausen","St. Gallen"}
>>> cities_backup = more_cities
>>> more_cities.clear()
>>> cities_backup
set()
>>>
Die Zuweisung „cities_backup = more_cities” erzeugt nur einen Pointer, d.h. einen weiteren Namen für das gleiche Objekt.
7.4.4
difference()
Diese Methode liefert die Differenz von zwei oder mehr Mengen zurück. Wir illustrieren
dies wie immer an einem Beispiel:
>>> x = {"a","b","c","d","e"}
>>> y = {"b","c"}
>>> z = {"c","d"}
>>> x.difference(y)
set(['a', 'e', 'd'])
>>> x.difference(y).difference(z)
{'a', 'e'}
>>>
7.4 Operationen auf „set”-Objekten
Statt die Methode difference zu benutzen, hätten wir auch den Operator „-” benutzen können:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> x
{'a',
>>> x
{'a',
>>>
7.4.5
- y
'e', 'd'}
- y - z
'e'}
difference_update()
Die Methode difference_update entfernt alle Elemente einer anderen Menge aus einer
Menge. „x.difference_update()” ist gleichbedeutend mit „x = x - y”
>>> x = {"a","b","c","d","e"}
>>> y = {"b","c"}
>>> x.difference_update(y)
>>> x
{'a', 'e', 'd'}
>>>
>>> x = {"a","b","c","d","e"}
>>> y = {"b","c"}
>>> x = x - y
>>> x
{'a', 'e', 'd'}
>>>
7.4.6
discard(el)
Das Element el wird aus einer Menge entfernt, falls es enthalten ist. Falls el nicht in der
Menge enthalten ist, passiert nichts.
>>> x = {"a","b","c","d","e"}
>>> x.discard("a")
>>> x
{'c', 'b', 'e', 'd'}
>>> x.discard("z")
>>> x
{'c', 'b', 'e', 'd'}
>>>
61
62
7 Mengen
7.4.7
remove(el)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Funktioniert wie discard(), aber falls el nicht in der Menge enthalten ist, wird ein Fehler
generiert, d.h. ein KeyError:
>>> x = {"a","b","c","d","e"}
>>> x.remove("a")
>>> x
{'c', 'b', 'e', 'd'}
>>> x.remove("z")
Traceback (most recent call last):
File "<stdin&module>", line 1, in <module>
KeyError: 'z'
>>>
7.4.8
intersection(s)
Liefert die Schnittmenge von s und der Instanzmenge zurück.
>>> x = {"a","b","c","d","e"}
>>> y = {"c","d","e","f","g"}
>>> x.intersection(y)
{'c', 'e', 'd'}
>>>
Dies kann auch mit dem „&”-Zeichen formuliert werden:
>>> x = {"a","b","c","d","e"}
>>> y = {"c","d","e","f","g"}
>>> x & y
{'c', 'e', 'd'}
>>>
7.4.9
isdisjoint()
Diese Methode liefert True zurück, wenn zwei Mengen eine leere Schnittmenge haben.
>>> x = {"a","b"}
>>> y = {"c","d"}
>>> z = {"b","c"}
>>> x.isdisjoint(y)
True
>>> x.isdisjoint(z)
False
7.4 Operationen auf „set”-Objekten
7.4.10
issubset()
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
x.issubset(y) liefert True zurück, falls x eine Untermenge von y ist. „<=” kann statt des Aufrufs der Methode verwendet werden. „<” prüft, ob es sich um eine echte Untermenge handelt: Wenn x < y gilt, dann enthält y mindestens ein Element, das nicht in x enthalten ist.
>>> x = {"a","b","c","d","e"}
>>> y = {"c","d"}
>>> x.issubset(y)
False
>>> y.issubset(x)
True
>>> x < y
False
>>> y < x # y ist eine echte Untermenge von x
True
>>> x < x # eine Menge kann nie eine echte Untermenge ihrer selbst
sein.
False
>>> x <= x
True
>>>
7.4.11
issuperset()
x.issuperset(y) liefert True zurück, falls x eine Obermenge von y ist. „>=” kann statt des
Aufrufs der Methode verwendet werden. Ein einfaches Größer-Zeichen „>” prüft, ob es sich
um eine echte Obermenge handelt: Wenn x > y gilt, dann enthält x mindestens ein Element,
das nicht in y enthalten ist.
>>> x = {"a","b","c","d","e"}
>>> y = {"c","d"}
>>> x.issuperset(y)
True
>>> x > y
True
>>> x >= y
True
>>> x >= x
True
>>> x > x
False
>>> x.issuperset(x)
True
>>>
63
64
7 Mengen
7.4.12
pop()
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
pop() liefert ein beliebiges Element der Menge zurück. Dieses Element wird dabei aus der
Menge entfernt. Die Methode erzeugt einen KeyError, falls die Menge leer ist.
>>> x = {"a","b","c","d","e"}
>>> x.pop()
'a'
>>> x.pop()
'c'
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
8
8.1
Eingaben
Eingabe mittels input
Es gibt kaum Programme, die ohne jegliche Eingaben auskommen. Eingaben können über viele Wege
erfolgen, so zum Beispiel aus einer Datenbank, von
einem anderen Rechner im lokalen Netzwerk oder
auch über das Internet. Die einfachste und wohl
auch häufigste Eingabe erfolgt jedoch über die Tastatur. Für diese Form der Eingabe bietet Python die
Funktion input (Eingabeprompt).
Kommt es zum Aufruf der Funktion input während
eines Programmlaufs, wird der Programmablauf solange gestoppt, bis die Benutzerin oder der Benutzer eine Eingabe über die Tastatur tätigt und diese mit der Return-Taste abschließt. Damit der User
auch weiß, was er einzugeben hat, wird der String
des Parameters „Eingabeprompt” ausgegeben, sofern ein solcher String existiert. Der Parameter „Eingabeprompt” von input() ist optional.
Der Eingabestring des Benutzers wird von input()
nicht interpretiert, d.h. input() liefert immer einen
String zurück:
>>> eingabe = input("Ihre Eingabe? ")
Ihre Eingabe? 34
>>> eingabe
'34'
>>> eingabe = input("Ihre Eingabe? ")
Ihre Eingabe? [3,5,8]
>>> eingabe
'[3,5,8]'
>>> type(eingabe)
<class 'str'>
>>>
Bild 8.1 Eingabe
66
8 Eingaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Meistens wird auf den Ergebnisstring von input sofort der entsprechende cast-Operator
angewendet, um den für die Weiterverarbeitung benötigten Datentyp zu erhalten:
>>> alter = int(input("Ihr Alter? "))
Ihr Alter? 42
>>> alter, type(alter)
(42, <class 'int'>)
>>>
>>> preis = float(input("Artikelpreis? "))
Artikelpreis? 33.99
>>> preis, type(preis)
(33.99, <class 'float'>)
>>>
Eine unangenehme Überraschung erlebt manche Anfängerin oder Anfänger, wenn man
eine Liste einlesen will und diese entsprechend mittels „list” umwandelt:
>>> farben = list(input("Farben? "))
Farben? ["rot", "grün", "blau"]
>>> farben
['[', '"', 'r', 'o', 't', '"', ',', ' ', '"', 'g', 'r', 'ü', 'n', '"',
',', ' ', '"', 'b', 'l', 'a', 'u', '"', ']']
>>>
Eigentlich ist es klar und logisch, was passiert ist. Python wandelt den String in eine Liste, indem jedes einzelne Zeichen zu einem Listenelement wird. Wir hätten aber gerne die
einzelnen Farben als Elemente. Dazu müssen wir die Funktion eval benutzen. eval interpretiert die Eingabe und liefert den entsprechenden Datentyp zurück. Dies funktioniert
nicht nur für Listen, sondern auch für andere Datentypen wie beispielsweise Tupel und
Dictionaries.
>>> farben = eval(input("Farben? "))
Farben? ["rot", "grün", "blau"]
>>> farben, type(farben)
(['rot', 'grün', 'blau'], <class 'list'>)
>>>
>>> haeufigkeiten = eval(input("Häufigkeiten? "))
Häufigkeiten? {"a":5, "b":7, "c":3}
>>> haeufigkeiten, type(haeufigkeiten)
({'a': 5, 'c': 3, 'b': 7}, <class 'dict'>)
>>>
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
9
9.1
Verzweigungen
Anweisungsblöcke und Einrückungen
Eigentlich geht es in diesem Kapitel um Verzweigungen. Allerdings müssen wir dazu wissen, wie
man mehrere Anweisungen zu einem Block, einem
Anweisungsblock, zusammenfasst. Außerdem lassen
sich Blöcke sehr gut im Zusammenhang mit Verzweigungen erklären. Wir kennen das Problem der Blockbildung und der Kennzeichnung der Zusammengehörigkeit auch aus der Umgangssprache:
Wenn es morgen regnet, werde ich den Keller
entrümpeln. Dann werde ich die Schränke aufräumen und die Fotos sortieren.
Im obigen Text wird eine Folge von Aktionen be- Bild 9.1 Mainzer Psalter
schrieben, die in einer zeitlichen Abfolge durchgeführt werden sollen. Eigentlich scheint der Text eindeutig zu sein. Eine gewisse Unsicherheit bleibt jedoch: Ist das Aufräumen der Schränke und die Sortierung der Fotos auch an
den Regen gekoppelt? Was, wenn es nicht regnet? Der folgende Text gibt darüber Auskunft:
Wenn es morgen regnet, werde ich den Keller entrümpeln. Dann werde ich die Schränke aufräumen und die Fotos sortieren. Ansonsten werde ich schwimmen gehen.
Abends gehe ich mit meiner Frau ins Kino.
Im Text haben wir bereits eine bedingte Anweisung bzw. Verzweigung in umgangssprachlicher Form. Kann sich nun seine Frau auf den Kinobesuch freuen oder muss sie dafür auch
auf Regen hoffen? Obigen Text können wir auch in einer Python nachempfundenen Form
schreiben, um dadurch eine größere Eindeutigkeit zu schaffen.
Wenn es morgen regnet, dann werde ich Folgendes tun:
den Keller entrümpeln.
die Schränke aufräumen
die Fotos sortieren.
Ansonsten, also wenn es nicht regnet, werde ich Folgendes tun:
schwimmen gehen.
Abends gehe ich mit meiner Frau ins Kino.
68
9 Verzweigungen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Einen solchen bedingten Arbeitsablauf formuliert man für Algorithmen meistens mit sogenannten Programmablaufplänen bzw. Flussdiagrammen:
Die Strukturierung von Programmen mittels Anweisungsblöcken ist ein Charakteristikum
von Programmiersprachen. So kann man bereits ein komplettes Programm als einen Block
sehen, in den – in der Regel – viele weitere Blöcke verschachtelt sind. Es gibt verschiedene Methoden, solche Blöcke syntaktisch zusammenzufassen. Sprachen wie Algol 60 und
Pascal benutzen dazu begin und end. C, C++ und Java fassen ihre Blöcke durch geschweifte Klammern, also „{” und ”}”, zusammen. In manchen Skriptsprachen wie beispielsweise
Bash werden Blöcke mittels do ... done, if ... fi oder case ... esac zusammengefasst.
Einen Nachteil haben alle diese Methoden: Für den Interpreter oder Compiler einer Sprache mag der Code eindeutig sein, aber Programmierer schreiben den Code auf eine Art,
dass er schlecht strukturiert ist. Wir wollen dies an einem „pseudo” C-Code-Schnipsel verdeutlichen:
if ( raining ) {
keller_entruempeln();
schraenke_aufraeumen();
fotos_sortieren();
} else
schwimmen_gehen();
kino_gehen(ehefrau);
Stellen Sie sich vor, das Programm stünde wie folgt im Editor:
if ( raining ) {
keller_entruempeln();
schraenke_aufraeumen();
fotos_sortieren();
} else
9.1 Anweisungsblöcke und Einrückungen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
schwimmen_gehen();
kino_gehen(ehefrau);
Nun sieht es so aus, als gehöre der Aufruf „kino_gehen(ehefrau)” auch zum „else”-Fall, d.h.
der Kinobesuch findet also nur statt, falls es nicht regnet. Für C ist es aber egal, ob die Zeile
eingerückt ist oder nicht. Die Bedeutung ist immer gleich: Der Kinobesuch findet unabhängig vom Regen statt! Anders ausgedrückt: Es ist möglich, C-Code unübersichtlich oder
verwirrend zu schreiben. Im obigen Fall suggeriert die Anordnung der Zeilen, also die Einrückungen, ein falsches Verständnis.
In Python ist dies komplett anders. Durch Einrückungen wird die Zugehörigkeit zu Blöcken festgelegt. Die erste, also die „richtige” Version unseres C-Programms sieht in PseudoPython-Code wie folgt aus:
if raining:
keller_entruempeln()
schraenke_aufraeumen()
fotos_sortieren()
else:
schwimmen_gehen()
kino_gehen(ehefrau)
Der Kinobesuch mit Ehefrau ist in obigem Programm unabhängig vom Regen „vorprogrammiert”. Rückt man „kino_gehen(ehefrau)” auf das Niveau von „schwimmen_gehen()”
ein, findet der Kinobesuch nur noch statt, wenn es nicht regnet.
Die Einrückung von Zeilen und die Benutzung von Leerzeichen am Anfang von Zeilen
dienen also bei Python als Strukturierungselement. Programmierer werden dadurch „gezwungen”, übersichtlichen und unmissverständlichen Code zu schreiben, zumindest was
die Blockzugehörigkeit betrifft.
In unserem Beispiel haben wir gesehen, dass nach dem „if” und nach dem „else” ein Doppelpunkt folgt. Dieser Doppelpunkt nach einem sogenannten Anweisungskopf leitet immer einen eingerückten Block ein. Das heißt, es folgen beliebig viele, aber mindestens eine
Anweisung, die alle um die gleiche Anzahl von Leerzeichen eingerückt sind. Apropos Leerzeichen: Man kann auch Tabulatoren statt Leerzeichen verwenden. Man sollte aber auf jeden Fall vermeiden, Tabulatoren und Leerzeichen bei den Einrückungen zu mischen, weil
es bei der gleichzeitigen Benutzung verschiedener Editoren, IDE oder Betriebssysteme zu
Problemen kommen kann, da die Anzahl der Leerzeichen pro Tabulator unterschiedlich
eingestellt sein kann.
Allgemein sieht es also so aus:
Anweisungskopf:
Anweisung
Anweisung
Anweisung
69
70
9 Verzweigungen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
9.2
Bedingte Anweisungen in Python
Im vorigen Abschnitt haben wir bereits gesehen, wie
man den rein linearen Programmablauf durch bedingte Anweisungen beeinflussen und kontrollieren
kann. Neben den bedingten Anweisungen, also den
Verzweigungen, gibt es noch die Schleifen als weitere Kontrollstrukturen.
In diesem Kapitel beschäftigen wir uns mit bedingten Anweisungen. Mit ihnen wird sichergestellt,
dass manche Codeteile nur unter bestimmten BeBild 9.2 Verzweigungen
dingungen ausgeführt werden. Sind die Bedingungen nicht erfüllt, wird dieser Code nicht ausgeführt.
Anders ausgedrückt: Eine Verzweigung legt fest, welcher von zwei (oder auch mehr) Programmteilen (Alternativen) in Abhängigkeit von einer (oder mehreren) Bedingungen ausgeführt wird. Bedingte Anweisungen und Verzweigungen werden in Programmiersprachen
(ebenso wie die Schleifen) den Kontrollstrukturen zugerechnet, weil mit ihrer Hilfe ein Programm auf verschiedene Zustände reagieren kann, die sich aus Eingaben und Berechnungen ergeben.
9.2.1
Einfachste if-Anweisung
Die einfachste Form einer if-Anweisung sieht in Python wie folgt aus:
if bedingung:
anweisungen
Der eingerückte Codeblock wird nur ausgeführt, wenn die Bedingung „bedingung” wahr,
also True ist.
Im Folgenden zeigen wir ein kleines Beispielprogramm zur Prüfung der Altersbeschränkung im Kino:
alter = int(input("Dein Alter? "))
if alter < 12:
print("Sorry, der Film ist erst ab 12!")
Im Folgenden wird das Programm zweimal gestartet:
$ python3 alterscheck.py
Dein Alter? 11
Sorry, der Film ist erst ab 12!
$ python3 alterscheck.py
Dein Alter? 14
9.2 Bedingte Anweisungen in Python
9.2.2
if-Anweisung mit else-Zweig
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir sehen, dass wir keine Ausgabe erhalten, wenn ein potenzieller Besucher alt genug für
den Film ist. Dazu können wir unsere if-Anweisung um einen else-Zweig erweitern. Der
else-Zweig wird immer dann ausgeführt, wenn das Alter größer oder gleich 12 ist, d.h. wenn
die Bedingung nicht erfüllt ist:
$ python3 alterscheck.py
Dein Alter? 11
Sorry, der Film ist erst ab 12!
$ python3 alterscheck.py
Dein Alter? 14
Okay, viel Spaß!
9.2.3
elif-Zweige
Wir schreiben nun ein Programm für einen Kinderfilm, der keine Altersbeschränkungen
hat. Wir gehen aber davon aus, dass Kinder unter 4 ihn nicht verstehen werden und Jugendliche über 16 ihn zu albern finden werden. Aber ab 12 mag der Genuss schon fraglich
sein. In diesem Programm benützen wir verschachtelte „if”-Anweisungen:
alter = int(input("Dein Alter? "))
if alter < 4:
print("Film ist zu kompliziert!")
else:
if alter < 12:
print("Okay, viel Spaß!")
else:
if alter < 16:
print("Bist du sicher, ob das der richtige Film ist?")
else:
print("Wollen Sie sich das wirklich antun?")
So würde allerdings kaum jemand programmieren. Statt der verschachtelten „if”-Anweisungen können wir „elif” benutzen. „elif” stellt wie der Name andeutet eine Abkürzung für
„else if” dar:
alter = int(input("Dein Alter? "))
if alter < 4:
print("Film ist zu kompliziert!")
elif alter < 12:
print("Okay, viel Spaß!")
elif alter < 16:
print("Bist du sicher, ob das der richtige Film ist?")
else:
print("Wollen Sie sich das wirklich antun?")
71
72
9 Verzweigungen
9.3
Vergleichsoperatoren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
In den obigen Beispielen hatten wir bereits Vergleichsoperatoren benutzt. Wir stellen sie
hier mit weiteren Vergleichsoperatoren in einer Tabelle zusammen:
Operator
Erklärung
Beispiel
Wahrheitswert
==
Prüfung auf
Gleichheit
42 == 42 oder [3, 4, 8] == [3, 4, 8]
True
!=
Prüfung auf
Ungleichheit
42 != 43 oder [3, 4, 8] != 17
True
<
Prüfung auf
„kleiner”
4 < 12 oder "Tisch" < "Tischbein"
True
<=
Prüfung auf
„kleiner gleich”
4 <= 12 oder "Tischbein" <= "Tischbein"
True
>
Prüfung auf
„größer”
4 > 12 oder "Tisch" > "Tischbein"
False
>=
Prüfung auf
„größer gleich”
40 >= 12 oder "Tischbein" >= "Tischbein"
True
9.4
Zusammengesetzte Bedingungen
Man kann auch Bedingungen mittels der logischen Operatoren „and”, „or” und „not” zusammenfügen.
Beispiel:
>>> a = 47
>>> 20 < a and a < 100
True
9.5
Wahr oder falsch
Eine Bedingung kann auch aus einer einzigen Variablen bestehen wie im folgenden Beispiel:
x = int(input("Zahl: "))
if x:
print("Die eingegebene Zahl ist ungleich Null")
else:
print("Die eingegebene Zahl ist gleich Null")
9.6 Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Eine Variable kann ein beliebiges Objekt referenzieren. Es stellt sich dann die Frage, welchen Wahrheitswert wir diesem Objekt zuordnen, wenn die Variable als Bedingung benutzt
wird. Es funktioniert ähnlich wie im Gesetz: „Was nicht verboten ist, ist erlaubt”. Dadurch
wird vermieden, dass es Lücken im Gesetz gibt. Ähnlich verhält es sich auch mit den Bedingungen in Python. Alles, was nicht False ist, hat den Wert True. Wir brauchen also nur
zu definieren, was „falsch”, also False ist:
■
numerische Null-Werte(0, 0L, 0.0, 0.0+0.0j),
■
der boolesche Wert False,
■
leere Zeichenketten,
■
leere Listen, leere Tupel,
■
leere Dictionaries
■
sowie der spezielle Wert None.
Alle anderen Werte betrachtet Python als „wahr”, also True.
Beispiel:
>>> liste = []
>>> if liste:
...
print("Liste enthält Elemente!")
... else:
...
print("Liste ist leer!")
...
Liste ist leer!
9.6
Aufgaben
1. Aufgabe:
Kinder- und Hundeliebhaber stellen sich häufig die Frage, wie alt ihr Hund wohl wäre,
wenn er kein Hund, sondern ein Mensch wäre. Landläufig rechnet man Hundejahre
in Menschenjahre um, indem man das Alter des Hundes mit 7 multipliziert. Je nach
Hundegröße und Rasse sieht die Umrechnung jedoch etwas komplizierter aus, z.B.:
■
Ein einjähriger Hund entspricht in etwa einem 14-jährigen Menschen.
■
2 Jahre eines Hundes entsprechen 22 Jahre eines Menschen.
■
Ab dann entspricht ein Hundejahr jeweils 5 Menschenjahren.
Schreiben Sie ein Programm, das das Alter eines Hundes erfragt und dann nach obiger Methode berechnet, welchem Alter in Menschenjahren das entspricht.
Lösung: Lösungen zu Kapitel 9 (Verzweigungen), Seite 480
73
74
9 Verzweigungen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
2. Aufgabe:
Ein Kalenderjahr hat bekanntlich 365 oder 366 Tage. Nach dem Gregorianischen Kalenderjahr dauert ein Jahr exakt 365.2425 Tage, also 365 Tage, 5 Stunden, 49 Minuten, 12 Sekunden oder anders ausgedrückt: 31.556.952 Sekunden. Man sieht, dass
ein Jahr also grob gesprochen einen Viertel Tag länger ist als 365 Tage. Um diesen
Unterschied zu beheben, hat man Schalttage eingefügt. Alle vier Jahre wird mit dem
29. Februar ein Schalttag eingefügt. Allerdings machen wir damit einen neuen kleinen „Fehler”, denn nun haben wir einen Hundertertstel Tag zuviel. Aus diesem Grunde
wird alle Hundert Jahre – und zwar, wenn die Jahreszahl durch Hundert teilbar ist –
auf einen Schalttag verzichtet. So war beispielsweise das Jahr 1900 kein Schaltjahr,
obwohl es durch vier teilbar war. Man benötigt jedoch noch alle 400 Jahre ein weiteres Korrektiv, dann wird ein Schalttag eingefügt, obwohl die Jahreszahl durch Hundert
teilbar ist. Nach dieser Regel war das Jahr 2000 ein Schaltjahr.
Schreiben Sie nun ein Python-Programm, das berechnet, ob eine gegebene Jahreszahl ein Schaltjahr ist oder nicht.
Lösung: Lösungen zu Kapitel 9 (Verzweigungen), Seite 480
3. Aufgabe:
Auch bei dieser Aufgabe bleiben wir bei der Zeitrechnung. Zu einer gegebenen Zeit in
Stunden, Minuten und Sekunden sollen Sie eine Sekunde hinzuzählen.
Aus der Uhrzeit 12:59:59 wird beispielsweise nach der Addition von einer Sekunde die
Zeit 13:00:00 Uhr.
Lesen Sie Stunden-, Minuten- und Sekundenwerte der Uhrzeit in drei Integer-Werte
ein.
Das Programm soll die neue Uhrzeit in der obigen Form ausgeben, also hh:mm:ss.
Lösung: Lösungen zu Kapitel 9 (Verzweigungen), Seite 481
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
10
10.1
Schleifen
Übersicht
Schleifen werden benötigt, um einen Codeblock, also eine oder mehrere PythonAnweisungen, wiederholt auszuführen.
Einen solchen Codeblock bezeichnet man
auch als Schleifenkörper oder Body. In
Python gibt es zwei Schleifentypen: die
while-Schleife und die for-Schleife.
Die meisten Schleifenarten, die in Programmiersprachen Verwendung finden,
enthalten einen Zähler oder ganz allgemein Variablen, die im Verlauf der Berechnungen innerhalb des Schleifenkörpers ihre Werte ändern. Außerhalb, das heißt
noch vor Beginn der Schleife, werden diese
Variablen initialisiert. Vor jedem Schleifendurchlauf wird geprüft, ob ein Ausdruck,
in dem diese Variable oder Variablen vorkommen, wahr ist. Dieser Ausdruck bestimmt das Endekriterium der Schleife. Solange die Berechnung dieses Ausdrucks
wahr ist, d.h. „True” liefert, wird der Rumpf Bild 10.1 Karussell
der Schleife ausgeführt. Nachdem alle Anweisungen des Schleifenkörpers durchgeführt worden sind, springt die Programmsteuerung automatisch zum Anfang der Schleife,
also zur Prüfung des Endekriteriums zurück und prüft wieder, ob diese nochmals erfüllt ist.
Wenn ja, geht es wie oben beschrieben weiter, ansonsten wird der Schleifenkörper nicht
mehr ausgeführt, und es wird mit dem Rest des Skripts fortgefahren. Das unten stehende
Diagramm zeigt dies schematisch.
76
10 Schleifen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
10.2
while-Schleife
Das folgende Skript, das wir in der interaktiven Shell direkt eintippen können, gibt die Zahlen von 1 bis 4, gefolgt von ihrem Quadrat, unter Benutzung einer while-Schleife aus:
>>> i = 1
>>> while i <= 4:
...
print(i, i**2)
...
i += 1
...
1 1
2 4
3 9
4 16
Auch die Summe der Zahlen von 1 bis 100 lässt sich mittels einer while-Schleife leicht berechnen, wie wir im folgenden Programm sehen können:
n = 100
sum = 0
i = 1
while i <= n:
sum = sum + i
i = i + 1
print("Summe von 1 bis " + str(n) + ": " + str(sum) )
Zu obigem Programm lässt sich Folgendes sagen. Zur Aufnahme der Summe haben wir
eine Variable sum benutzt. sum ist aber auch eine Python-Funktion, die die Summe einer
aus numerischen Werten bestehenden Liste oder eines Tupels berechnet:
>>> sum([3, 4.5, 9])
16.5
Damit hätten wir uns natürlich auch das obige Programm zur Berechnung der Summe der
Zahlen von 1 bis 100 sparen können, indem wir „sum” und „range” benutzen:
>>> n = 100
>>> sum(range(1, n+1))
5050
10.3 break und continue
Einige kennen sicherlich auch die Gaußsche Summenformel, mit der wir die Aufgabenstellung auch ohne sum und range direkt lösen können:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> n = 100
>>> summe = n * (n + 1) / 2
>>> summe
5050.0
10.3
break und continue
Für die Schleifen existieren zwei wichtige Anweisungen: „break” zum vorzeitigen Abbruch
der Schleife und „continue”, um einen Durchlauf zu beenden und mit dem nächsten
Durchlauf bzw. der Überprüfung der Abbruchbedingung weiterzumachen. Die breakAnweisung steht innerhalb des Schleifenrumpfs meist in Verbindung mit einer if-Abfrage.
Stößt der Programmablauf auf eine break-Anweisung, wird die Schleife unmittelbar abgebrochen. Das Programm wird mit der ersten Anweisung nach der Schleife fortgesetzt. Bei
geschachtelten Schleifen wird mittels break nur die innerste Schleife abgebrochen. Trifft
der Programmablauf auf eine continue-Anweisung, wird der Schleifendurchlauf abgebrochen, und der Programmablauf kehrt zum Schleifenkopf zurück, wo geprüft wird, ob die
Bedingung für einen weiteren Durchlauf erfüllt ist.
Im folgenden Beispiel benutzen wir sowohl break als auch continue. Falls ein doppelter
Eintrag in der Liste steht, wird mittels continue auf das nächste Listenelement weitergegangen. Falls die Liste eine negative Zahl oder eine Null enthält, wird die Schleife komplett
abgebrochen:
liste = eval(input("Liste mit positiven Zahlen eingeben: "))
n = len(liste)
i = 0
previous = None
erg = []
while i < n:
current = liste[i]
i += 1
if current == previous:
continue
if current <= 0:
print("Abbruch: Nicht-positive Zahl gefunden!")
break
erg.append(current)
previous = current
print(erg)
77
78
10 Schleifen
Zum besseren Verständnis des obigen Programms zeigen wir einen Beispiellauf:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
$ python3 break_example.py
Liste mit positiven Zahlen eingeben: [37, 99, 123, 17, 17, 17, 89, 32,
-3]
Abbruch: Nicht-positive Zahl gefunden!
[37, 99, 123, 17, 89, 32]
10.4
else-Teil
C-Programmierern1 wie auch Programmierern anderer Programmiersprachen kommt es
meist sehr merkwürdig vor, wenn sie ein else ohne zugehöriges if finden. Bei Schleifen
kann ein else-Teil folgen, das muss aber nicht so sein. Die Anweisungen im else-Teil werden ausgeführt, sobald die Bedingung nicht mehr erfüllt ist. Sicherlich fragen sich einige
nun, worin dann der Unterschied zu einer normalen while-Schleife liegt. Hätte man die
Anweisungen nicht in den else-Teil gesteckt, sondern einfach hinter die while-Schleife gestellt, wären sie ja auch genauso ausgeführt worden. Der else-Teil einer while-Schleife wird
erst zusammen mit dem break-Kommando sinnvoll. Normalerweise wird eine Schleife nur
beendet, wenn die Bedingung im Schleifenkopf erfüllt ist. Mit break kann man aber eine
Schleife vorzeitig – also gewissermaßen als Notausstieg – verlassen.
Im folgenden Beispiel, einem einfachen Zahlenratespiel, kann man erkennen, dass in
Kombination mit einem break der else-Zweig durchaus sinnvoll sein kann. Nur wenn die
while-Schleife regulär beendet wird, d.h. der Spieler die Zahl erraten hat, gibt es einen
Glückwunsch. Gibt der Spieler auf, d.h. break, dann wird der else-Zweig der while-Schleife
nicht ausgeführt.
import random
n = 20
to_be_guessed = random.randint(1, n)
1
Es gibt auch eine else-Schleife in C und C++. Diese wird aber nur selten verwendet.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
10.4 die Alternative im Erfolgsfall: else
guess = 0
while guess != to_be_guessed:
guess = int(input("Neuer Versuch: "))
if guess > 0:
if guess > to_be_guessed:
print("Zu gross")
elif guess < to_be_guessed:
print("Zu klein")
else:
print("Schade, dass du aufgibst!")
break
else:
print("Gratuliere, das war's")
Die Ausgabe einer Spielsitzung könnte beispielsweise so aussehen:
$ python3 number_game.py
Dein/Ihr Versuch: 10
Zu klein
Dein/Ihr Versuch: 15
Zu klein
Dein/Ihr Versuch: 18
Gratuliere, das war's
$
Bei der while-Schleife von Python handelt es sich um eine sogenannte kopfgesteuerte
Schleife. Bevor der Schleifenrumpf ausgeführt wird, muss geprüft werden, ob die Bedingung im Schleifenkopf, also nach dem Schlüsselwort „while”, erfüllt ist. Bei einer kopfgesteuerten Schleife erfolgt also immer zuerst die Abfrage der im Schleifenkopf stehenden
Bedingung, bevor der Schleifenrumpf ausgeführt wird. Manchmal würde man lieber zuerst den Schleifenrumpf ausführen und dann eine Abbruchsbedingung prüfen. Dies ist
ein Schleifenkonstrukt, welches viele Programmiersprachen bieten und unter dem Namen
„fußgesteuerte Schleife” bekannt ist, z.B. in der syntaktischen Form „do SCHLEIFENKÖRPER until ABBRUCHBEDINGUNG” oder „repeat SCHLEIFENKÖRPER until ABBRUCHBEDINGUNG”. Python bietet keine fußgesteuerte Schleife. Man kann diese jedoch mit einem
„while True” in Verbindung mit einer „break”-Anweisung am Ende des Schleifenkörpers
simulieren. Wir demonstrieren dies im folgenden Beispiel:
import random
n = 20
to_be_guessed = random.randint(1,n)
guess = 0
while True:
guess = int(input("Ihr Versuch: "))
if guess > 0:
if guess > to_be_guessed:
print("Zu gross")
79
80
10 Schleifen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
elif guess < to_be_guessed:
print("Zu klein")
# repeat until:
if guess == to_be_guessed:
print("Glückwunsch, das war's")
break
10.5
For-Schleife
Wie auch die while-Schleife ist die for-Schleife eine Kontrollstruktur, mit der eine Gruppe von Anweisungen (ein Block) wiederholt ausführt werden kann. Die Syntax der forSchleifen unterscheidet sich in den verschiedenen Programmiersprachen. Ebenso ist die
Semantik einer for-Schleife, also wie sie vom Compiler oder Interpreter zu verstehen bzw.
auszuführen ist, von Programmiersprache zu Programmiersprache unterschiedlich. Die
„klassische” numerische Schleife, wie sie C und C++ kennt, besitzt eine Schleifenvariable, die mit einem Startwert initialisiert wird und sich nach jedem Durchlauf des Schleifenkörpers verändert, d.h. meistens um einen bestimmten Wert (z.B. 1) erhöht oder vermindert wird, bis der definierte Zielwert erreicht ist. Man nennt diese Schleifenform auch
Zählschleife, weil die Schleifenvariable und damit auch der Startwert, der Endwert und die
Schrittweite numerisch sein müssen.
Im folgenden Beispiel sehen wir eine for-Schleife in C, die die Zahlen von 1 bis 100 ausdruckt:
for( i = 0; i < 100; i++)
printf("i: %d\n", i);
Auch wenn Sie diese Schleifenform bereits in C oder einer anderen Sprache liebgewonnen
haben, müssen wir Sie leider enttäuschen: Python kennt keine solche for-Schleife. Wohlgemerkt „keine solche”, aber sehr wohl eine for-Schleife. Die in Python benutzte Art von
for-Schleife entspricht der in der Bash-Shell oder in Perl verwendeten foreach-Schleife. Bei
dieser Schleifenart handelt es sich um ein Sprachkonstrukt, mit dessen Hilfe nacheinander beispielsweise die Elemente einer Menge oder Liste bearbeitet werden können. Dazu
werden sie einer Variable zugewiesen.
Im Folgenden sehen wir die allgemeine Syntax der for-Schleife in Python. Sequenz steht
für ein iterierbares Objekt.
for Variable in Sequenz:
Anweisung_1
Anweisung_2
...
Anweisung_n
else:
Else-Anweisung_1
Else-Anweisung_2
...
Else-Anweisung_m
10.5 For-Schleife
Wie bereits gesagt, dient in Python die for-Schleife zur Iteration über eine Sequenz von
Objekten, während sie in vielen anderen Sprachen meist nur „eine etwas andere whileSchleife” ist.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiel einer for-Schleife in Python:
>>> languages = ["C", "C++", "Perl", "Python"]
>>> for language in languages:
...
print(language)
...
C
C++
Perl
Python
>>>
Wie die while-Schleife besitzt auch die for-Schleife einen optionalen else-Block. Wie bei
der while-Schleife wird der else-Block nur ausgeführt, wenn die Schleife nicht durch eine
break-Anweisung abgebrochen wurde. Das bedeutet, dass der else-Block nur dann ausgeführt wird, wenn alle Elemente der Sequenz abgearbeitet worden sind.
Trifft der Programmablauf auf eine break-Anweisung, so wird die Schleife sofort verlassen
und das Programm nach der Anweisung fortgesetzt, die der for-Schleife folgt, falls es überhaupt noch Anweisungen nach der for-Schleife gibt.
Üblicherweise befindet sich die break-Anweisung wie im folgenden Beispiel innerhalb einer Konditionalanweisung:
essbares = ["Schinken", "Speck", "Spinat", "Nüsse"]
for gericht in essbares:
if gericht == "Spinat":
print("Ich mag keinen Spinat!")
break
print("Lecker, " + gericht)
else:
print("Glück gehabt, kein Spinat dabei gewesen!")
print("Jetzt bin ich satt!")
Wenn wir obiges Beispiel unter kein_spinat.py speichern und aufrufen, erhalten wir folgende Ausgaben:
bernd@marvin ~/beispiele/kein_spinat.py
Lecker, Schinken
Lecker, Speck
Ich mag keinen Spinat!
Jetzt bin ich satt!
$
Wenn wir in der Liste der essbaren Dinge „Spinat” durch „Schokolade” austauschen, erhalten wir folgende Ausgabe:
81
82
10 Schleifen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
bernd@marvin ~/beispiele $ python3 kein_spinat.py
Lecker, Schinken
Lecker, Speck
Lecker, Schokolade
Lecker, Nüsse
Glück gehabt, kein Spinat dabei gewesen!
Jetzt bin ich satt!
Vielleicht ist unsere Abscheu vor Spinat aber doch nicht so groß, dass wir sofort aufhören
zu essen, wie es in unserem Programm der Fall ist. In diesem Fall kommt die continueAnweisung ins Spiel. Im folgenden kleinen Skript benutzen wir continue, um mit dem
nächsten Artikel der essbaren Dinge weiterzumachen. „continue” schützt uns davor, „Spinat” essen zu müssen:
essbares = ["Schinken", "Speck", "Spinat", "Nüsse"]
for gericht in essbares:
if gericht == "Spinat":
print("Ich mag keinen Spinat!")
continue
print("Lecker, " + gericht)
else:
print("Glück gehabt, kein Spinat dabei gewesen!")
print("Jetzt bin ich satt!")
Die Ausgabe sieht dann wie folgt aus:
bernd@marvin ~/dropbox/python-buch/dritte_auflage_neu/beispiele $
python3 kein_spinat.py
Lecker, Schinken
Lecker, Speck
Ich mag keinen Spinat!
Lecker, Nüsse
Glück gehabt, kein Spinat dabei gewesen!
Jetzt bin ich satt!
$
Mit for-Schleifen können wir über beliebige iterierbare Objekte iterieren, wie wir in den
folgenden Beispielen zeigen So können wir beispielsweise auch über Dictionaries iterieren,
obwohl diese ja keine Reihenfolge aufweisen:
cities = {"London":
"Berlin":
"Madrid":
"Rome":
for city in cities:
print(city)
8615246,
3562166,
3165235,
2874038}
10.6 Aufgaben
An der Ausgabe erkennen wir, dass eine Iteration der Iteration über die Keys entspricht:
bernd@marvin ~/tmp $ python3 for_examples.py
London
Rome
Madrid
Berlin
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Iterieren wir über einen String, so tun wir dies buchstabenweise:
for char in "Berlin":
print(char)
Die Ausgabe sieht wie folgt aus:
bernd@marvin ~/tmp $ python3 for_examples.py
B
e
r
l
i
n
10.6
Aufgaben
Für die erste Aufgabe benötigen wir die römischen Zahlen. Für diejenigen, die mit römischen Zahlen nicht so ganz sicher sind, geben wir hier die Zuordnungen in der folgenden
Tabelle.
Tabelle 10.1 Römische Zahlen
Römische Zahl
Wert (Dezimalzahl)
I
1
II
2
III
3
IV
4
V
5
VI
6
VII
7
VIII
8
IX
9
X
10
XI
11
XIV
14
(Fortsetzung nächste Seite)
83
84
10 Schleifen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Tabelle 10.1 Römische Zahlen (Fortsetzung)
Römische Zahl
Wert (Dezimalzahl)
XV
15
XVI
16
...
...
XIX
19
XX
20
XXI
21
...
...
XXIX
29
XXX
30
XL
40
L
50
LX
60
XC
90
C
100
CC
200
CD
400
D
500
CM
900
M
1000
MM
2000
1. Aufgabe:
Schreiben Sie ein Python-Programm, das eine beliebige römische Zahl in eine
„gewöhnliche” Dezimalzahl umrechnet.
Lösung: 40.4 (1. Aufgabe), Seite 482
2. Aufgabe:
Bild 10.2 Achtung: Frösche
In der nächsten Aufgabe lernen wir einen besonderen Frosch kennen, so wie ihn sich
nur Mathematiker ausdenken können. Besonders seine Art, eine Straße zu über-
10.6 Aufgaben
queren, macht es zweifelhaft, ob er in der realen Welt lange überleben könnte. Er
überquert eine 2,50 Meter breite Straße wie folgt: Mit dem ersten Sprung legt er die
erstaunliche Distanz von einem Meter zurück, dann springt er wegen zunehmender
Erschöpfung mit jedem weiteren Schritt immer nur noch halb so weit wie vorher.
Die Entfernung, die er dabei zurücklegt, berechnet sich also als Summe der Werte
1 + 0, 5 + 0.25 + 0, 125 und so weiter.
Dies entspricht natürlich in mathematischer Schreibweise der folgenden Notation:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
n 1
X
i =0
2i
= 1+
1 1 1
1
+ + +
...
2 4 8 16
Versuchen Sie, mittels eines Python-Programms herauszubekommen, ob der Frosch
es auf die andere Straßenseite schafft.
Lösung: 40.4 (2. Aufgabe), Seite 483
3. Aufgabe:
Der indische Herrscher Shihram tyrannisierte seine Untertanen und stürzte sein Land
in Not und Elend. Um die Aufmerksamkeit des Königs auf seine Fehler zu lenken,
ohne seinen Zorn zu entfachen, schuf Dahers Sohn, der weise Brahmane Sissa, ein
Spiel, in dem der König als wichtigste Figur ohne Hilfe anderer Figuren und Bauern
nichts ausrichten kann. Der Unterricht im Schachspiel machte auf Shihram einen starken Eindruck. Er wurde milder und ließ das Schachspiel verbreiten, damit alle davon
Kenntnis nähmen. Um sich für die anschauliche Lehre von Lebensweisheit und zugleich Unterhaltung zu bedanken, gewährte er dem Brahmanen einen freien Wunsch.
Dieser wünschte sich Weizenkörner: Auf das erste Feld eines Schachbretts wollte er
ein Korn, auf das zweite Feld die doppelte Menge, also zwei, auf das dritte wiederum
doppelt so viele, also vier und so weiter. Der König lachte und war gleichzeitig erbost
über die vermeintliche Bescheidenheit des Brahmanen.
Als sich Shihram einige Tage später erkundigte, ob Sissa seine Belohnung in Empfang genommen habe, musste er hören, dass die Rechenmeister die Menge der Weizenkörner noch nicht berechnet hätten. Der Vorsteher der Kornkammer meldete nach
mehreren Tagen ununterbrochener Arbeit, dass er diese Menge Getreidekörner im
ganzen Reich nicht aufbringen könne. Auf allen Feldern eines Schachbretts zusammen wären es 18.446.744.073.709.551.615 Weizenkörner. Nun stellte er sich die
Frage, wie das Versprechen eingelöst werden könne. Der Rechenmeister half dem
Herrscher aus der Verlegenheit, indem er ihm empfahl, er solle Sissa ibn Dahir ganz
einfach das Getreide Korn für Korn zählen lassen.2
Berechnen Sie mithilfe eines Python-Programms, ohne eine Formel zu Hilfe zu nehmen, wie viele Weizenkörner angehäuft werden müssen.
Lösung: 40.4 (3. Aufgabe), Seite 484
2
Die Weizenkornlegende wurde wörtlich von Wikipedia entnommen.
85
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
11
11.1
Dateien lesen
und schreiben
Dateien
Man findet wohl kaum jemanden im 21. Jahrhundert, der nicht wüsste, was eine Datei ist.
Auch wenn nicht jeder eine exakte Definition
geben könnte, also beispielsweise: Eine Datei
ist eine Menge von logisch zusammenhängenden und meist sequentiell geordneten Daten,
die auf einem Speichermedium dauerhaft gespeichert werden und mittels eines Bezeichners bzw. Namens wieder identifizierbar und
damit ansprechbar sind. Die Daten einer Datei existieren also über die Laufzeit eines Programms hinaus. Deswegen werden sie auch als
nicht-flüchtig oder persistent bezeichnet.
Auch wenn es so scheinen mag: Das Wort Datei
ist kein altes deutsches Wort. Es ist eine künstliche Schöpfung des Deutschen Instituts für Nor- Bild 11.1 Dateien
mung (DIN). Die beiden Wörter Daten und Kartei wurden zu dem neuen Wort Datei verschmolzen. Ein solches Kunstwort wird übrigens
als Portmanteau oder Kofferwort bezeichnet.
Der Inhalt jeder Datei besteht grundsätzlich aus einer eindimensionalen Aneinanderreihung von Bits, die normalerweise in Byte-Blöcken zusammengefasst interpretiert werden.
Die Bytes erhalten erst durch Anwendungsprogramme und das Betriebssystem eine Bedeutung. Sie entscheiden also, ob es sich um eine Textdatei, ein ausführbares Programm
oder beispielsweise ein Musikstück oder ein Bild handelt.
11.2
Text aus einer Datei lesen
Unser erstes Beispiel zeigt, wie man Daten aus einer Datei ausliest. Um dies tun zu können,
muss man zuerst die Datei zum Lesen öffnen. Dazu benötigt man die open()-Funktion. Mit
88
11 Dateien lesen und schreiben
der open-Funktion erzeugt man ein Dateiobjekt und liefert eine Referenz auf dieses Objekt
als Ergebniswert zurück. Die open-Funktion erwartet zwei Parameter: einen Dateinamen,
gegebenenfalls mit einem Pfad, und optional kann man den Modus angeben:
open(filename,mode)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im folgenden Beispiel öffnen wir die Datei „ad_lesbiam.txt” zum Lesen, da der Modus auf
„r” (read) gesetzt ist:
fobj = open("ad_lesbiam.txt", "r")
Alternativ kann man die obige Anweisung auch ohne die Angabe des „r” schreiben. Fehlt
der Modus, wird automatisch Lesen zum Default-Wert:
fobj = open("ad_lesbiam.txt")
Sehr häufig bearbeitet man eine Datei zeilenweise, d.h. man liest eine Datei Zeile für Zeile.
Das folgende Programmstück demonstriert, wie man eine Datei zeilenweise einliest. Jede
Zeile wird dann mit print ausgegeben. Die String-Methode rstrip() entfernt vor der Ausgabe
etwaige Leerzeichen und Newlines vom rechten Rand:
fobj = open("ad_lesbiam.txt")
for line in fobj:
print(line.rstrip())
fobj.close()
Führt man obiges Programm aus, erhält man folgende Ausgabe1 :
VIVAMUS mea Lesbia, atque amemus,
rumoresque senum severiorum
omnes unius aestimemus assis!
soles occidere et redire possunt:
nobis cum semel occidit brevis lux,
nox est perpetua una dormienda.
da mi basia mille, deinde centum,
dein mille altera, dein secunda centum,
deinde usque altera mille, deinde centum.
dein, cum milia multa fecerimus,
conturbabimus illa, ne sciamus,
aut ne quis malus invidere possit,
cum tantum sciat esse basiorum.
1
Es handelt sich um ein Gedicht des Dichters Catullus. Deutsche Übersetzung: „Lass uns leben, meine liebe
Lesbia, und lieben und lass uns alle Gerüchte der allzu strengen Alten nicht höher als einen Cent achten!
Sonnen können untergehen und wieder aufgehen: wenn uns einmal das kurze Licht ausgeht, müssen wir
eine ewige Nacht schlafen. Gib mir tausend Küsse, darauf hundert, darauf ein anderes Tausend und ein
zweites Hundert, als nächstes ein weiteres Tausend und dann hundert. Dann, wenn wir viele Tausende
gemacht haben, werden wir jene verwirren, damit wir es nicht wissen, noch irgendein Schlechter auf uns
neidisch sein kann, wenn er weiß, wie viele Küsse es gewesen sind.”
11.3 Schreiben in eine Datei
11.3
Schreiben in eine Datei
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Auch das Schreiben in eine Datei lässt sich in Python einfach bewerkstelligen. Zum Öffnen
der Datei benutzt man „w” statt „r” als Modus. Daten schreibt man in eine Datei mit der
Methode write des Dateiobjekts.
Im folgenden Programm versehen wir die vorige Datei mit Zeilennummern, d.h. wir schreiben eine neue Datei, die alle Zeilen der alten Datei plus eine Zeilennummer vor jeder Zeile
enthält.
fobj_in = open("ad_lesbiam.txt")
fobj_out = open("ad_lesbiam2.txt", "w")
counter = 0
for line in fobj_in:
counter += 1
out_line = "{0:>3s} {1:s}\n".format(str(counter),line.rstrip())
fobj_out.write(out_line)
fobj_in.close()
fobj_out.close()
Die Datei „ad_lesbiam2.txt” enthält nach Ausführung des obigen Programms folgenden
Text:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
VIVAMUS mea Lesbia, atque amemus,
rumoresque senum severiorum
omnes unius aestimemus assis!
soles occidere et redire possunt:
nobis cum semel occidit brevis lux,
nox est perpetua una dormienda.
da mi basia mille, deinde centum,
dein mille altera, dein secunda centum,
deinde usque altera mille, deinde centum.
dein, cum milia multa fecerimus,
conturbabimus illa, ne sciamus,
aut ne quis malus invidere possit,
cum tantum sciat esse basiorum.
11.4
Die Methoden read() und readlines()
Bis jetzt haben wir Dateien Zeile für Zeile mit Schleifen verarbeitet. Aber es kommt öfters
vor, dass man eine Datei gerne in eine komplette Datenstruktur einlesen will, z.B. einen
String oder eine Liste. Auf diese Art kann die Datei schnell wieder geschlossen werden, und
man arbeitet anschließend nur noch auf der Datenstruktur weiter:
89
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
90
11 Dateien lesen und schreiben
>>> poem = open("ad_lesbiam.txt").readlines()
>>> print(poem)
['VIVAMUS mea Lesbia, atque amemus,\n', 'rumoresque senum severiorum\n
', 'omnes unius aestimemus assis!\n', 'soles occidere et redire
possunt:\n', 'nobis cum semel occidit brevis lux,\n', 'nox est
perpetua una dormienda.\n', 'da mi basia mille, deinde centum,\n',
'dein mille altera, dein secunda centum,\n', 'deinde usque altera
mille, deinde centum.\n', 'dein, cum milia multa fecerimus,\n', '
conturbabimus illa, ne sciamus,\n', 'aut ne quis malus invidere
possit,\n', 'cum tantum sciat esse basiorum. \n', '\n']
>>> print(poem[2])
omnes unius aestimemus assis!
Im obigen Beispiel wurde das ganze Gedicht in eine Liste namens poem geladen. Wir können nun beispielsweise die dritte Zeile mit poem[2] ansprechen.
Eine andere angenehme Methode, eine Datei einzulesen, bietet die Methode read() von
open. Mit dieser Methode kann man eine ganze Datei in einen String einlesen, wie wir im
folgenden Beispiel zeigen:
>>> poem = open("ad_lesbiam.txt").read()
>>> print(poem[20:34])
atque amemus,
11.5
with-Anweisung
Wie wir in den vorigen Abschnitten Dateien gelesen und geschrieben haben, sollte man es
eigentlich nicht tun, obwohl es so funktioniert. Man sollte unbedingt die with-Anweisung
benutzen. with ist ein allgemeines Konstrukt, das auch in anderen Zusammenhängen in
Python benutzt wird und welches man auch für eigene Zwecke definieren kann.
Benutzt man es mit einem open, so wird die Datei automatisch nach Ablauf des Blocks oder
bei Auftreten einer Ausnahme geschlossen. Damit ist sichergestellt, dass die Datei immer
geschlossen wird! Ein explizites close ist also nicht mehr nötig!
Unsere Beispiele aus den vorigen Abschnitten haben wir im Folgenden mit with neu geschrieben:
with open("ad_lesbiam.txt") as fobj:
for line in fobj:
print(line.rstrip())
11.6 Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im folgenden Beispiel öffnen wir mit with eine Datei zum Lesen und eine zum Schreiben:
with open("ad_lesbiam.txt") as fobj_in:
with open("ad_lesbiam2.txt", "w") as fobj_out:
counter = 0
for line in fobj_in:
counter += 1
out = "{0:>3s} {1:s}\n".format(str(counter),line)
fobj_out.write(out.rstrip())
Im vorigen Beispiel ist es unschön, dass wir nach jedem with einrücken müssen. Dadurch
kommen wir auf acht Leerzeichen Einrückungstiefe. Wir können auch mit einem with
gleichzeitig mehrere Dateien öffnen. Diese werden dann mit Kommata getrennt. Allerdings
müssen dann alle in einer Zeile stehen. Allerdings kann man auch den Rückwärtsschrägstrich benutzen, um die Zeile in einer weiteren Zeile fortzusetzen. Man muss allerdings
darauf achten, dass der Rückwärtsschrägstrich das letzte Zeichen der Zeile ist:
with open("ad_lesbiam.txt") as fobj_in,\
open("ad_lesbiam2.txt", "w") as fobj_out:
counter = 0
for line in fobj_in:
counter += 1
out_line = "{0:>3s} {1:s}\n".format(str(counter),line.rstrip()
)
fobj_out.write(out_line)
11.6
Aufgaben
1. Aufgabe:
Gegeben sei eine Datei, in der jeweils abwechselnd ein Vorname und ein Nachname
in einer Zeile steht, also beispielsweise so:
Fred
Miller
Eve
Turner
Steve
Baker
Diese Datei soll in eine andere Datei überführt werden, in der pro Zeile jeweils ein
Vorname und ein Nachname steht.
Lösung: 40.5 (1. Aufgabe), Seite 485
91
92
11 Dateien lesen und schreiben
2. Aufgabe:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Schreiben Sie ein Programm, das jeweils drei Zeilen aus der folgenden Datei zu jeweils einer zusammenfasst und diese als Standardausgabe mittels print() ausgibt.
D.h. eine Ausgabezeile soll jeweils den Vornamen, den Nachnamen und den Künstlernamen bzw. die Band des Künstlers enthalten. Der Band- bzw. Künstlername soll in
Klammern ausgegeben werden. Also z.B. „Marshall Bruce Mathers III (Eminem)”:
Brian
Molko
Placebo
Jim
Morrison
The Doors
Ray
Davies
The Kinks
Marshall Bruce
Mathers III
Eminem
Andre Romelle
Young
Dr. Dre
Beth
Gibbons
Portishead
Lösung: 40.5 (2. Aufgabe), Seite 486
3. Aufgabe:
Ändern Sie das vorige Programm so, dass es nun die Ausgabe in eine Ausgabedatei
schreibt.
Lösung: 40.5 (3. Aufgabe), Seite 486
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
12
12.1
Formatierte Ausgabe
und Strings formatieren
Wege zur Formatierung der Ausgabe
Wenn man programmieren lernt, steht im Anfang zunächst einmal die Programmierlogik
im Vordergrund. Man möchte schnell lernen,
wie Variablen, Datenstrukturen, Verzweigungen,
Schleifen usw. funktionieren. Die „schöne” Ausgabe der Ergebnisse ist erst einmal sekundär.
Man freut sich, wenn das Programm überhaupt
etwas ausgibt, und vor allen Dingen, wenn es
dann auch noch richtig ist. Ob die Ergebnisse
ansprechend formatiert sind, ist in dieser Phase von untergeordneter Bedeutung. Aber irgendwann rückt dann auch der Wunsch nach einer
hübschen Ausgabe der Ergebnisse in den Fokus
der Lernenden.
„Irgendwann” könnte jetzt wohl bei allen erreicht
sein. Deshalb werden wir uns nun intensiv den
verschiedenen Methoden zuwenden, mit denen
man formatierte Strings und formatierte AusgaBild 12.1 Buchdruck 1568
ben erzeugen kann. Python bietet, was eigentlich Python-untypisch ist, verschiedene Wege zur
Formatierung, die wir im Folgenden alle vorstellen. Wir empfehlen aber, die formatMethode der Stringklasse zu benutzen, die wir kurz vor Ende dieses Kapitels besprechen.
Die format-Methode ist die bei Weitem flexibelste. Bevor wir auf die Stringformatierung
eingehen, wollen wir uns die print-Funktion im Detail anschauen.
12.2
print-Funktion
Das Aufrufverhalten der print-Funktion ist wie folgt definiert:
print(value1, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
94
12 Formatierte Ausgabe und Strings formatieren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die print-Funktion druckt beliebig viele Werte (value1, value2, ...) aus, die durch
Komma getrennt sind. Bei der Ausgabe werden die Werte standardmäßig durch Leerzeichen getrennt. Im folgenden Beispiel sehen wir zwei print-Aufrufe, die jeweils zwei
Werte, d.h. einen String und eine Float-Zahl, ausgeben:
>>> print("a = ", a)
a = 3.564
>>> print("a = \n", a)
a =
3.564
Wir sehen im zweiten print des vorigen Beispiels, dass das Leerzeichen zwischen zwei Werten, also in unserem Fall die Werte „a = \n” und „3.564”, immer durch ein Leerzeichen
getrennt werden, auch wenn die Ausgabe in einer neuen Zeile weitergeht.
Mit dem Schlüsselwortparameter sep kann man den Separator, der zwischen den Werten
ausgegeben wird, auf einen beliebigen Stringwert setzen, also auch zum Beispiel auf einen
leeren String oder einen Smiley:
>>> print("a",
a b
>>> print("a",
ab
>>> print(192,
192.168.178.42
>>> print("a",
a:-)b
"b")
"b", sep="")
168, 178, 42, sep=".")
"b", sep=":-)")
Nach der Ausgabe der Werte beendet die print-Funktion die Ausgabe mit einem Newline,
wie wir im Folgenden sehen:
>>> for i in range(3):
...
print(i)
...
0
1
2
Man kann dem Schlüsselwortparameter end einen beliebigen String zuweisen, der dann
statt dem Default-Wert \n verwendet wird. Das erlaubt uns z.B., mehrere Werte in einer
Zeile auszugeben:
>>> for
...
...
0 1 2 3
>>>
>>> for
...
...
0 :-) 1
i in range(4):
print(i, end=" ")
i in range(4):
print(i, end=" :-) ")
:-) 2 :-) 3 :-)
12.3 Stringformatierung im C-Stil
Die Ausgabe der print-Funktion wird in einen Datenstrom1 geleitet. Standardmäßig wird
dazu die Standardausgabe benutzt, also sys.stdout. Mit dem Schlüsselwort file sind wir
schließlich in der Lage, die Ausgabe in eine Datei umzuleiten.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> fh = open("daten.txt","w")
>>> print("42 ist die Antwort, aber was ist die Frage?", file=fh)
>>> fh.close()
Wir können feststellen, dass print nun keine Ausgabe mehr in der interaktiven Shell erzeugt. Die Ausgabe erfolgt jetzt in die Datei „daten.txt”. Auf diese Art ist es nicht nur möglich, Ausgaben in eine beliebige Datei umzulenken, sondern auch in den Standardfehlerkanal:
>>> import sys
>>> # Ausgabe in Standardfehlerkanal:
...
>>> print("Error: 42", file=sys.stderr)
Error: 42
12.3
Stringformatierung im C-Stil
Wir zeigen im Folgenden eine der Programmiersprache C und den C-Funktionen printf
und sprintf angelehnte Vorgehensweise, um formatierte Strings zu erzeugen.2
Gibt es ein printf in Python? Das ist die brennende Frage für Python-Anfänger, die von C,
Perl, Bash oder anderen Programmiersprachen kommen, die dieses Statement bzw. diese
Funktion benutzen. Zu sagen, dass Python eine print-Funktion und keine printf-Funktion
hat, ist nur die eine Seite der Medaille. Also gibt es doch eine „printf”-Funktion in Python?
Nein, aber die Funktionalität des „alten” printf wurde in anderer Form übernommen. Zu
diesem Zweck wird der Modulo-Operator „%” von der String-Klasse überladen. Deshalb
spricht man bei dem überladenen Operator „%” der String-Klasse auch häufig vom StringModulo-Operator, obwohl es eigentlich wenig mit der eigentlichen Modulo-Arithmetik zu
tun hat. Manchmal wird er auch synonym als String-Modulus-Operator bezeichnet. Ein
weiterer Begriff für diese Operation lautet „String-Interpolation”, weil bei dieser Operation
Werte wie Integers, Floats usw. in den zu erzeugenden formatierten String eingefügt, also
interpoliert werden. Den anhand der String-Interpolation erzeugten formatierten String
kann man dann z.B. mittels print (also nicht printf ) ausgeben. Aber man kann diesen formatierten String auch auf andere Art im Programm weiter verwenden.
Das folgende Diagramm verbildlicht die Arbeitsweise des String-Modulo-Operators:
Auf der linken Seite des String-Modulo-Operators befindet sich der sogenannte FormatString, und auf der rechten Seite steht ein Tupel mit den Werten, die in den Format-String
interpoliert bzw. eingefügt werden sollen. Die Werte können Literale, Variablen oder beliebige arithmetische Ausdrücke sein.
1
2
engl. stream
Dieses Vorgehen gilt als veraltet und als schlechter Python-Stil.
95
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
96
12 Formatierte Ausgabe und Strings formatieren
Der Formatstring enthält Platzhalter. In unserem Beispiel befinden sich zwei davon: „%5d”
und „%8.2f”.
Die allgemeine Syntax für diese Platzhalter lautet:
%[flags][width][.precision]type
Schauen wir uns einmal die Platzhalter unseres Beispiels
genauer an: Der zweite, also „%8.2f”, ist eine Formatbeschreibung für eine Float-Zahl. Wie auch alle anderen Platzhalter wird er durch ein „%”-Zeichen eingeleitet. Dieses wird von einer Zahl gefolgt, die die totale Anzahl der Zeichen angibt, mit der die Zahl ausgegeben
werden soll. Diese Anzahl beinhaltet auch den Dezimalpunkt und alle Ziffern vor und nach dem Punkt. Unsere Float-Zahl 59.058 muss also mit 8 Zeichen dargestellt
werden. Der Dezimalanteil der Zahl ist auf 2 gesetzt, dies
ist die Zahl hinter dem „.” beim Platzhalter. Das bedeutet, dass die Anzahl der Nachkommastellen auf 2 gesetzt
ist. Das letzte Zeichen im Platzhalter ist der Buchstabe
„f”. Wie sich leicht vermuten lässt, steht er für „float”.
Bild 12.2 float-Platzhalter
Schaut man sich die Ausgabe an, erkennt man, dass die Zahl „59.058” als „ 59.06” ausgegeben wird. Man sieht auch, dass die Ausgabe nicht etwa nach zwei Stellen abgebrochen
wird, sondern dass eine Rundung erfolgt. Außerdem werden bei der Ausgabe von links drei
Leerzeichen an die Zahl angefügt.
Der erste Platzhalter „%5d” dient als Beschreibung für die erste Komponente unseres Tupels, d.h. die Ganzzahl (Integer) 453. Diese Zahl soll mit 5 Zeichen ausgedruckt werden.
Da 453 aber nur aus 3 Stellen besteht, werden der Zahl in der Ausgabe zwei Leerzeichen
vorangestellt. Viele werden statt „%5d” einen Platzhalter der Art „%5i” erwartet haben, da
es sich ja um eine Integer-Zahl handelt. Worin besteht der Unterschied? Ganz einfach: Es
gibt keinen Unterschied zwischen „d” und „i”. Beide werden zur Formatierung von Integers
genutzt.
12.3 Stringformatierung im C-Stil
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Vorteile bzw. die Schönheit der formatierten Ausgabe kann man nur ermessen, wenn
man mehrere gleichermaßen formatierte Ausgaben über mehrere Zeilen ausgibt. Im folgenden Beispiel zeigen wir, wie es aussieht, wenn wir fünf Fließkommazahlen (floats)
gleich formatiert mit dem Platzhalter „%6.2f” in verschiedenen Zeilen ausgeben:
Platzhaltersymbol
Bedeutung
d
vorzeichenbehaftete Ganzzahl (Integer, dezimal)
i
vorzeichenbehaftete Ganzzahl (Integer, dezimal)
o
vorzeichenlose Ganzzahlen im Oktalformat
u
obsolete (veraltet), ansonsten wie d, d.h. eine vorzeichenbehaftete Ganzzahl
x
vorzeichenlose Ganzzahlen (hexadezimal)
X
vorzeichenlose Ganzzahlen (hexadezimal), Uppercase
e
Fließkommazahl in wissenschaftlicher Notation, d.h. im Exponentialformat.
E
wie e, aber Großbuchstaben für Exponenten „e”).
f
Fixed point. Displays the number as a fixed-point number.
F
wie f, aber nan wird als NAN dargestellt
g
g entspricht entweder e oder f. Dies wird automatisch in Abhängigkeit zur
Größe des Wertes und der gegebenen Präzision entschieden.
G
G ist analog zu g, aber es entspricht entweder E oder f.
c
ein Zeichen
s
Eine Zeichenkette (String); beliebige Python-Objekte werden in einen
String mittels der Methode str() gewandelt.
%
Es findet keine Argument-Konvertierung statt, es wird ein „%”-Zeichen
ausgegeben.
Die folgenden Beispiele zeigen einige exemplarische Anwendungen der Konvertierungsmöglichkeiten der vorigen Tabelle:
>>> "%10.3e"% (356.08977)
' 3.561e+02'
>>> "%10.3E"% (356.08977)
' 3.561E+02'
>>> "%10.3f"% (356.08977)
'
356.090'
97
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
98
12 Formatierte Ausgabe und Strings formatieren
>>> x = float('nan')
>>> "%5.2F" % (x,)
' NAN'
>>> "%5.2f" % (x,)
' nan'
>>>
>>> print("%10o"% (25))
31
>>> print("%10.3o"% (25))
031
>>> print("%10.5o"% (25))
00031
>>> "%c" % (97,)
'a'
>>> "%c" % (98,)
'b'
>>> print("%5x"% (47))
2f
>>> print("%5.4x"% (47))
002f
>>> print("%5.4X"% (47))
002F
>>> print("Ein Prozentzeichen: %% " % ())
Ein Prozentzeichen: %
>>>
Flag
Bedeutung
#
Wird dieses Zeichen mit o, x oder X benutzt, wird der jeweilige Wert mit dem entsprechenden folgenden Präfix 0, 0o, 0O, 0x oder 0X versehen.
0
Das Ergebnis der Umwandlung wird mit Nullen aufgefüllt.
-
Das Ergebnis der Umwandlung wird linksbündig ausgegeben.
Falls kein Vorzeichen (beispielsweise ein Minuszeichen) ausgegeben wird, wird ein Leerzeichen vor den Wert gesetzt.
+
Das Ergebnis der Umwandlung wird mit einem Vorzeichen versehen („+” oder „-”). Dieses
Flag überschreibt ein „space”-Flag.
Beispiele:
>>> print("%#5X"% (47))
0X2F
>>> print("%5X"% (47))
2F
>>> print("%#5.4X"% (47))
0X002F
>>> print("%#5o"% (25))
0o31
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
12.4 Der pythonische Weg: Die String-Methode „format”
>>>
+42
>>>
42
>>>
+42
>>>
42
>>>
42
print("%+d"% (42))
print("% d"% (42))
print("%+2d"% (42))
print("% 2d"% (42))
print("%2d"% (42))
Obwohl es so ausschauen mag, ist die Formatierung nicht Teil der Print-Funktion. Schaut
man sich die vorigen Beispiele genauer an, sieht man, dass wir einen formatierten String
an die Print-Funktion übergeben haben. Oder anders ausgedrückt: Wenn die StringInterpolation auf einen String angewendet wird, liefert sie einen String zurück. Dieser
String wird dann an print übergeben. Dies bedeutet wiederum, dass wir die String-Interpolation auch hätten „zweistufig” anwenden können, d.h. wir hätten erst einen formatierten String erzeugt, diesen einer Variablen zugewiesen und diese Variable dann an print
übergeben:
>>> s = "Preis: $ %8.2f"% (356.08977)
>>> print(s)
Preis: $
356.09
>>>
12.4
Der pythonische Weg:
Die String-Methode „format”
Die String-Methode „format” wurde mit Python 2.6 als Ersatz für die String-Interpolation
eingeführt. Ganz allgemein sieht die Syntax von „format” wie folgt aus:
template.format(p0, p1, ..., k0=v0, k1=v1, ...)
Der Format-String ist ein String, der ein oder mehrere Format-Codes innerhalb eines konstanten Texts enthält, d.h. Felder, die ersetzt werden sollen. Die zu „ersetzenden Felder”
sind von geschweiften Klammern umgeben, also „{” und „}”. Eine geschweifte Klammer
und der von ihr umschlossene „Code” wird mit dem formatierten Wert aus dem korrespondierenden Argument ersetzt. Nach welchen Regeln dies erfolgt, werden wir im Folgenden
erläutern. Alles andere, was nicht von geschweiften Klammern umschlossen ist, wird wörtlich, also ohne Änderungen, ausgedruckt. Möchte man eine öffnende oder eine schließende geschweifte Klammer ausgeben, muss man diese verdoppeln, also „{{” oder „}}”.
Es gibt zwei Arten von Argumenten für die format-Methode. Die Argumentenliste startet
mit 0 oder mehr Positionsargumenten (p0, p1, ...), die von 0 oder mehr Schlüsselwortargumenten der Form name=value gefolgt werden können.
99
100
12 Formatierte Ausgabe und Strings formatieren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Ein Positionsparameter der format-Methode kann benutzt werden, indem man den Index
des Parameters nach der öffnenden geschweiften Klammer angibt, d.h. {0} für den ersten
Parameter, {1} für den zweiten und so weiter. Dem Index des Positionsparameters nach der
öffnenden geschweiften Klammer kann ein Doppelpunkt und einem Formatstring folgen,
der dem des String-Interpolationsparameters ähnelt, den wir zu Beginn des Kapitels besprochen hatten, also zum Beispiel {0:5d}.
Falls die Positionsparameter in der Reihenfolge benutzt werden, in der sie in der Parameterliste stehen, kann die Angabe des Indexes innerhalb der geschweiften Klammern entfallen, d.h. ’{} {} {}’ entspricht ’{0} {1} {2}’. Um es nochmals klarzustellen: Will man sie in einer
anderen Reihenfolge benutzen, sind die Angaben der Indexpositionen dringend notwendig, wie z.B. hier ’{2} {1} {0}’.
Im folgenden Diagramm zeigen wir anhand eines Beispiels, wie die Stringmethode „format” für zwei Parameter funktioniert:
Beispiele mit Positionsparametern:
>>> "Erstes Argument: {0}, zweites: {1}".format(47,11)
'Erstes Argument: 47, zweites: 11'
>>> "Zweites Argument: {1}, erstes: {0}".format(47,11)
'Zweites Argument: 11, erstes: 47'
>>> "Zweites Argument: {1:3d}, erstes: {0:7.2f}".format(47.42,11)
'Zweites Argument: 11, erstes:
47.42'
>>> "Erstes Argument: {}, zweites: {}".format(47,11)
'Erstes Argument: 47, zweites: 11'
>>> # Argumente können auch mehrmals verwendet werden:
...
>>> "precisions: {0:6.2f} or {0:6.3f}".format(1.4148)
'precions:
1.41 or 1.415'
Im folgenden Beispiel demonstrieren wir, wie Schlüsselwortparameter mit der formatMethode benutzt werden können:
>>> "Artikel: {a:5d}, Preis: {p:8.2f}".format(a=453, p=59.058)
'Artikel:
453, Preis:
59.06'
Mit der format-Methode ist es auch möglich, Daten links- und rechtsbündig auszugeben.
Dazu müssen wir der Formatierungsanweisung ein „<” (linksbündig) oder ein „>” (rechtsbündig) voranstellen. Wir demonstrieren dies an den folgenden Beispielausgaben:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
12.4 Der pythonische Weg: Die String-Methode „format”
>>> "{0:<20s} {1:6.2f}".format('Spam & Eggs:', 6.99)
'Spam & Eggs:
6.99'
>>> "{0:>20s} {1:6.2f}".format('Spam & Eggs:', 6.99)
'
Spam & Eggs:
6.99'
>>> "{0:>20s} {1:6.2f}".format('Spam & Ham:', 7.99)
'
Spam & Ham:
7.99'
>>> "{0:<20s} {1:6.2f}".format('Spam & Ham:', 7.99)
'Spam & Ham:
7.99'
>>> "{0:<20} {1:6.2f}".format('Spam & Ham:', 7.99)
'Spam & Ham:
7.99'
>>> "{0:>20} {1:6.2f}".format('Spam & Ham:', 7.99)
'
Spam & Ham:
7.99'
>>>
Ausrichtungsoption
Bedeutung
’<’
Das Feld wird linksbündig innerhalb des vorhandenen Raums ausgegeben. Strings werden standardmäßig linksbündig ausgegeben.
’>’
Das Feld wird rechtsbündig innerhalb des vorhandenen Raums ausgegeben. Numerische Werte werden standardmäßig rechtsbündig ausgegeben.
’=’
Die Auffüllzeichen (padding characters) werden zwischen Vorzeichen,
falls ein Vorzeichen ausgegeben wird, und dem eigentlichen Beginn der
Ziffern einer Zahl eingeführt. Damit können Felder der Art „+000000120”
ausgegeben werden. Diese Ausrichtungsoption kann nur auf numerische Werte angewendet werden.
’^’
Ein Feld wird zentriert innerhalb des zur Verfügung stehenden Raums
ausgegeben.
Wenn keine minimale Feldlänge angegeben wird, entspricht die Feldlänge immer der Länge der Daten, die ein Feld enthält. In diesen Fällen haben die Ausrichtungsoptionen keine
Bedeutung.
Zusätzlich können wir noch die Ausgabe mittels der sign-Option beeinflussen. Diese Optionen sind nur für numerische Werte gültig:
101
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
102
12 Formatierte Ausgabe und Strings formatieren
sign-Option
Bedeutung
’+’
Es soll immer ein Vorzeichen ausgegeben werden, also unabhängig davon, ob
es sich um eine positive oder negative Zahl handelt.
’-’
Ein Vorzeichen soll nur bei negativen Zahlen verwendet werden.
space
Statt eines „+”-Zeichens wird bei positiven Zahlen ein Leerzeichen „ ” vorangestellt. Bei negativen Zahlen ein Minus-Zeichen „-”.
12.5
Benutzung von Dictionaries
beim Aufruf der „format”-Methode
In den vorigen Kapiteln haben wir gesehen, dass wir zwei Möglichkeiten haben, Werte mit
„format” zu formatieren:
■
Benutzung des Index bzw. der Position des Arguments:
>>> print("Die Hauptstadt von {0:s} ist {1:s}".format("Ontario","
Toronto"))
Die Hauptstadt von Ontario ist Toronto
Um es nochmals zu erwähnen: Im vorigen Beispiel hätten wir auch geschweifte Klammern mit leerem Inhalt benutzen können!
■
Benutzung von Schlüsselwortparametern:
>>> print("Die Hauptstadt von {province} ist {capital}".format(
province="Ontario",capital="Toronto"))
Die Hauptstadt von Ontario ist Toronto
Im zweiten Fall könnten wir auch ein Dictionary verwenden, wie wir im folgenden Code
sehen können:
>>> print("Die Hauptstadt von {province} ist {capital}".format(**k))
Die Hauptstadt von Ontario ist Toronto
Schauen wir uns das folgende Python-Programm an:
capital_country = {"Vereinigte Staaten" : "Washington",
"US" : "Washington",
"Kanada" : "Ottawa",
"Deutschland": "Berlin",
"Frankreich" : "Paris",
"Großbritannien" : "London",
"Schweiz" : "Bern",
"Österreich" : "Wien",
"Niederlande" : "Amsterdam"}
print("Länder und ihre Hauptstädte:")
for c in capital_country:
print("{country}: {capital}".format(country=c, capital=
capital_country[c]))
12.6 Benutzung von lokalen Variablen in „format”
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wenn wir dieses Programm starten, erhalten wir die folgende Ausgabe:
$ python3 country_capitals.py
Länder und ihre Hauptstädte:
Schweiz: Bern
Großbritannien: London
US: Washington
Frankreich: Paris
Kanada: Ottawa
Niederlande: Amsterdam
Deutschland: Berlin
Österreich: Wien
Vereinigte Staaten: Washington
Das vorige Programm können wir so umschreiben, dass im Aufruf der format-Methode
das Dictionary direkt verwendet wird. Dazu verwenden wir jedoch einen Mechanismus
(**), den wir erst in Kapitel Doppeltes Sternchen im Funktionsaufruf, Seite 129 behandeln
werden:
capital_country = {"Vereinigte Staaten" : "Washington",
"US" : "Washington",
"Kanada" : "Ottawa",
"Deutschland": "Berlin",
"Frankreich" : "Paris",
"Großbritannien" : "London",
"Schweiz" : "Bern",
"Österreich" : "Wien",
"Niederlande" : "Amsterdam"}
print("Countries and their capitals:")
for c in capital_country:
format_string = c + ": {" + c + "}"
print(format_string.format(**capital_country))
Der Ausdruck ist gleich wie beim ersten Programm.
12.6
Benutzung von lokalen Variablen
in „format”
„locals” ist eine Funktion, die ein Dictionary mit den lokal definierten Namen und ihren
aktuellen Werten zurückliefert. Die Variablen sind die Schlüssel dieses Dictionarys, und
die Werte zu den Schlüsseln entsprechen den Werten der Variablen:
>>> a = 42
>>> b = 47
>>> def f(): return 42
103
104
12 Formatierte Ausgabe und Strings formatieren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
...
>>> locals()
{'a': 42, 'b': 47, 'f': <function f at 0xb718ca6c>, '__builtins__': <
module 'builtins' (built-in)>, '__package__': None, '__name__': '
__main__', '__doc__': None}
Das Dictionary, was von locals() zurückgeliefert wird, kann als Parameter für die formatMethode genutzt werden. Dadurch ist es möglich, im Format-String lokale Variablen zu
verwenden.
Im folgenden Beispiel benutzen wir die lokalen Variablen a, b und f:
>>> print("a={a}, b={b} and f={f}".format(**locals()))
a=42, b=47 and f=<function f at 0xb718ca6c>
12.7
Formatierte Stringliterale
Formatierte Stringliterale, auch als f-Strings bekannt, sind Strings, denen ein „f” vorangestellt ist. Sie sind erst seit Python3.6 verfügbar! Sie sind ähnlich wie die Strings, die von
str.format() akzeptiert werden. Sie enthalten Ersetzungsfelder, die von geschweiften
Klammern umgeben sind. Diese Ersetzungsfelder sind Ausdrücke, die zur Laufzeit ausgewertet und nach den format-Regeln formatiert werden:
>>> article = "Regal"
>>> height = 176.4
>>> f"Artikel: {article}, Höhe: {height:6.2f}"
'Artikel: Regal, Höhe: 176.40'
>>> width, precision = 10, 3
>>> x = 529.9637
>>> f"Value: {x:{width}.{precision}}"
'Value:
5.3e+02'
12.8
Weitere String-Methoden
zum Formatieren
Die String-Klasse enthält weitere Methoden, die man auch zu Formatierungszwecken nutzen kann: ljust, rjust, center und zfill
Falls S ein String ist, können wir die vier Methoden wie folgt erklären:
■
center(...):
S.center(width[, fillchar]) -> str
12.8 Weitere String-Methoden zum Formatieren
An den String S werden links und rechts Füllzeichen bis zur Länge „width” so angefügt,
dass der ursprüngliche String zentriert wird. Wird für das Füllzeichen „fillchar” kein Wert
angegeben, wird als Standardwert ein Leerzeichen verwendet.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiele:
>>> s = "Python"
>>> s.center(10)
' Python '
>>> s.center(10,"*")
'**Python**'
■
ljust(...):
S.ljust(width[, fillchar]) -> str
Der String S wird linksbündig zurückgeliefert. Rechts wird der String mit Füllzeichen
„fillchar” bis zur Länge „width” angefüllt. Auch hier ist ein Leerzeichen der Default-Wert
für „fillchar”.
Beispiele:
>>> s = "Training"
>>> s.ljust(12)
'Training
'
>>> s.ljust(12,":")
'Training::::'
■
rjust(...):
S.rjust(width[, fillchar]) -> str
Der String S wird rechtsbündig zurückgeliefert. Von links wird der String mit Füllzeichen
„fillchar” bis zur Länge „width” angefüllt. Auch hier ist ein Leerzeichen der Default-Wert
für „fillchar”.
Beispiele:
>>> s = "Programming"
>>> s.rjust(15)
'
Programming'
>>> s.rjust(15, "~")
'~~~~Programming'
■
zfill(...):
S.zfill(width) -> str
Ein String S wird von links mit Nullen bis zu der Länge „width” aufgefüllt. Der String S
wird nicht abgeschnitten, falls er bereits länger als die vorgesehene Länge „width” ist.
Diese Methode entspricht einem
S.rjust(width, "0")
105
106
12 Formatierte Ausgabe und Strings formatieren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiele:
>>> account_number = "43447879"
>>> account_number.zfill(12)
'000043447879'
>>> # can be emulated with rjust:
...
>>> account_number.rjust(12,"0")
'000043447879'
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
13
13.1
Flaches und tiefes
Kopieren
Einführung
In diesem Kapitel geht es nun um das Kopieren
von Listen und vor allem um das Kopieren von verschachtelten Listen. Dabei kann es vor allem bei
Anfängern, falls sie die genauen Zusammenhänge
nicht kennen, zu verblüffenden und verwirrenden
Erfahrungen kommen, die meist mit Fehlern im Programm einhergehen.
Im Folgenden werden wir die Identitätsfunktion id
benutzen, die ein Objekt als Parameter hat. id(obj)
liefert die „Identität” des Objekts obj. Diese Identität,
der Rückgabewert der Funktion, ist ein Integer-Wert,
der eindeutig und konstant für dieses Objekt ist, solange es im Programmablauf existiert.
Betrachten wir folgenden Code:
>>> x = 3
>>> y = x
Es stellt sich die Frage, was bei der Zuweisung y = x Bild 13.1 Mona-Lisa-Kopie
passiert? Bezeichnen x und y die gleiche Speicherzelle – oder besser das gleiche Objekt – oder wird eine eigene Speicherzelle, d.h. ein eigenes
Objekt, für y angelegt, in das die Zahl 3 kopiert wird?
Ersteres ist der Fall, d.h. x und y teilen sich das gleiche Objekt. Bildlich kann man sich dies
wie folgt veranschaulichen:
108
13 Flaches und tiefes Kopieren
Den Beweis, dass dies wirklich so ist, können wir mittels der id-Funktion führen. Wir zeigen, dass die Identität von x und y gleich sind, d.h. sie „zeigen” auf das gleiche Objekt bzw.
teilen sich das gleiche Objekt. Man sagt auch: x und y referenzieren das gleiche Objekt:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> x = 3
>>> y = x
>>> print(id(x), id(y))
137220832 137220832
Was passiert nun, wenn wir einer der beiden Variablen einen neuen Wert zuweisen, also
beispielsweise die Zahl 2 der Variablen y:
>>> y = 2
>>> print(id(x)==id(y), x, y)
False 3 2
Bildlich sieht die Situation nun also wie folgt aus:
y zeigt nun auf ein eigenes Integer-Objekt. Alle Variablen in Python sind Referenzen auf
Objekte. Trotzdem verhalten sich Zuweisungen der Art y = x (wenn x ein unveränderlicher
Datentyp wie int, float, str oder tuple ist) wie Kopien, da das „Sharing” (gemeinsame
Referenz) aufgelöst wird, sobald x oder y ein neues Objekt zugewiesen wird. Veränderliche
Objekte wie Listen und Dictionaries können wir jedoch nicht mittels einer einfachen Zuweisung kopieren. Auch in diesem Fall gibt es nur Zeiger auf das gleiche Objekt. Im Folgenden wollen wir dies und die daraus resultierenden Herausforderungen im Detail darstellen
und auf einige Probleme eingehen.
13.2
Kopieren einer Liste
Im folgenden Code-Beispiel legen wir als Erstes eine flache Liste mit dem Namen colours1
an. Mit „flach”1 meinen wir, dass die Liste nicht verschachtelt ist, also keine weiteren Unterlisten enthält. Anschließend weisen wir die Liste colours1 einer Variablen colours2 zu.
Mittels der print()-Funktion können wir uns überzeugen, dass beide Variablen den gleichen Inhalt haben, und mittels id() sehen wir, dass beide Variablen auf das gleiche Listenobjekt zeigen. Nun weisen wir colours2 eine neue Liste zu. Es erstaunt wenig, dass die
Werte von colours1 dabei unverändert bleiben, d.h. colours1 zeigt weiterhin auf sein altes Objekt, während colours2 ein neues Objekt erhalten hat. Wie im ersten Beispiel mit
den Integer-Variablen wird ein neuer Speicherbereich für colours2 angelegt, wenn dieser
Variablen eine komplett neue Liste – also ein neues Objekt – zugeordnet wird.
1
engl. shallow
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
13.2 Kopieren einer Liste
>>> colours1 = ["red", "blue"]
>>> colours2 = colours1
>>> print(colours1)
['red', 'blue']
>>> print(colours2)
['red', 'blue']
>>> print(id(colours1), id(colours2))
140233898425800 140233898425800
>>> colours2 = ["rot", "blau"]
>>> print(colours1)
['red', 'blue']
>>> print(colours2)
['rot', 'blau']
Im folgenden Beispiel gehen wir der
Frage nach, was passiert, wenn wir
einer Variablen nicht ein neues Objekt zuordnen, sondern nur ein einzelnes Element der Liste ändern. Um
dies zu testen, weisen wir dem zwei- Bild 13.2 Referenzen auf Listen
ten Element von colours2, also dem
Element mit dem Index 1, einen neuen Wert zu. Ohne das vorher Gesagte würde es viele
erstaunen, nun zu sehen, dass auch colours1 damit verändert wurde. Aber colours1 wurde ja nicht verändert, sondern nur das Objekt, auf das diese Variable ebenso wie colours2
zeigt.
>>> colours1 = ["red", "blue"]
>>> colours2 = colours1
>>> colours2[1] = "green"
>>> print(colours1)
['red', 'green']
>>> print(colours2)
['red', 'green']
Wie wir bereits im vorigen Beispiel
gesehen haben, wird bei der Zuweisung colours2 = colours1 keine
Kopie, sondern nur eine Referenz auf
die gleiche Liste erzeugt. Änderungen
innerhalb des Listenobjekts wirken
sich also auf beide Variablennamen –
colours1 und colours2 – aus, da sie
ja auf dasselbe Objekt zeigen.
Bild 13.3 Referenzen auf Listen
109
110
13 Flaches und tiefes Kopieren
13.3
Flache Kopien
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Mit der copy-Methode der list-Klasse können wir flache Kopien von Listen erzeugen2 :
>>> liste1 = ['a','b','c','d']
>>> liste2 = liste1.copy()
>>> liste2[1] = 'x'
>>> print(liste1)
['a', 'b', 'c', 'd']
>>>
Sobald jedoch auch Unterlisten
in der zu kopierenden Liste vorkommen, werden nur Zeiger auf
diese Unterlisten kopiert:
Dieses Verhalten beim Kopieren veranschaulicht das nebenstehende Bild. Weist man nun
zum Beispiel dem 0-ten Element
einer der beiden Listen einen
neuen Wert zu, führt dies nicht
zu einem Nebeneffekt. Probleme
gibt es erst, wenn man direkt eines der beiden Elemente der Unterliste verändert.
Bild 13.4 Kopieren von Listen
Um dies zu demonstrieren, ändern wir nun zwei Einträge in lst2:
>>> lst1 = ['a','b',['ab','ba']]
>>> lst2 = lst1.copy()
>>> lst2[0] = 'c'
>>> lst2[2][1] = 'd'
>>> print(lst1)
['a', 'b', ['ab', 'd']]
Man erkennt, dass man aber
nicht nur die Einträge in lst2 geändert hat, sondern auch den
Eintrag von lst1[2][1]. Dies liegt
daran, dass in beiden Listen, also lst1 und lst2, das jeweils dritte Element nur ein Link auf eine physikalisch gleiche Unterliste
ist. Diese Unterliste wurde nicht
mit lst1.copy() mitkopiert.
Bild 13.5 Kopieren von Listen
2
Dazu kann man auch den Teilbereichsoperator (slicing) verwenden: „liste2 = liste1[:]”
13.4 Kopieren mit deepcopy
13.4
Kopieren mit deepcopy
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Abhilfe für das eben beschriebene Problem schafft das Modul copy. Dieses Modul stellt
die Funktion deepcopy zur Verfügung, die das komplette oder besser: tiefe Kopieren einer
nicht flachen Listenstruktur erlaubt:
>>> from copy import deepcopy
>>> lst1 = ['a','b',['ab','ba']]
>>> lst2 = deepcopy(lst1)
Die Speichersituation nach dem obigen Code haben wir im folgenden Diagramm dargestellt:
Im folgenden Code zeigen wir, dass sich Zuweisungen nur auf die jeweilige Liste auswirken,
da nun auch die Unterliste kopiert worden ist:
>>> lst2[2][1] = "d"
>>> lst2[0] = "c";
>>> print(lst2)
['c', 'b', ['ab', 'd']]
>>> print(lst1)
['a', 'b', ['ab', 'ba']]
Die Speichersituation sehen wir im folgenden Diagramm:
111
112
13 Flaches und tiefes Kopieren
13.5
Deepcopy für Dictionaries
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
deepcopy aus dem Modul copy funktioniert für beliebige Python-Objekte, also beispielsweise auch für Dictionaries:
>>> from copy import deepcopy
>>> d = {"a":3, "b":4}
>>> copy_of_d = deepcopy(d)
>>> d["c"] = 1
>>> d
{'b': 4, 'a': 3, 'c': 1}
>>> copy_of_d
{'b': 4, 'a': 3}
Auch die Methode copy der Klasse dict erzeugt nur flache Kopien eines Dictionarys, wie
wir im Folgenden sehen können:
>>> p = {"Frank" : {"Gewicht":74.8, "Größe":183.4}}
>>> p = {"Frank" : {"Gewicht":74.8, "Größe":183.4},
...
"Sarah": {"Gewicht":57.3, "Größe": 168.9}}
>>> p2 = p.copy()
>>> p["Sarah"]["Gewicht"] -= 0.4
>>> p2
{'Sarah': {'Gewicht': 56.9, 'Größe': 168.9}, 'Frank': {'Gewicht':
74.8, 'Größe': 183.4}}
Wir sehen, dass wir nicht nur das Dictionary p, sondern auch das Dictionary p2 verändert
haben. Um dieses Problem zu beheben, erzeugen wir mit deepcopy eine tiefe Kopie. Wir
arbeiten weiter mit den Werten der obigen Shell. Das Gewicht von Sarah reduziert sich um
weitere 200 Gramm, allerdings wirkt sich dies nun nicht auf die tiefe Kopie p2 aus.
>>> from copy import deepcopy
>>> p2 = deepcopy(p)
>>> p["Sarah"]["Gewicht"] -= 0.2
>>> p2["Sarah"]["Gewicht"]
56.9
>>> p["Sarah"]["Gewicht"]
56.699999999999996
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
14
14.1
Funktionen
Allgemein
Das Konzept einer Funktion gehört wohl zu den
bedeutendsten der Mathematik. Auch in Programmiersprachen werden sie häufig eingesetzt, um mathematische Funktionen in Algorithmen umzusetzen. Eine solche Funktion berechnet ein oder mehrere Werte und ist vollständig von den übergebenen
Eingabewerten, den sogenannten Parametern, abhängig.
Ganz allgemein gesehen stellt eine Funktion ein
Strukturierungselement in einer Programmiersprache dar, um eine Menge von Anweisungen zu gruppieren, damit man sie mehrmals im Programm verwenden kann. Ohne Funktionen könnte man Code
nur kopieren, um ihn wiederzuverwenden. Spätere Bild 14.1 Funktionen
Änderungen müssten dann an allen kopierten Stellen durchgeführt werden, was natürlich sehr aufwendig und fehlerträchtig wäre. Die Benutzung von Funktionen erhöht außerdem die Verständlichkeit und Qualität eines Programms. Ebenso lassen sich durch ihre Verwendung
auch die Kosten für die Software-Entwicklung und die Wartung der Software senken.
14.2
Funktionen
In diesem Kapitel zeigen wir, wie man in Python eigene Funktionen schreibt. Funktionen
hatten wir bereits in den vorigen Kapiteln benutzt. So benutzten wir die Funktion len, um
die Anzahl der Zeichen eines Strings oder die Anzahl der Elemente einer Liste zu ermitteln.
Eine Funktion wird aufgerufen und liefert einen Wert – oder genauer gesagt: ein Objekt –
zurück. Diesen Wert kann man auch wieder in einem Ausdruck weiterverwenden, wie wir
es im nächsten Beispiel tun. Wir multiplizieren den Rückgabewert mit der Zahl 3:
114
14 Funktionen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> name = "Monty Python"
>>> len(name)
12
>>> len(name) * 3
36
Eine Funktionsdefinition wird mit dem Schlüsselwort def eingeleitet, gefolgt von einem
frei wählbaren Funktionsnamen. Eingeschlossen in ein Klammerpaar folgt dann die Parameterliste. Sie kann auch leer sein. Die Parameternamen der Parameterliste werden durch
Kommata getrennt.
def funktionsname(Parameterliste):
Anweisung(en)
Der Funktionskörper, der aus den Anweisungen besteht, die bei Aufruf der Funktion ausgeführt werden, wird durch eine homogene Einrückung markiert, also ebenso wie alle anderen Blöcke. Die Funktionsdefinition ist beendet, wenn eine Anweisung erfolgt, die wieder
auf derselben Einrückungsstufe steht wie der Kopf der Funktion.
Der Funktionskörper kann ein oder mehrere return-Anweisungen enthalten. Diese können
sich an beliebiger Stelle innerhalb des Funktionskörpers befinden. Eine return-Anweisung
beendet den Funktionsaufruf, und das Ergebnis des Ausdrucks, der hinter der returnAnweisung steht, wird an die aufrufende Stelle zurückgeliefert. Falls dem return kein
Ausdruck folgt, wird der spezielle Wert None zurückgeliefert. Falls der Funktionsaufruf das
Ende des Funktionskörpers erreicht, ohne auf eine return-Anweisung gestoßen zu sein,
endet der Funktionsaufruf, und es wird ebenfalls der Wert None zurückgegeben.
Schauen wir uns ein einfaches Beispiel an:
def say_hello(name):
return "Hello " + name
n = "Michael"
n = say_hello(n)
print(n)
name = "Kirstin"
name = say_hello(name)
print(name)
Bild 14.2 Funktionsaufrufe
14.3 Docstring
Im Diagramm zeigen wir, was passiert, wenn man das Programm ausführt. Beim ersten
Teil des Programms handelt es sich um die Definition der Funktion. Sie wird beim Lauf
des Programms „übersprungen”. Als erste Anweisung wird die Zuweisung an die Variable n
ausgeführt. Danach wird die Funktion say_hello mit dem Argument n aufgerufen. Zurückgeliefert wird der String „Hello Michael”. Danach wird die Variable „name” auf „Kirstin”
gesetzt. Dann wird wieder die Funktion say_hello aufgerufen, die dann den String „Hello
Kirstin” zurückliefert.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Ein weiteres Beispiel:
def fahrenheit(T_in_celsius):
return (T_in_celsius * 9 / 5) + 32
for t in (22.6, 25.8, 27.3):
print(t, ": ", fahrenheit(t))
Die Ausgabe des obigen Skripts mit der Funktion fahrenheit sieht wie folgt aus:
22.6 :
25.8 :
27.3 :
72.68
78.44
81.14
14.3
Docstring
Wie der Name nahelegt, handelt es sich beim Docstring um einen String, der zu Dokumentationszwecken dient. Er steht in einer Funktion direkt nach dem Funktionskopf, also nach
der „def”-Zeile. Der Wert des Docstrings ist in dem Attribut .__doc__ gespeichert und vom
Programm aus abrufbar.
Wir definieren unsere Funktion „fahrenheit” vom vorigen Abschnitt nun mit einem Docstring.
Diesmal allerdings in der interaktiven Python-Shell:
>>> def fahrenheit(T_in_celsius):
...
""" returns the temperature in degrees Fahrenheit """
...
return (T_in_celsius * 9 / 5) + 32
...
>>>
Wir können sehen, dass der Docstring keine Auswirkungen auf das Ein- und Ausgabeverhalten der Funktion hat:
>>> fahrenheit(10)
50.0
Den Docstring selbst können wir über das Attribut .__doc__ ansprechen:
>>> fahrenheit.__doc__
' returns the temperature in degrees Fahrenheit '
>>>
115
116
14 Funktionen
Die besondere Bedeutung des Docstrings sieht man, wenn man die help-Funktion benutzt.
Wenn wir in der Shell den Befehl help(fahrenheit) eingeben, erhalten wir folgende Ausgabe:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Help on function fahrenheit in module __main__:
fahrenheit(T_in_celsius)
returns the temperature in degrees Fahrenheit
(END)
Durch Eintippen des Buchstaben „q” können Sie übrigens die Help-Ausgabe wieder verlassen.
Besonders eindrucksvoll zeigt sich jedoch der Docstring, wenn wir mehrere Funktionen in
einer Python-Datei definieren. Wir können auch einen weiteren String an den Anfang dieser Datei stellen. Das folgende Programm bietet zwei Funktionen zur Umwandlung zwischen Celsius und Fahrenheit:
"""
Ein Modul mit Konvertierungshilfen zwischen Grad Celsius und Grad
Fahrenheit
"""
def fahrenheit(T_in_celsius):
""" rechnet die Temperatur T_in_celsius in Grad Fahrenheit um """
return T_in_celsius * 9 / 5 + 32
def celsius(T_in_fahrenheit):
""" rechnet die Temperatur T_in_fahrenheit in Grad Celsius um """
return (T_in_fahrenheit - 32) * 5 / 9
Wir speichern das obige Programm in einer Datei, die wir konvertierungen.py nennen. Wir
können diese Datei wie ein Modul importieren.1 Danach rufen wir help auf dieses Modul
auf:
>>> import konvertierungen
>>> help(konvertierungen)
Wir erhalten nun die folgende schöne Ausgabe, die sich in ihrem Aussehen nicht von Standardmodulen unterscheidet:
Help on module konvertierungen:
NAME
konvertierungen - Ein Modul mit Konvertierungshilfen zwischen Grad
Celsius und Grad Fahrenheit
FUNCTIONS
celsius(T_in_fahrenheit)
1
Jedes Python-Programm ist automatisch auch ein Modul!
14.4 Standardwerte für Funktionen
rechnet die Temperatur T_in_fahrenheit in Grad Celsius um
fahrenheit(T_in_celsius)
rechnet die Temperatur T_in_celsius in Grad Fahrenheit um
FILE
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
/home/bernd/Dropbox/python-buch/dritte_auflage/beispiele/
konvertierungen.py
(END)
Wir sehen, dass der Docstring im Hilfstext unter den entsprechenden Funktionen erscheint.
14.4
Standardwerte für Funktionen
Man hat auch die Möglichkeit, die Parameter einer Funktion mit Standardwerten – meist
auch als Default-Werte bezeichnet – zu versehen. Diese Parameter sind dann optional beim
Aufruf der Funktion, d.h. man kann für einen solchen Parameter ein Argument zur Verfügung stellen, muss das aber nicht. Wird kein Wert angegeben, wird der Standardwert für
diesen Parameter eingesetzt. Wir zeigen dies mit einer kleinen Funktion im folgenden Beispiel. Mit der Funktion „hallo” wird eine Person mit Namen begrüßt. Wird jedoch beim
Aufruf kein Name übergeben, druckt sie nur „Hallo Namenloser!”:
>>> def hallo(name="Namenloser"):
...
print("Hallo " + name + "!")
...
>>> hallo("Peter")
Hallo Peter!
>>> hallo()
Hallo Namenloser!
>>>
Im nächsten Beispiel definieren wir die Funktion „umfang”, die den Umfang eines Rechtecks berechnet:
>>> def umfang(laenge=2, breite=1):
...
return 2 * (laenge + breite)
...
>>>
>>> umfang(5, 3)
16
>>> umfang(5)
# für die breite wird der Standardwert ,,1''
benutzt
12
>>> umfang()
# es werden nun beide Standardwerte benutzt
6
117
118
14 Funktionen
Wie sieht es aber aus, wenn wir nur einen Wert für die Breite angeben wollen? Bisher wurden die Argumente der Reihenfolge nach an die Parameter übergeben. Übergibt man nur
ein Argument, bedeutet das automatisch, dass dies ein Wert für den ersten Parameter darstellt, also in unserem Fall für laenge.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
14.5
Schlüsselwortparameter
Für das zuletzt beschriebene Problem liefern die Schlüsselwortparameter eine Lösung.
Schlüsselwortparameter werden benutzt, um beim Aufruf einer Funktion einen Ausdruck
einem bestimmten Parameter zuzuordnen. Die Funktionsdefinition selbst ändert sich
nicht, d.h. ein Schlüsselwortparameter entspricht einem Parameter.
Wir sind nun in der Lage, unsere vorher definierte Funktion „umfang” nur mit einem Wert
für den Parameter „breite” aufzurufen, wenn wir diesen als Schlüsselwortparameter verwenden:
>>> umfang(breite=1.5)
7.0
Im folgenden Beispiel nutzen wir Schlüsselwortparameter, obwohl es nicht notwendig wäre. Es dient aber dazu, die Lesbarkeit eines Programmes deutlich zu erhöhen:
>>> umfang(laenge=5, breite=3)
16
>>> umfang(5, 3)
# so hätten wir auch statt des vorigen Aufrufs
schreiben können
16
>>> umfang(breite=3, laenge=5)
# oder so
16
14.6
Funktionen ohne return
In der Funktion Hallo hatten wir lediglich eine print-Funktion, aber keine returnAnweisung verwendet. Man sieht also, dass es nicht zwingend notwendig ist, eine returnAnweisung in einer Funktion zu haben.
Zu Beginn unseres Kapitels stellten wir aber fest, dass Funktionen einen Wert bzw. ein Objekt zurückliefern. Funktionen liefern immer ein Objekt zurück. Was passiert aber, wenn
wir keine return–Anweisungen in der Funktion haben? Bei Funktionen ohne returnAnweisungen wird das None-Objekt zurückgeliefert. Wir demonstrieren dies mit einer
primitiven Funktion, deren Körper nur aus einer pass-Anweisung besteht:
>>> def f():
...
pass
...
14.7 Mehrere Rückgabewerte
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>>
>>> x = f()
>>> x == None
True
>>> print(x)
None
None wird auch zurückgeliefert, wenn eine return-Anweisung ohne nachfolgenden Ausdruck eine Funktion beendet, wie wir im folgenden Beispiel sehen:
>>> def f():
...
return
...
>>> x == None
True
Ansonsten wird der Wert des Ausdrucks hinter dem return zurückgeliefert.
14.7
Mehrere Rückgabewerte
Eine Funktion kann genau ein Objekt zurückliefern, welches beispielsweise ein numerischer Wert wie eine ganze Zahl (Integer) oder eine Fließkommazahl (float) sein kann, aber
auch ein komplexes Objekt wie etwa eine Liste oder ein Dictionary. Wollen wir beispielsweise drei Integers zurückliefern, können wir diese in ein Tupel oder eine Liste packen
und dieses Listen- oder Tupel-Objekt als return-Wert nutzen. Wir können also indirekt auf
diese Art beliebig viele Werte zurückliefern. Im folgenden Beispiel berechnet die Funktion
fib_intervall die Fibonacci-Begrenzung für eine beliebige positive Zahl, d.h. sie liefert ein
2-Tupel zurück. Das erste Element ist die größte Fibonacci-Zahl, die kleiner oder gleich x
ist, und die zweite Komponente ist die kleinste Fibonacci-Zahl größer oder gleich x. Der
Rückgabewert wird direkt mittels „unpacking” in die Variablen lub und sup gespeichert:
def fib_intervall(x):
"""
liefert ein Tupel mit der größten Fibonacci-Zahl, kleiner oder
gleich x, und der kleinsten Fibonacci-Zahl, größer oder gleich x,
zurück
"""
if x < 0:
return -1
old, new = 0,1
while True:
if new < x:
old, new = new, old+new
else:
return (old, new)
119
120
14 Funktionen
for i in (0, 1, 2, 3, 4, 5, 7, 8, 9, 12, 13, 14) :
print(i, fib_intervall(i))
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Das obige Programm liefert die folgenden Ergebnisse zurück:
0 (0, 1)
1 (0, 1)
2 (1, 2)
3 (2, 3)
4 (3, 5)
5 (3, 5)
7 (5, 8)
8 (5, 8)
9 (8, 13)
12 (8, 13)
13 (8, 13)
14 (13, 21)
14.8
Lokale und globale Variablen
Variablen sind standardmäßig lokal in einer Funktion, in der sie definiert werden. Man
kann aber auch auf globale Variablen innerhalb einer Funktion lesend zugreifen:
def f():
print(s)
s = "Python"
f()
Das obige Skript gibt „Python” aus. Wir fügen s = „Perl” vor der print-Anweisung ein:
def f():
s = "Perl"
print(s)
s = "Python"
f()
print(s)
Die Ausgabe lautet dann:
Perl
Python
Daran erkennt man, dass die Variable s in der Funktion f lokal ist, denn die Variable s des
Hauptprogramms hat noch ihren ursprünglichen Wert. Interessant wird es nun, wenn man
eine print-Anweisung vor die Zuweisung an s in der Funktion f einfügt:
def f():
print(s)
14.8 Lokale und globale Variablen in Funktionen
s = "Perl"
print(s)
s = "Python"
f()
print(s)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Man erhält nun die folgende Fehlermeldung:
UnboundLocalError: local variable 's'
referenced before assignment
Die Variable s ist in f() mehrdeutig, d.h. im ersten print von f() könnte es sich um die globale Variable s mit dem Wert „Python” handeln. Anschließend definieren wir eine lokale
Variable mit dem Wert „Perl”.
Will man innerhalb einer Funktion auf eine globale Variable schreibend zugreifen, so muss
man diese explizit als global deklarieren. Dies geschieht mittels der global-Anweisung:
def f():
global s
print(s)
s = "Perl"
print(s)
s = "Python"
f()
print(s)
Die Ausgabe lautet:
Python
Perl
Perl
Definiert man eine Variable innerhalb einer Funktion als global, so muss diese nicht im Namensraum existieren, aus dem die Funktion aufgerufen wird. Die Variable wird zur Laufzeit
erzeugt und existiert nach Abarbeitung der Funktion auch im Namensraum, aus dem die
Funktion aufgerufen worden ist:
>>> # es existiert keine Variable x:
...
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>>
>>> def f():
...
global x
...
x = 42
...
>>> # nach der Definition von f existiert noch keine Variable x:
121
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
122
14 Funktionen
...
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>>
>>> f()
>>>
>>> # nach dem Aufruf von f() existiert nun eine Variable x
...
>>> x
42
>>>
14.9
Parameterübergabe im Detail
Eine Funktion oder eine Prozedur benötigt üblicherweise Informationen über die Umgebung, aus der heraus sie aufgerufen wurde. Die Schnittstelle zwischen dieser Umgebung
und der Funktion bzw. Prozedur, d.h. dem Funktions- bzw. Prozedurrumpf (body), besteht
aus speziellen Variablen, die man als Parameter bezeichnet. Indem man Parameter benutzt, kann man verschiedenste Objekte von „außen” innerhalb der Funktion benutzen.
Die Syntax, wie man Parameter deklariert, und die Semantik, wie man die Argumente des
Aufrufs an die formalen Parameter der Funktion oder Prozedur weiterleitet, sind abhängig
von der jeweiligen Programmiersprache.
Im vorigen Abschnitt haben wir mehrfach die Begriffe Parameter und Argumente verwendet. Bei einigen Lesern hat sich sicherlich der Eindruck eingestellt, dass die beiden Begriffe synonym sind. Auch in der Literatur werden diese Begriffe häufig synonym verwendet.
Dennoch gibt es einen klar definierten Unterschied. Parameter stehen im Kopf der Funktion oder Prozedur, also in der Definition der Funktion oder Prozedur, während Argumente
beim Aufruf verwendet werden. Sie liefern die Werte für die formalen Parameter während
der Laufzeit des Programms.
Python benutzt einen Mechanismus, den man als „Call by Object” (auch „Call by Object
Reference” oder „Call by Sharing”) bezeichnet. Wenn man unveränderliche Argumente wie
Integer, Strings oder Tupel an eine Funktion übergibt, verhält sich die Übergabe nach außen hin wie eine Wertübergabe2 . Die Referenz auf das unveränderliche Objekt wird an den
formalen Parameter der Funktion übertragen. Innerhalb der Funktion kann der Inhalt des
Objekts nicht verändert werden, da es sich ja um unveränderliche Objekte handelt.
Anders verhält es sich jedoch, wenn man veränderliche Argumente übergibt. Auch sie werden per Referenz an die Funktionsparameter übertragen. Allerdings können einzelne Elemente eines solchen veränderlichen Objekts im Funktionsrumpf verändert werden. Wenn
wir beispielsweise eine Liste an eine Funktion übergeben, müssen wir zwei Fälle unter2
Oft auch als „Call by value” bezeichnet
14.9 Parameterübergabe im Detail
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
scheiden: Die Elemente einer Liste können verändert werden, d.h. diese Änderung wirkt
sich auch auf den Gültigkeitsbereich aus, aus dem die Funktion aufgerufen wurde. Wird
allerdings ein komplett neues Element dem formalen Parameter zugewiesen, z.B. eine andere Liste, dann hat dies keine Auswirkungen auf die „alte” Liste, also auf die Liste, die beim
Aufruf übergeben worden ist.3
Das oben Gesagte veranschaulichen wir zunächst mit einem
Beispiel für die Übergabe einer Integer-Variablen. Der Parameter innerhalb der Funktion ref_demo bleibt solange eine Referenz auf das Objekt, das übergeben wurde, wie keine Änderung am Parameter erfolgt. Sobald also ein neuer
Wert an den Parameter überwiesen wird, erzeugt Python eine neue Speicherstelle für das Objekt, und der formale Parameter zeigt nun auf diese Speicherstelle. Die Variable des
Funktionsaufrufs wird dadurch nicht verändert, wie wir im
folgenden Beispiel nachvollziehen können:
Bild 14.3 ref_demo vor der
Zuweisung „x=42”
def ref_demo(x):
print("x=",x," id=",id(x))
x=42
print("x=",x," id=",id(x))
Wenn man die Funktion ref_demo() des obigen Beispiels aufruft, so wie wir es im
unten stehenden Beispiel tun, können wir prüfen, was mit x passiert: Wir können im
Hauptprogramm (Gültigkeitsbereich main) sehen, dass x die Identität 10914624 hat.
In der ersten print-Anweisung der ref_demo()-Funktion
wird das x aus dem main-Gültigkeitsbereich benutzt, denn
wir sehen, dass wir den gleichen Identitätswert für x erhalten. Wenn wir jedoch den Wert 42 dem x zuweisen, erhält
x eine neue Identität, 10915680. Dies bedeutet, dass die Variable x der Funktion ein neues Objekt referenziert bzw. erhalten hat. Die Variable x in main bleibt unberührt, d.h.
sie referenziert immer noch das Objekt mit der Identität
10914624, welches immer noch den Wert 9 beinhaltet.
Bild 14.4 ref_demo nach der
Dies bedeutet, dass sich Python zuerst wie Call-by- Zuweisung „x=42”
Reference verhält, d.h. man benutzt bzw. referenziert dasselbe Objekt. Aber sobald x einen neuen Wert zugewiesen bekommt, verhält es sich wie
bei Call-by-Value, was in diesem Fall bedeutet, dass x ein eigenes Objekt erhält. Es wird also eine lokale Speicherposition für den formalen Parameter x angelegt, und das x in main
behält seinen ursprünglichen Wert:
>>> x = 9
>>> id(x)
10914624
>>> ref_demo(x)
3
Sollten Sie dazu Verständnisprobleme haben, empfehlen wir Ihnen, das Kapitel 13 (Flaches und tiefes Kopieren) nochmals gründlich durchzuarbeiten.
123
124
14 Funktionen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
x= 9 id= 10914624
x= 42 id= 10915680
>>> id(x)
10914624
>>> x
9
14.10
Effekte bei veränderlichen Objekten
Im vorigen Abschnitt haben wir gesehen, dass man unveränderliche Objekte, die man als
formale Parameter an eine Funktion übergibt, in der Funktion nicht verändern kann. Anders sieht es jedoch bei veränderlichen Objekten wie Listen oder Dictionaries aus.
Im folgenden Beispiel übergeben wir die Liste fib an den formalen Parameter liste der
Funktion s:
>>> def s(liste):
...
print(id(liste))
...
liste += [47,11]
...
print(id(liste))
...
>>> fib = [0,1,1,2,3,5,8]
>>> id(fib)
24746248
>>> s(fib)
24746248
24746248
>>> id(fib)
24746248
>>> fib
[0, 1, 1, 2, 3, 5, 8, 47, 11]
Wir sehen, dass die Werte 47 und 11 an die Liste angehängt werden. Da sowohl die Variable
liste als auch die Variable fib dieses Objekt referenzieren, wirkt sich diese Änderung auf
beide aus.
Dies bedeutet, dass wir die an s übergebene Liste innerhalb der Funktion verändert haben.
Dies kann gewünscht sein, wie dies beispielsweise bei append der Fall ist, aber es könnte
sich auch um einen „unerwünschten” Seiteneffekt der Funktion handeln. Von einem Funktionsaufruf erwartet man, dass die Funktion den korrekten Wert für die Argumente zurückliefert und sonst keine Effekte verursacht, z.B. Ändern von Speicherzuständen von übergebenen Objekten. Auch wenn manche Programmierer bewusst Nebeneffekte zur Lösung
ihrer Probleme einsetzen, wird dies im Allgemeinen als schlechter Stil betrachtet. Schlimmer ist es jedoch, wenn Nebeneffekte4 auftreten, mit denen man nicht gerechnet hat, und
4
auch als Seiteneffekte bezeichnet
14.11 Kommandozeilenparameter
dadurch Fehler verursacht werden. Dies kann auch in Python passieren, z.B. dann, wenn
Listen oder Dictionaries als Parameter übergeben werden.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Man kann dies verhindern, wenn man statt einer Liste eine Kopie der Liste als Parameter
übergibt. Mittels der Slicing-Funktion kann man ganz leicht eine Kopie einer flachen Liste
erzeugen. So wird in s(fib[:]) nicht die Liste fib, sondern eine komplette Kopie übergeben.
Der Aufruf verändert also nicht den Wert von fib, wie man im folgenden interaktiven Programmstück sehen kann:
>>>
...
...
...
>>>
>>>
[0,
>>>
[0,
def s(liste):
liste += [47,11]
print(liste)
fib = [0,1,1,2,3,5,8]
s(fib[:])
1, 1, 2, 3, 5, 8, 47, 11]
fib
1, 1, 2, 3, 5, 8]
14.11
Kommandozeilenparameter
Nicht nur Funktionen, sondern auch einem Python-Skript kann man Argumente übergeben. Man bezeichnet diese als Kommandozeilenparameter. Ruft man ein Python-Skript
aus einer Shell auf, werden die Argumente jeweils durch ein Leerzeichen voneinander
getrennt hinter dem Skriptnamen aufgeführt. Im Python-Skript selber sind die Argumente, also die Kommandozeilenparameter, als Liste unter dem Namen sys.argv abrufbar.
Zusätzlich zu den Kommandozeilenparametern enthält diese Liste auch den Namen
des aufrufenden Skripts (Dateiname). Dieser steht als erster Eintrag in der Liste, also
sys.argv[0].
Das Skript (argumente.py) listet sämtliche mitgegebenen Argumente in der Standardausgabe auf:
# Modul sys wird importiert:
import sys
# Iteration über sämtliche Argumente:
for Argument in sys.argv:
print(Argument)
Beispiel eines Aufrufs, falls das Skript unter argumente.py gespeichert wurde:
python argumente.py python kurs fuer anfaenger
Dieser Aufruf erzeugt folgende Ausgabe:
argumente.py
python
125
126
14 Funktionen
kurs
fuer
anfaenger
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
14.12
Variable Anzahl von Parametern
Man hat sehr häufig Fälle, in denen die Anzahl der beim Aufruf nötigen Parameter a priori
nicht bekannt ist.5 Im Folgenden zeigen wir, wie man Funktionen definiert, die mit einer
beliebigen Anzahl von Argumenten aufgerufen werden können.
Der Begriff Stelligkeit oder Arität bezeichnet in der Informatik die Parameteranzahl von
Funktionen, Prozeduren oder Methoden. Als variadische Funktion bezeichnet man in der
Informatik Funktionen, Prozeduren oder Methoden mit unbestimmter Arität, also solche,
deren Parameteranzahl nicht bereits in ihrer Deklaration festgelegt ist. Zur Definition von
variadischen Funktionen benutzen wir in Python den „*”-Operator vor einem Parameter.
>>> def varpafu(*x): print(x)
...
>>> varpafu()
()
>>> varpafu(34, "Do you like Python?", "Of course")
(34, 'Do you like Python?', 'Of course')
Wir erkennen, dass die beim Aufruf an varpafu übergebenen Argumente in einem Tupel
gesammelt werden. Auf dieses Tupel kann als „normale” Variable im Rumpf der Funktion
zugegriffen werden. Ruft man die Funktion ohne jegliche Argumente auf, so ist x ein leeres
Tupel. Manchmal benötigt man eine feste Anzahl von Positionsparametern, die von einer
beliebigen Anzahl von Parametern gefolgt werden können. Dies ist möglich, aber die Positionsparameter müssen immer zuerst kommen. Im folgenden Beispiel benutzen wir einen
Positionsparameter „city”, der immer angegeben werden muss, gefolgt von einer beliebigen Anzahl von weiteren Städten „other_cities”:
>>> def locations(city, *other_cities):
...
print(city, other_cities)
...
>>> locations("Berlin")
Berlin ()
>>> locations("Berlin", "Freiburg", "Stuttgart",
... "Konstanz","Frankfurt")
Berlin ('Freiburg', 'Stuttgart', 'Konstanz', 'Frankfurt')
Wir wollen dies an einem sinnvolleren Beispiel veranschaulichen. Wir schreiben dazu eine
Funktion, die das arithmetische Mittel aus einer variablen Anzahl von Werten berechnet:
5
Diejenigen mit Programmiererfahrungen in C und C++ kennen das Konzept als varargs.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
14.12 Variable Anzahl von Parametern / Variadische Funktionen
>>> def arimittel(erster_wert, *weitere_werte):
...
return (erster_wert + sum(weitere_werte)) / (1 + len(
weitere_werte))
...
>>> arimittel(1)
1.0
>>> arimittel(1, 2)
1.5
>>> arimittel(35, 89, 103, 1, 17)
49.0
>>> arimittel()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: arimittel() missing 1 required positional argument: '
erster_wert'
Man mag sich fragen, warum wir sowohl einen Positionsparameter „erster_wert” als
auch einen Parameter für eine variable Anzahl von Werten „*weitere_werte” in unserer
Funktionsdefinition benutzt haben. Die Idee besteht darin, dass wir erzwingen wollen,
dass unsere Funktion immer mit einer nicht-leeren Anzahl von Argumenten aufgerufen
wird. Dies ist notwendig, da ein leeres Tupel die Länge 0 hat und wir dann durch 0 dividieren würden, was einen Fehler verursachen würde.
Wie sieht es aber nun aus, wenn wir das arithmetische Mittel einer Liste oder eines Tupel
berechnen wollen? Ruft man die Funktion mit einer Liste oder einem Tupel als Argument
statt mit einer variablen Anzahl von Zahlen auf, wird ein Fehler generiert:
>>> lst = [4, 7, 9, 45]
>>> arimittel(lst)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in arimittel
TypeError: can only concatenate list (not "int") to list
>>>
Da man aber häufig Listen oder Tupel mit Werten hat, von denen man das arithmetische
Mittel berechnen möchte, brauchen wir einen einfachen Weg, um dies zu bewerkstelligen.
In unserem konkreten Fall könnte man die einzelnen Elemente der Liste lst auch direkt
aufrufen. Aber es ist klar, dass dies für eine längere Liste unpraktikabel wäre.
>>> arimittel(lst[0], lst[1], lst[2], lst[3])
16.25
Schwerwiegender ist jedoch die Tatsache, dass wir normalerweise nicht wissen, wieviel
Elemente eine Liste oder Tupel hat. Deshalb ist obiges Vorgehen im allgemeinen Fall nicht
anwendbar.
Die Lösung besteht in der Benutzung eines weiteren Sternchens:
>>> arimittel(*lst)
16.25
127
128
14 Funktionen
14.13
* in Funktionsaufrufen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Ein Stern kann auch in einem Funktionsaufruf erscheinen, wie wir im vorigen Beispiel gesehen haben: Die Semantik ist in diesem Fall „invers” zu der Verwendung eines Sterns in
der Funktionsdefinition. Ein Argument wird entpackt und nicht gepackt. In anderen Worten: Die Elemente einer Liste oder eines Tupels werden vereinzelt:
>>> def f(x,y,z):
...
print(x,y,z)
...
>>> p = (47,11,12)
>>> f(*p)
(47, 11, 12)
Es besteht wohl kaum die Notwendigkeit zu erwähnen, dass diese Art, unsere Funktion
aufzurufen, komfortabler ist als die folgende:
>>> f(p[0],p[1],p[2])
(47, 11, 12)
Unsere Beispielfunktion f erwartet genau 3 Argumente. Häufig wird jedoch sowohl in der
Definition als auch beim Aufruf einer Funktion ein Sternchen verwendet. Beim Aufruf werden dann Tupel oder Listen entpackt, und bei der Definition werden beliebig viele Argumente in ein Tupel gepackt:
>>> def f(*args):
...
return args
...
>>>
>>> f(3, 4, 5, 6)
# In der Funktion f werden die Werte ins Tupel
args gepackt
(3, 4, 5, 6)
>>>
>>> lst = [4, 6, 12]
>>> f(*lst)
# Liste lst wird entpackt, und dann werden die Werte
in args gepackt
(4, 6, 12)
>>>
Da die Länge der Liste lst erst während der Laufzeit bekannt ist, kann man beim Schreiben
des Programms also nur die Sternchen-Notation verwenden. Ein explizites Aufrufen der
Listenelemente, also f(lst[0], lst[1], lst[2]), ist deshalb nicht möglich, wenn wir
die Anzahl der Elemente einer Liste nicht kennen.
14.14 Beliebige Schlüsselwortparameter
14.14
Beliebige Schlüsselwortparameter
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Es gibt auch einen Mechanismus für eine beliebige Anzahl von Schlüsselwortparametern.
Um dies zu ermöglichen, wurde als Notation ein doppeltes Sternchen „**” eingeführt:
>>> def f(**args):
...
print(args)
...
>>> f()
{}
>>> f(de="German",en="English",fr="French")
{'fr': 'French', 'de': 'Germnan', 'en': 'English'}
Die Pseudo-Schlüsselwortparameter werden also in ein Dictionary gepackt.
14.15
Doppeltes Sternchen
im Funktionsaufruf
Das folgende Beispiel veranschaulicht die Verwendung von ** in einem Funktionsaufruf:
>>> def f(a,b,x,y):
...
print(a,b,x,y)
...
>>> d = {'a':'append', 'b':'block','x':'extract','y':'yes'}
>>> f(**d)
('append', 'block', 'extract', 'yes')
und nun * und ** im Zusammenspiel:
>>> def foo(*args, **kwargs):
...
print("args: ", args)
...
print("kwargs: ", kwargs)
...
>>> foo(42,89,12, x=17, y="Hallo", z=[3,8,9])
args: (42, 89, 12)
kwargs: {'x': 17, 'y': 'Hallo', 'z': [3, 8, 9]}
>>>
129
130
14 Funktionen
14.16
Aufgaben
1. Aufgabe:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Auch für diejenigen, die bereits aus dem Alter heraus sind, in dem sie sich mit Freunden gerne in einer Geheimsprache unterhalten, sollte die folgende Aufgabe interessant sein: Zu schreiben ist eine Funktion, die den String, den sie als Eingabe erhält,
in die sogenannte Löffelsprache übersetzt.
>>> txt2loeffelsprache("Hallo")
'Halefallolefo'
>>> txt2loeffelsprache("Verstehst du Deutsch?")
'veleferstelefehst dulefu deulefeutsch?'
Die Codierung erfolgt nach folgenden Regeln: Immer, wenn ein Vokal kommt, wird
dieser verdoppelt und dazwischen ein vorher festgelegter String gesetzt. Häufig wird
für diesen String „lew” oder wie oben „lef” verwendet. Diphtonge werden wie ein Vokal
betrachtet. Damit sieht die Umsetzung wie folgt aus:
e = elewe
a = alewa
i = ilewi
o = olewo
u = ulewu
ü = ülewü
ö = ölewö
ä = älewä
ei = eilewei
au = aulewau
ie = ielewie
eu = euleweu
Der Einfachheit halber wandeln wir den String vor der Kodierung in Kleinbuchstaben
um, damit wir keine Großbuchstabenvokale und -diphtonge behandeln müssen.
Lösung: Lösungen zu Kapitel 14 (Funktionen), Seite 490
2. Aufgabe:
Schreiben Sie eine Funktion, die einen String aus der Löffelsprache wieder in normales Deutsch zurück wandelt.
Also:
>>> l = txt2loeffelsprache("Eineiig")
>>> print(l)
eilefeineilefeiilefig
>>> print(loeffelsprache2txt(l)
... )
eineiig
Hinweis:
Zur Lösung dieser Aufgabe können Sie die Stringmethode replace möglicherweise gut
gebrauchen. Diese wird in Kapitel 19 (Alles über Strings . . . ) auf Seite 186 behandelt.
Lösung: Lösungen zu Kapitel 14 (Funktionen), Seite 491
14.16 Aufgaben
3. Aufgabe:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Schreiben Sie eine Funktion is_palindrome, die prüft, ob es sich bei einer übergebenen Zeichenkette um ein Palindrom handelt.
Anmerkung: Ein Palindrom ist eine Zeichenkette, die von vorne und von hinten gelesen gleich bleibt, wie beispielsweise die Namen „Anna” und „Otto” oder die Wörter
„Reittier” und „Lagerregal”. Das längste deutsche Ein-Wort-Palindrom lautet „Reliefpfeiler”. Palindrome müssen nicht nur aus Buchstaben bestehen. Es gibt auch Zahlenpalindrome, wie etwa 24742.
Lösung: Lösungen zu Kapitel 14 (Funktionen), Seite 492
4. Aufgabe:
Es gibt auch Satzpalindrome. Auch hier gilt, dass sie vorwärts und rückwärts gelesen
gleich sind. Aber bei Satzpalindromen gilt die Besonderheit, dass Leerzeichen und
Sonderzeichen gegebenenfalls ignoriert bzw. eingefügt werden:
Beispiele:
Ein Eis esse sie nie.
Ein Esel lese nie.
Nein, kann Anna knien?
Oh, Cello voll Echo!
Erweitern Sie Ihre Funktion is_palindrome, dass sie auch Satzpalindrome prüfen
kann.
Lösung: Lösungen zu Kapitel 14 (Funktionen), Seite 493
131
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
15
15.1
Rekursive Funktionen
Definition und Herkunft des Begriffs
Die Rekursion hat etwas mit Unendlichkeit zu
tun. Ich weiß, dass die Rekursion etwas mit Unendlichkeit zu tun hat. Er denkt, dass ich weiß,
dass die Rekursion etwas mit Unendlichkeit zu
tun hat. Sie ist sicher, dass er denkt, dass ich
weiß, dass die Rekursion etwas mit Unendlichkeit zu tun hat. Wir glauben nicht, dass sie sicher ist, dass er denkt, dass ich weiß, dass die
Rekursion etwas mit Unendlichkeit zu tun hat
... Dieses sprachliche Spiel könnten wir beliebig fortsetzen. Dies ist ein Beispiel für RekurBild 15.1 Rekursion in der natürlichen
sion in der natürlichen Sprache. Die Rekursi- Sprache
on ist nicht nur eine fundamentale Eigenschaft
der natürlichen Sprache, sondern der menschlichen kognitiven Fähigkeiten. Unser ganzes Denken basiert auf rekursiven Denkprozessen. Der Linguist Noam Chomsky hatte einen Formalismus eingeführt, um sprachliche
Ausdrücke mithilfe einer Metasprache formal zu definieren. Diese Definition definiert rekursiv eine Grammatik. Diese formalen Sprachen von Chomsky werden auch zur Definition und Spezifikation von Programmiersprachen in der Informatik benutzt. Die Beweismethode der „Vollständigen Induktion” in der Mathematik ist auch eine Art von Rekursion.
Das Adjektiv „rekursiv” stammt vom lateinischen Verb „recurrere”, was „zurücklaufen”
bedeutet. Das ist genau das, was eine rekursive Definition oder eine rekursive Funktion
macht: Sie läuft gewissermaßen zurück auf sich selbst, indem sie sich selbst aufruft. Jedem,
der sich bereits ein wenig mit Mathematik oder Informatik beschäftigt hat, dürfte wohl die
Fakultät begegnet sein. Vor allem, wenn man bereits eine Programmiersprache erlernt hat.
Die Fakultätsfunktion ist das Standardbeispiel für die Einführung der Rekursion in nahezu jedem Tutorial. Die Gründe liegen auf der Hand: Der mathematische Hintergrund ist
leicht zu verstehen, und sie lässt sich besonders leicht rekursiv programmieren. Also folgen wir der Tradition und widmen uns auch in unserem Python-Tutorial dieser Funktion.
Mathematisch wird die Fakultät wie folgt definiert:
n! = n * (n-1)!, if n > 1 and f(1) = 1
134
15 Rekursive Funktionen
15.2
Definition der Rekursion
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Rekursion ist eine Programmiertechnik oder ein Programmierkonzept, in dem eine
Funktion sich selbst ein oder mehrmals in ihrem Funktionskörper (body) aufruft. Eine
Funktionsdefinition, die die Bedingungen der Rekursion erfüllt, nennen wir eine rekursive
Funktion.
Abbruchbedingung: Eine rekursive Funktion muss terminieren, damit man sie in einem
Programm benutzen kann. Eine rekursive Funktion terminiert, wenn mit jedem Rekursionsschritt das Problem reduziert wird und sich in Richtung der Abbruchbedingung bewegt,
d.h. dem Fall, in dem die Funktion sich nicht mehr selbst aufruft. So liefert beispielsweise die Fakultätsfunktion für das Argument 1 den Wert 1 zurück, ohne dass ein rekursiver
Aufruf notwendig ist (Anmerkung für Mathematiker: Das Abbruchkriterium in einer rekursiven Funktion entspricht der Induktionsverankerung bei der vollständigen Induktion!).
Eine rekursive Funktion kann in eine Endlosschleife führen, wenn das Abbruchkriterium
nicht erreicht wird.
Beispiel für die Fakultät:
4! = 4 * 3!
3! = 3 * 2!
2! = 2 * 1
Ersetzt man die ausgerechneten Werte in der jeweiligen Ursprungsgleichung, erhält man
für 4! folgenden Ausdruck:
4! = 4 * 3 * 2 * 1
Ganz allgemein könnten wir sagen: Rekursion ist eine Methode in der Informatik, in der
die Lösung für ein Problem auf die Lösung kleinerer Instanzen des Problems zurückgeführt
wird.
15.3
Rekursive Funktionen in Python
Als Beispiel für eine rekursive Funktion in Python wählen wir eine rekursive Implementierung der Fakultätsfunktion in Python. Man sieht, dass das Python-Skript ebenso elegant
wie die mathematische Funktion ist:
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
Wenn wir nachvollziehen wollen, wie die Funktion funktioniert, können wir zwei print()Funktionen ergänzen:
15.4 Die Tücken der Rekursion
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def factorial(n):
print("factorial has been called with n = " + str(n))
if n == 1:
return 1
else:
res = n * factorial(n-1)
print("intermediate result for ", n, " * factorial(" ,n-1, "):
",res)
return res
print(factorial(5))
Obiges Python-Skript liefert folgende Ausgaben:
factorial has been called with n = 5
factorial has been called with n = 4
factorial has been called with n = 3
factorial has been called with n = 2
factorial has been called with n = 1
intermediate result for 2 * factorial(
intermediate result for 3 * factorial(
intermediate result for 4 * factorial(
intermediate result for 5 * factorial(
120
1
2
3
4
):
):
):
):
2
6
24
120
Die Fakultätsfunktion lässt sich natürlich auch iterativ schreiben:
def iterative_factorial(n):
result = 1
for i in range(2,n+1):
result *= i
return result
15.4
Die Tücken der Rekursion
In diesem Abschnitt geht es um ein spezielles
Problem bei der Rekursion. Ruft eine Funktion sich in einem Ausdruck wiederholt auf,
also mehr als einmal, erhalten wir ein exponentielles Laufzeitverhalten. Am Beispiel
der Fibonacci-Zahlen demonstrieren wir, wie
dieses exponentielle Laufzeitverhalten zustande kommt.
Die Fibonacci-Zahlen sind eine unendliche
Zahlenfolge. Deshalb werden die FibonacciZahlen auch als Fibonacci-Folge bezeichnet.
Bild 15.2 Fibonacci-Rechtecke und goldener
Schnitt
135
136
15 Rekursive Funktionen
Im Folgenden sehen Sie die ersten Glieder der
Fibonacci-Folge:
0,1,1,2,3,5,8,13,21,34,55,89, ...
Versuchen Sie doch einmal, das Konstruktionsprinzip dieser Folge zu erkennen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Es sollte hoffentlich nicht zu schwer gewesen sein. Jede Zahl, außer den beiden ersten,
ergibt sich aus der Summe der beiden Vorgänger.
Formale Definition der Fibonacci-Zahlen:
Fn = Fn-1 + Fn-2
wobei gilt: F0 = 0 und F1 = 1
Die Fibonacci-Zahlen sind nach dem Mathematiker Leonardo von Pisa, besser bekannt als
Fibonacci, benannt. In seinem Buch „Liber Abaci”, das im Jahre 1202 veröffentlicht wurde,
führte er diese Folge als Übungsaufgabe ein. In dieser Übung geht es um Kaninchen. Seine
Folge beginnt mit F1 = 1, während in modernen Mathematikbüchern und Büchern über
Programmiersprachen die Folge meistens mit dem Wert für das Argument 0 startet, also F0
= 0. Dies ist aber gewissermaßen nur Geschmackssache, denn ob man bei 0 oder 1 startet,
hat keine Auswirkungen auf die weiteren Folgeglieder.
Die Fibonacci-Zahlen resultieren also aus einem „künstlichen” Kaninchenproblem, das die
folgenden Bedingungen erfüllt:
■
■
■
Die Anfangspopulation wird von einem Kaninchenpaar gebildet.
Ein neugeborenes Kaninchenpaar kann sich erst am Ende des ersten Monats paaren und
wirft am Ende des zweiten Monates ein weiteres Paar.
Ansonsten wirft jedes Kaninchenpaar jeweils ein weiteres Kaninchenpaar pro Monat. Sie
sind unsterblich.
Die Fibonacci-Zahlen geben also die Anzahl der Paare am Ende eines bestimmten Monats
an.
15.5
Fibonacci-Folge in Python
Nun kommen wir endlich wieder zu Python und den rekursiven Funktionen zurück. Die Fibonacci-Zahlen lassen sich sehr leicht als rekursive Python-Funktion realisieren. Die rekursive Python-Implementierung spiegelt ziemlich exakt die rekursive mathematische Funktion wider:
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
15.5 Fibonacci-Folge in Python
Eine iterative Funktion zur Berechnung der Fibonacci-Zahlen lässt sich ebenso leicht in
Python bewerkstelligen, wie wir im folgenden Skript sehen können:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def fibi(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
Benutzt man die beiden Funktionen fib() und fibi(), stellt man sehr schnell fest, dass sie ein
deutlich unterschiedliches Laufzeitverhalten an den Tag legen. Bei kleinen Werten, z.B. n
= 10, 11 oder 15, hat man rein gefühlsmäßig, also ohne exakte Zeitmessung, den Eindruck,
dass fibi() und fib() genauso schnell wären. Aber bei Werten ab n = 30 sieht es deutlich
anders aus. fib() braucht nun sekundenlang, während fibi() weiterhin „sofort” mit dem Ergebnis da ist. Um dieses Gefühl mit soliden Messwerten zu untermauern, haben wir ein
Skript geschrieben, mit dem wir die Zeiten messen können:
from timeit import Timer
from fibo import fib
t1 = Timer("fib(10)","from fibo import fib")
for i in range(1,41):
s = "fib(" + str(i) + ")"
t1 = Timer(s,"from fibo import fib")
time1 = t1.timeit(3)
s = "fibi(" + str(i) + ")"
t2 = Timer(s,"from fibo import fibi")
time2 = t2.timeit(3)
print("n=%2d, fib: %8.6f, fibi: %7.6f, percent: %10.2f" % (i,
time1, time2, time1/time2))
time1 ist die Zeit in Sekunden, die drei Aufrufe von fib(n) benötigen, und time2 ist entsprechend die Zeit für drei Aufrufe der iterativen Funktion fibi(). Wenn wir uns die Ergebnisse
anschauen, sehen wir, dass fib(20) etwa 14 Millisekunden benötigt, während fibi(20) für
drei Aufrufe gerade 0,011 Millisekunden benötigt. fib(20) ist also etwa 1300 Mal schneller
als fibi(20). fib(40) braucht bereits 215 Sekunden, also mehr als 3,5 Minuten, während fibi(4) sich mit 0,016 Millisekunden begnügt.
n= 1,
n= 2,
n= 3,
n= 4,
n= 5,
n= 6,
n= 7,
n= 8,
n= 9,
n=10,
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
0.000004,
0.000005,
0.000006,
0.000008,
0.000013,
0.000020,
0.000030,
0.000047,
0.000075,
0.000118,
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
0.000005,
0.000005,
0.000006,
0.000005,
0.000006,
0.000006,
0.000006,
0.000008,
0.000007,
0.000007,
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
0.81
1.00
1.00
1.62
2.20
3.36
5.04
5.79
10.50
16.50
137
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
138
15 Rekursive Funktionen
n=11,
n=12,
n=13,
n=14,
n=15,
n=16,
n=17,
n=18,
n=19,
n=20,
n=21,
n=22,
n=23,
n=24,
n=25,
n=26,
n=27,
n=28,
n=29,
n=30,
n=31,
n=32,
n=33,
n=34,
n=35,
n=36,
n=37,
n=38,
n=39,
n=40,
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
0.000198, fibi: 0.000007, percent:
27.70
0.000287, fibi: 0.000007, percent:
41.52
0.000480, fibi: 0.000007, percent:
69.45
0.000780, fibi: 0.000007, percent:
112.83
0.001279, fibi: 0.000008, percent:
162.55
0.002059, fibi: 0.000009, percent:
233.41
0.003439, fibi: 0.000011, percent:
313.59
0.005794, fibi: 0.000012, percent:
486.04
0.009219, fibi: 0.000011, percent:
840.59
0.014366, fibi: 0.000011, percent:
1309.89
0.023137, fibi: 0.000013, percent:
1764.42
0.036963, fibi: 0.000013, percent:
2818.80
0.060626, fibi: 0.000012, percent:
4985.96
0.097643, fibi: 0.000013, percent:
7584.17
0.157224, fibi: 0.000013, percent:
11989.91
0.253764, fibi: 0.000013, percent:
19352.05
0.411353, fibi: 0.000012, percent:
34506.80
0.673918, fibi: 0.000014, percent:
47908.76
1.086484, fibi: 0.000015, percent:
72334.03
1.742688, fibi: 0.000014, percent: 123887.51
2.861763, fibi: 0.000014, percent: 203442.44
4.648224, fibi: 0.000015, percent: 309461.33
7.339578, fibi: 0.000014, percent: 521769.86
11.980462, fibi: 0.000014, percent: 851689.83
19.426206, fibi: 0.000016, percent: 1216110.64
30.840097, fibi: 0.000015, percent: 2053218.13
50.519086, fibi: 0.000016, percent: 3116064.78
81.822418, fibi: 0.000015, percent: 5447430.08
132.030006, fibi: 0.000018, percent: 7383653.09
215.091484, fibi: 0.000016, percent: 13465060.78
Was ist faul an unserer rekursiven Implementierung?
Schauen wir uns den Berechnungsbaum an, d.h. die Reihenfolge, in der die Funktionsaufrufe erfolgen. Statt fib() schreiben wir allerdings vereinfachend f():
15.5 Fibonacci-Folge in Python
Wir können sehen, dass der Unterbaum zur Berechnung von f(2) (also fib(3)) dreimal auftaucht und der Unterbaum zur Berechnung von f(3) zweimal. Wenn man sich vorstellt, f(6)
zu berechnen, erkennt man, dass f(4) zweimal aufgerufen wird, f(3) dreimal und so weiter. Das bedeutet, dass alle Berechnungen immer wieder durchgeführt werden, da bereits
berechnete Werte nicht gespeichert werden. Sie müssen also immer wieder aufs Neue berechnet werden.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir können eine „Erinnerung” für unsere rekursive Version implementieren. Dazu nutzen
wir ein Dictionary, in dem wir bereits berechnete Werte speichern:
memo = {0:0, 1:1}
def fibm(n):
if not n in memo:
memo[n] = fibm(n-1) + fibm(n-2)
return memo[n]
Diese Technik, also das Speichern von Ergebnisse zur späteren Wiederverwendung, bezeichnet man als Memoisation.
Wir schauen uns wieder die benötigten Zeiten im Vergleich zu fibi() an:
from timeit import Timer
from fibo import fib
t1 = Timer("fib(10)","from fibo import fib")
for i in range(1,41):
s = "fibm(" + str(i) + ")"
t1 = Timer(s,"from fibo import fibm")
time1 = t1.timeit(3)
s = "fibi(" + str(i) + ")"
t2 = Timer(s,"from fibo import fibi")
time2 = t2.timeit(3)
print("n=%2d, fib: %8.6f, fibi: %7.6f, percent: %10.2f" % (i,
time1, time2, time1/time2))
Wir können sehen, dass unsere neue Funktionsversion sogar schneller ist als die iterative.
Vor allem die großen Argumente profitieren besonders von der Memoisation:
n= 1,
n= 2,
n= 3,
n= 4,
n= 5,
n= 6,
n= 7,
n= 8,
n= 9,
n=10,
n=11,
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
0.000011,
0.000011,
0.000012,
0.000012,
0.000012,
0.000011,
0.000012,
0.000011,
0.000011,
0.000010,
0.000011,
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
0.000015,
0.000013,
0.000014,
0.000015,
0.000016,
0.000017,
0.000017,
0.000018,
0.000018,
0.000020,
0.000020,
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
0.73
0.85
0.86
0.79
0.75
0.65
0.72
0.61
0.61
0.50
0.55
139
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
140
15 Rekursive Funktionen
n=12,
n=13,
n=14,
n=15,
n=16,
n=17,
n=18,
n=19,
n=20,
n=21,
n=22,
n=23,
n=24,
n=25,
n=26,
n=27,
n=28,
n=29,
n=30,
n=31,
n=32,
n=33,
n=34,
n=35,
n=36,
n=37,
n=38,
n=39,
n=40,
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
fib:
0.000004,
0.000004,
0.000004,
0.000004,
0.000003,
0.000004,
0.000004,
0.000004,
0.000003,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
0.000004,
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
fibi:
0.000007,
0.000007,
0.000008,
0.000008,
0.000008,
0.000009,
0.000009,
0.000009,
0.000010,
0.000009,
0.000010,
0.000010,
0.000011,
0.000012,
0.000011,
0.000011,
0.000012,
0.000012,
0.000013,
0.000012,
0.000012,
0.000013,
0.000012,
0.000013,
0.000013,
0.000014,
0.000014,
0.000013,
0.000014,
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
percent:
0.59
0.57
0.52
0.50
0.39
0.45
0.45
0.45
0.29
0.45
0.40
0.40
0.35
0.33
0.34
0.35
0.32
0.33
0.31
0.34
0.33
0.30
0.34
0.31
0.31
0.29
0.29
0.31
0.29
Wenn Sie gerne wissen wollen, wie wir den rekursiven Ansatz in seiner „Reinheit” retten
können, dann empfehlen wir das Kapitel 33.9 (Memoisation).
15.6
Aufgaben
1. Aufgabe:
Schreiben Sie eine rekursive Version der Funktion f(n) = 3 * n, also eine Funktion, die
die Vielfachen von 3 berechnet.
Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 495
15.6 Aufgaben
2. Aufgabe:
Schreiben Sie eine rekursive Python-Funktion, welche die Summe der ersten n ganzen Zahlen zurückliefert.
(Hinweis: Diese Funktion sieht ähnlich wie die Fakultätsfunktion aus!)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 495
3. Aufgabe:
Schreiben Sie eine Funktion, die das Pascalsche Dreieck implementiert:
1
1
1
1
1
1
1
2
3
4
5
1
3
1
6
4
1
10 10 5
1
Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 495
4. Aufgabe:
Die Fibonacci-Zahlen verbergen sich auch innerhalb des Pascalschen Dreiecks. Wenn
man die fett markierten Zahlen im folgenden Pascalschen Dreieck aufsummiert, erhält
man die 7. Fibonacci-Zahl:
1
1
1
1
1
2
3
1
3
1
6
4
1
1
5
10 10 5
1
1
6
15 20 15 6
1
1
4
Schreiben Sie ein rekursives Programm, d.h. rekursive Funktion, die die FibonacciZahlen aus einem Pascalschen Dreieck heraus berechnet.
Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 496
5. Aufgabe:
Schreiben Sie eine rekursive Funktion in Python für das Sieb des Eratosthenes. Das
Sieb des Eratosthenes ist ein einfacher Algorithmus zur Berechnung aller Primzahlen bis zu einer bestimmten natürlichen Zahl. Dieser Algorithmus geht auf den alten
griechischen Mathematiker Eratosthenes zurück. Algorithmus zum Finden aller Primzahlen kleiner einer gegebenen natürlichen Zahl n:
1. Erzeuge eine Liste der natürlichen Zahlen von 2 bis n: 2, 3, 4, ..., n.
2. Starte mit einem Zähler i mit dem Wert 2, d.h. die erste Primzahl.
3. Beginnend mit i+i, inkrementiere jeweils um i und lösche alle so erhaltenen Zahlen
aus der Liste, falls vorhanden, also die Zahlen 2*i, 3*i, 4*i, und so weiter.
141
142
15 Rekursive Funktionen
4. Finde die erste auf i folgende Zahl in der Liste. Dies ist die nächste Primzahl.
5. Setze i auf den im letzten Schritt gefundenen Wert.
6. Wiederhole die Schritte 3, 4 und 5, bis i größer als n ist. (Als Verbesserung: Es
genügt, bis zur Quadratwurzel von n zu gehen)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
7. Alle Zahlen, die noch in der Liste sind, sind Primzahlen.
Wenn wir uns den obigen Algorithmus genauer anschauen, sehen wir, dass wir ineffizient sind. So versuchen wir zum Beispiel, die Vielfachen von 4 zu entfernen, obwohl
die bereits durch die Vielfachen von 2 entfernt worden sind. Man erkennt, dass es
genügt, die Vielfachen der Primzahlen bis zu der Quadratwurzel von n zu entfernen.
Diese Menge kann man rekursiv konstruieren.
Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 496
6. Aufgabe:
Schreiben Sie eine rekursive Funktion find_index(), die für einen Wert n den Index aus
der Fibonacci-Folge zurückliefert, falls n eine Fibonacci-Zahl ist, und ansonsten -1
zurückliefert, also wenn n kein Element der Folge ist.
Für eine Fibonacci-Zahl n gilt also:
fib(find_index(n)) == n
Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 497
7. Aufgabe:
Die Summe der Quadrate zweier aufeinander folgender Fibonacci-Zahlen ist ebenfalls
wieder eine Fibonacci-Zahl. Man sieht dies zum Beispiel bei 2 und 3, denn 2*2 + 3*3
ergibt 13, was Fib(7) entspricht.
Benutzen Sie die Funktion aus der vorigen Aufgabe, um die Position der Summe der
Quadrate aus zwei aufeinander folgenden Fibonacci-Zahlen in der Folge zu finden.
Mathematische Erklärung:
Seien a und b zwei aufeinander folgende Fibonacci-Zahlen. Die Fibonacci-Folge, die
mit „a” startet, sieht wie folgt aus:
0
1
2
3
4
5
6
a
b
a + b
a + 2b
2a + 3b
3a + 5b
5a + 8b
Wir erkennen, dass die Fibonacci-Zahlen als Faktoren von a und b erscheinen. Das
n-te Element dieser Folge kann mit folgender Formel berechnet werden:
F(n) = Fib(n-1)* a + Fib(n) * b
Daraus folgt, dass für eine natürliche Zahl n mit n > 1 Folgendes gilt:
Fib(2*n + 1) = Fib(n)**2 + Fib(n+1)**2
Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 498
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
16
16.1
Listen und Tupel
im Detail
Stapelspeicher
Wir haben schon einiges über Listen erfahren, aber es
fehlen noch einige wichtige Aspekte. So kann man eine Liste auch wie einen Stapelspeicher1 behandeln. In
der Informatik bezeichnet ein Stapelspeicher (oder Stapel) – manchmal auch Kellerspeicher (Keller) genannt –
eine Datenstruktur, die im Wesentlichen zwei Operationen besitzt: eine, mit der man Daten auf den Stapel legen kann, und eine, um das oberste Element vom Stapel wegzunehmen. Stellen Sie sich das wie einen Stapel noch zu lesender Bücher vor. Sie wollen die Bücher
des Stapels von oben nach unten durchlesen. Kaufen Sie
ein neues Buch, kommt es oben drauf und ist damit das
nächste Buch, was gelesen werden „muss”.
Bild 16.1 Bücherstapel
In den Programmiersprachen werden hierfür meistens die folgenden Operationen zur Verfügung gestellt:
■
■
■
1
2
push
Mit dieser Methode legt man ein neues Objekt oben2 auf den Stapel. Diese Operation
bezeichnet man im Deutschen als „einkellern”. Bei Python gibt es im Gegensatz zu vielen anderen Sprachen keine Methode mit dem Namen „push”, aber „append” erfüllt die
gleiche Funktionalität.
pop
Diese Methode liefert das oberste Objekt eines Stapels zurück. Dabei wird das Objekt
vom Stapel entfernt. Man bezeichnet dies im Deutschen als „auskellern”.
peek
Diese Methode dient zum Nachschauen, was zuoberst auf dem Stapel liegt. Dabei wird
das Objekt aber nicht wie bei pop entfernt. In Python gibt es keine solche Methode. Bei
Listen oder Tupel kann man sie jedoch mit einem Indexzugriff simulieren. Falls liste
eine Liste bezeichnet, dann verhält sich liste[-1] wie eine Methode liste.peek(), die es
aber in der list-Klasse nicht gibt.
englisch: stack
oder „rechts”, je nach Sichtweise
144
16 Listen und Tupel im Detail
16.2
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
■
Stapelverarbeitung in Python
s.append(x)
Hängt x ans Ende der Liste s an. Dies entspricht der Methode „push”, so wie sie sich in
anderen Programmiersprachen findet.
s.pop(i)
Gibt das i-te Element von s zurück und entfernt es dabei aus der Liste. Normalerweise,
also in anderen Sprachen, liefert pop nur das „oberste” bzw. das am weitesten rechts
stehende Element zurück.
s.pop()
Ist i nicht angegeben, wird das letzte Element genommen, was dem „normalen” pop entspricht, wie es in anderen Programmiersprachen verwendet wird.
Wie man „pop” und „append” anwendet, kann man dem folgenden Beispiel entnehmen.
>>> colors=["red","green"]
>>> colors.append("blue")
>>> colors
['red', 'green', 'blue']
>>> c = colors.pop()
>>> c
'blue'
>>> colors
['red', 'green']
>>> colors.append(colors.pop())
>>> colors
['red', 'green']
16.3
Bild 16.2 pop und append
extend
Die Methode „extend” dient dazu, an eine Liste mehrere Elemente anzuhängen.
s.extend(t)
Dabei muss das Argument „t” ein iterierbares Objekt sein.
>>>
>>>
>>>
[0,
fib = [0,1,1,2,3,5]
fib.extend([8,13,21])
fib
1, 1, 2, 3, 5, 8, 13, 21]
Man beachte den Unterschied zu append:
>>>
>>>
>>>
[0,
fib = [0,1,1,2,3,5]
fib.append([8,13,21])
fib
1, 1, 2, 3, 5, [8, 13, 21]]
16.4 ,+’-Operator oder append
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Das Argument von extend() kann auch ein Tupel sein. Das Ergebnis bleibt gleich:
>>>
>>>
>>>
>>>
>>>
[0,
>>>
>>>
>>>
[0,
t = (8,13,21)
l = [8,13,21]
fib = [0,1,1,2,3,5]
fib.extend(t)
print fib
1, 1, 2, 3, 5, 8, 13, 21]
fib = [0,1,1,2,3,5]
fib.extend(l)
print fib
1, 1, 2, 3, 5, 8, 13, 21]
Übergibt man einen String als Argument für extend, so wird der String in seine einzelnen
Buchstaben zerlegt, die dann einzeln in die Liste eingefügt werden:
>>> liste = []
>>> liste.extend("So what?")
>>> liste
['S', 'o', ' ', 'w', 'h', 'a', 't', '?']
16.4
,+’-Operator oder append
Außer append gibt es noch weitere Möglichkeiten, Elemente an eine Liste anzuhängen. So
kann man beispielsweise ein oder mehrere Elemente mit dem „+“-Operator anhängen:
>>>
>>>
>>>
[3,
L = [3,4]
L = L + [42]
L
4, 42]
Was das Laufzeitverhalten betrifft, ist bei diesem Weg höchste Vorsicht geboten, wie wir im
Folgenden sehen werden. Eine weitere Möglichkeit besteht in der Verwendung der erweiterten Zuweisung3 :
>>>
>>>
>>>
[3,
L = [3,4]
L += [42]
L
4, 42]
Logisch gesehen sind beide Vorgehensweisen gleichwertig, d.h. sie liefern die gleichen Ergebnisse. Im Folgenden wollen wir das Laufzeitverhalten dieser beiden und der appendMethode vergleichen. Wir messen die Laufzeit mittels des time-Moduls, auf das wir hier
nicht weiter eingehen wollen. Für das Verständnis des Programms genügt es zu wissen,
3
engl. augmented assignment, compound assignment
145
146
16 Listen und Tupel im Detail
dass time.time() eine Float-Zahl zurückliefert, die die Zeit in Sekunden seit „The Epoch”4
darstellt. Mit time.time() - start_time berechnen wir also die Zeit in Sekunden, die
nötig war, die for-Schleifen zu berechnen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
import time
n= 100000
start_time = time.time()
l = []
for i in range(n):
l = l + [i * 2]
print(time.time() - start_time)
start_time = time.time()
l = []
for i in range(n):
l += [i * 2]
print(time.time() - start_time)
start_time = time.time()
l = []
for i in range(n):
l.append(i * 2)
print(time.time() - start_time)
Dieses Programm liefert „erschreckende” Ergebnisse:
26.3175041676
0.0305399894714
0.0207479000092
Der „+”-Operator ist in diesem Lauf etwa 1268-mal langsamer als die append-Methode. Die
Erklärung ist einfach: Bei der append-Methode wird in jedem Schleifendurchlauf einfach
ein weiteres Element an die Liste angehängt. Im ersten Fall wird in jedem Schleifendurchgang, also mit jeder Zuweisung l = l + [i * 2], die komplette Liste kopiert, dann das
neue Element an die kopierte Liste angehängt. Anschließend muss der Speicherplatz für
die alte – nun nicht mehr benötigte – Liste freigegeben werden. Wir können auch sehen,
dass die Benutzung der erweiterten Zuweisung im Vergleich zur ersten Methode nahezu
genauso schnell ist wie der Weg mit append. Allerdings ist die erweiterte Methode dennoch etwas langsamer, da bei append das übergebene Objekt nur referenziert wird.
4
Bezeichnet das Datum 1. Januar 1970 und die Uhrzeit 00:00, als Beginn der Unix-Zeitzählung (ab UNIX
Version 6)
16.5 Entfernen eines Wertes
16.5
Entfernen eines Wertes
Mit der Methode „remove” kann man einen bestimmten Wert ohne Kenntnis des Indexes
aus einer Liste entfernen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
s.remove(x)
Dieser Aufruf entfernt das erste Vorkommen des Wertes x aus der Liste s. Falls x beim Aufruf
nicht in der Liste vorhanden ist, gibt es einen ValueError.
Im folgenden Beispiel rufen wir dreimal die remove-Methode auf, um die Farbe „green” zu
entfernen. Da die Farbe nur zweimal in der Liste „colours” vorkommt, erhalten wir beim
dritten Mal einen ValueError:
>>> colours = ["red", "green", "blue", "green", "yellow"]
>>> colours.remove("green")
>>> colours
['red', 'blue', 'green', 'yellow']
>>> colours.remove("green")
>>> colours
['red', 'blue', 'yellow']
>>> colours.remove("green")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
16.6
Enthalten in Liste
Wie wir bereits gesehen haben, kann man mit „el in liste” testen, ob ein Wert „el” in der
Liste „liste” vorkommt. Man erhält als Ergebnis True, wenn „el” einmal oder mehrmals in
der Liste vorkommt. Ist man an der genauen Anzahl interessiert, bietet sich die Methode
„count” an. „count” liefert die Anzahl der Vorkommen eines Elements in einer Liste zurück.
>>> colours = ["red", "green", "blue", "green", "yellow"]
>>> colours.count("green")
2
>>> colours.count("black")
0
16.7
Positionsbestimmung
Mit der Methode index kann man die Position eines Elements innerhalb einer Liste ermitteln.
s.index(x[, i[, j]])
147
148
16 Listen und Tupel im Detail
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Es wird der Index für das x ermittelt. Falls der optionale Parameter i gegeben ist, beginnt
die Suche erst ab dieser Position und endet bei der Position j, falls j gegeben ist. Es erfolgt
eine Fehlermeldung, falls x nicht in s vorkommt.
>>> colours = ["red", "green", "blue", "green", "yellow"]
>>> colours.index("green")
1
>>> colours.index("green", 2)
3
>>> colours.index("green", 3,4)
3
>>> colours.index("black")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 'black' is not in list
16.8
Einfügen von Elementen
Wir haben gesehen, dass wir mit append ein Element an das Ende einer Liste anhängen
können. Aber es gibt keine Möglichkeit, mittels append ein Element an eine beliebige Stelle
einer Liste einzufügen. Dazu gibt es die Methode „insert”:
s.insert(index, object)
Ein Objekt „object” wird in die Liste „s” eingefügt. Die früheren Elemente ab der Position
index werden um eins nach rechts verschoben.
>>> colours = ["red", "green", "blue", "yellow"]
>>> colours.insert(1,"black")
>>> colours
['red', 'black', 'green', 'blue', 'yellow']
Das Verhalten der Methode „append” lässt sich sehr einfach mit „insert” simulieren:
>>> abc = ["a","b","c"]
>>> abc.insert(len(abc),"d")
>>> abc
['a', 'b', 'c', 'd']
16.9
Besonderheiten bei Tupel
Wir haben bereits erfahren, dass Tupel unveränderlich sind, d.h. man kann Elemente nicht
entfernen, verändern oder neue Elemente anfügen. Das bedeutet, dass es die Methoden
„pop”, „append” und „insert” für Tupel nicht gibt. Auch über den Index kann man ein Tupel
nicht verändern, wie wir im Folgenden sehen:
16.9 Besonderheiten bei Tupel
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> t = (1,1,2,3,5,8,13)
>>> t[0] = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
16.9.1
Leere Tupel
Leere Tupel definiert man mit leeren runden Klammern:
>>> t = ()
>>> type(t)
<class 'tuple'>
16.9.2
1-Tupel
Wie sieht es aber aus, wenn man ein Tupel mit nur einem Element definieren will? Runde Klammern werden auch in arithmetischen Ausdrücken zur Klammerung von Teilausdrücken verwendet. So sind beispielsweise ((a + b) * c) und (c) gültige Ausdrücke. Damit versteht man auch, dass „t = (5)” eine Integer-Zahl und nicht einen Einer-Tupel mit der
Zahl 5 definiert:
>>> t = (5)
>>> type(t)
<class 'int'>
Tupel mit nur einem Element kann man nur mit einem „Trick” generieren. Hinter dem
Wert muss ein Komma folgen. Dies sieht nicht schön aus, aber erfüllt seinen Zweck:
>>> t = (5,)
>>> type(t)
<class 'tuple'>
16.9.3
Mehrfachzuweisungen, Packing und Unpacking
Wir haben schon gesehen, dass es in Python eine Mehrfachzuweisung gibt, mit der man
beispielsweise ideal Variablenwerte vertauschen kann:
>>> x, y = 47, 11
>>> x, y = y, x
>>> print(x,y)
11 47
Wir wollen nun zeigen, worin der Zusammenhang zwischen obigen Anweisungen und Tupeln besteht.
149
150
16 Listen und Tupel im Detail
Die folgende Anweisung bezeichnet man als Tupel-Packing, da man die Zahlen 47 und 11
in ein Tupel packt:
>>> t = "Philip", "Glass"
>>> type(t)
<class 'tuple'>
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Dies ist gewissermaßen eine Kurzschreibweise für
>>> t = ("Philip", "Glass")
Man spricht von Tupel-Unpacking oder Tupel-Entpacken, wenn man die einzelnen Werte
eines Tupels Variablen zuordnet:
>>> (first, last) = t
>>> print(first, last)
Philip Glass
Es gibt auch viele Anwendungsfälle, bei denen man beispielsweise den ersten Wert der
rechten Seite einer Variablen zuweisen will und den Rest als Liste einer weiteren Variablen.
Dies lässt sich mit dem Sternoperator umsetzen:
>>> first, *remainder = 35, 99,27, 18
>>> print(first)
35
>>> print(remainder)
[99, 27, 18]
Dies lässt sich auch analog mit der letzten Variablen bewerkstelligen. Natürlich auch in
Kombination mit der ersten Variablen:
>>> first, *middle, last = 35, 99,27, 18
>>> print(first)
35
>>> print(middle)
[99, 27]
>>> print(last)
18
>>>
>>> name = "Joyce Carol Oates"
>>> firstname, middlename, surname = name.split()
>>> print(firstname)
Joyce
>>> print(middlename)
Carol
>>> print(surname)
Oates
16.10 Die veränderliche Unveränderliche
16.10
Die veränderliche Unveränderliche
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir haben festgestellt, dass sich Tupel nicht verändern können! Folgender Code zeigt, dass
sie es scheinbar doch können:
>>> galileo = ([],)
>>> galileo[0].append("Sie dreht sich doch!")
>>> galileo
(['Sie dreht sich doch!'],)
Nein, keine Angst. Tupel sind unveränderlich, so wie sich die Sonne auch nur scheinbar
um die Erde dreht. Im obigen Beispiel ist das erste und einzige Objekt ein Zeiger auf eine
Liste. Die Liste wird dann mit der in-Place-Methode append verändert. Wir haben also die
Liste innerhalb des Tupels und nicht das Tupel selbst verändert. Versucht man, dem ersten
Element direkt einen neuen Wert zuzuweisen, erhält man wie erwartet einen Fehler:
>>> galileo = ([],)
>>> galileo[0] = "Sie dreht sich doch!"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
16.11
16.11.1
Sortieren von Listen
„sort” und „sorted”
Zum Sortieren von Python-Listen gibt es
zwei Möglichkeiten: zum einen die sortMethode der list-Klasse, die die Liste inplace sortiert, und zum anderen die eingebaute Funktion „sorted”, die aus einem
Iterator-Objekt eine neue sortierte Liste
baut.
Schauen wir uns zunächst einmal „sort”
an:
>>>
>>>
>>>
[7,
liste = [32,7,12,20,8]
liste.sort()
liste
8, 12, 20, 32]
Die Methode „sort” sortiert also direkt das
Objekt und liefert kein Ergebnis zurück
oder besser gesagt den speziellen Wert
„None”. Ein beliebter Fehler besteht darin,
Bild 16.3 Alles Strings
151
152
16 Listen und Tupel im Detail
dass man glaubt, dass liste.sort() die sortierte Liste zurückgibt. Im folgenden Beispiel hat
dann „liste” nur noch den Wert „None”, und die Liste ist verloren:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> liste = [32,7,12,20,8]
>>> liste = liste.sort()
>>> print(liste)
None
Die Funktion „sorted” hingegen liefert als Ergebnis die sortierte Liste zurück, und die Originalliste bleibt unverändert:
>>> liste = [32,7,12,20,8]
>>> x = sorted(liste)
>>> x
[7, 8, 12, 20, 32]
>>> liste
[32, 7, 12, 20, 8]
16.11.2
Umkehrung der Sortierreihenfolge
Um die Sortierreihenfolge umzukehren, genügt es, sowohl bei „sort” als auch bei „sorted”
den Schlüsselwortparameter „reverse” auf True zu setzen. Standardmäßig steht er auf False.
>>> liste = [32,7,12,20,8]
>>> x = sorted(liste, reverse=True)
>>> x
[32, 20, 12, 8, 7]
>>> liste.sort(reverse=True)
>>> liste
[32, 20, 12, 8, 7]
16.11.3
Eigene Sortierfunktionen
Manchmal kommt es vor, dass man eine Liste oder ein iterierbares Objekt nicht
nach der Standard-Ordnungsrelation sortieren will. Für diesen Fall übergeben wir
dem Schlüsselwortparameter „key” eine
Funktionsreferenz auf eine Funktion, die als
Argument den gleichen Typ wie die Listenelemente erwartet.
Die Arbeitsweise kann man sich mathematisch wie folgt vorstellen:
Bild 16.4 sort mit eigener Sortierabbildung
16.11 Sortieren von Listen
Die zu sortierende Liste sieht wie folgt aus:
l = [x 1 , x 2 , x 3 , . . . x n ]
Sei nun f die an „key” übergebene Funktion, dann wird die ganze Liste mittels f in eine neue
Liste abgebildet:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
[ f (x 1 ), f (x 2 ), f (x 3 ), f (x n )]
Die neue Liste wird von Python standardmäßig sortiert, aber jedes Mal, wenn zwei Werte, sagen wir beispielsweise f (x i ) und f (x j ), vertauscht werden müssen, werden auch die
entsprechenden Elemente in l vertauscht, also x i und x j .
Wir demonstrieren nun die Arbeitsweise von „key” im folgenden Beispiel. Wenn wir eine
Liste von Strings sortieren, erhalten wir keine alphabetische Sortierung, sondern eine, die
sich nach der Codierung der Zeichen richtet. Das heißt, dass jeder Großbuchstabe bzgl. der
Sortierreihenfolge kleiner ist als alle Kleinbuchstaben:
>>> l = ["yellow", "Green", "blue", "Black", "red"]
>>> sorted(l)
['Black', 'Green', 'blue', 'red', 'yellow']
Wären alle Farben in obigem Beispiel klein geschrieben, gäbe es das Problem nicht mit
„Black” und „Green”. Dies führt uns zur Lösung: Wir bilden alle Strings mittels des Parameters „key” in Kleinbuchstaben ab:
>>> l = ["yellow", "Green", "blue", "Black", "red"]
>>> sorted(l, key=str.lower)
['Black', 'blue', 'Green', 'red', 'yellow']
Bei Python ist nicht für jeden Datentyp oder Klasse eine Ordnung definiert. So beispielsweise auch nicht für die komplexen Zahlen. Versucht man, eine Liste mit komplexen Zahlen zu sortieren, erhält man einen „TypeError”:
>>> x = [ 3 + 4j, 2 - 1j, -4 -7j, 3 +2j]
>>> x.sort()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: complex() < complex()
Übergibt man jedoch die Funktion „abs” an „key”, funktioniert das Sortieren, da „abs” die
Länge eines Vektors berechnet, was einer Float-Zahl entspricht:
>>> x = [ 3 + 4j, 2 - 1j, -4 -7j, 3 +2j]
>>> x.sort(key=abs)
>>> x
[(2-1j), (3+2j), (3+4j), (-4-7j)]
Im Folgenden wollen wir Listen mit Tupel oder anderen Listen als Elemente anhand einer
bestimmten Komponente dieser Listen- oder Tupelelemente sortieren. Dazu müssen wir
eine Funktion übergeben, die ein Tupel oder eine Liste als Argument hat und eine Komponente zurückliefert.
153
154
16 Listen und Tupel im Detail
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Sehr elegant kann man diese Aufgabe mit der lambda-Notation lösen, die wir aber erst in
Kapitel lambda, map, filter und reduce, Seite 307 behandeln:
>>> phone = [("John", "Miller", "4567"), ("Tina", "Baker", "4289"), ("
Randy", "Steiner", "4103")]
>>> sorted(phone, key=lambda x:x[0])
[('John', 'Miller', '4567'), ('Randy', 'Steiner', '4103'), ('Tina', '
Baker', '4289')]
>>> sorted(phone, key=lambda x:x[1])
[('Tina', 'Baker', '4289'), ('John', 'Miller', '4567'), ('Randy', '
Steiner', '4103')]
>>> sorted(phone, key=lambda x:x[2])
[('Randy', 'Steiner', '4103'), ('Tina', 'Baker', '4289'), ('John', '
Miller', '4567')]
Will man die Aufgabe ohne Hilfe der lambda-Notation lösen, empfiehlt sich der Einsatz der
Funktion „itemgetter” aus dem Modul „operator”.
Die Funktion „itemgetter” wird mit einem Index „index” aufgerufen und liefert dann eine
Funktion zurück, mit der man dann von einem beliebigen iterierbaren Objekt den Wert des
Elements an der Position „index” zurückliefert.
>>> from operator import itemgetter
>>> colours = ["red", "green", "blue", "yellow", "pink", "brown"]
>>> numbers = [10,15,21,33,5,8]
>>> get_index1 = itemgetter(1)
>>> get_index1(colours)
'green'
>>> get_index1(numbers)
15
Natürlich bedarf es nicht des Umwegs, dass man sich einen extra Funktionsnamen für den
i-ten Itemzugriff anlegt. Man kann dies auch direkt tun:
>>> from operator import itemgetter
>>> colours = ["red", "green", "blue", "yellow", "pink", "brown"]
>>> numbers = [10,15,21,33,5,8]
>>> itemgetter(1)(colours)
'green'
>>> itemgetter(1)(numbers)
15
>>> itemgetter(4)(numbers)
5
>>> itemgetter(4)(colours)
'pink'
16.12 Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Nun können wir unser ursprüngliches Sortierproblem, was wir bereits in lambda-Notation
gelöst hatten, auch mit der itemgetter-Funktion lösen:
>>> from operator import itemgetter
>>> phone = [("John", "Miller", "4567"), ("Tina", "Baker", "4289"), ("
Randy", "Steiner", "4103")]
>>> # nach dem 0-ten Index sortieren:
...
>>> sorted(phone, key=itemgetter(0))
[('John', 'Miller', '4567'), ('Randy', 'Steiner', '4103'), ('Tina', '
Baker', '4289')]
>>> # nach dem 1-ten Index sortieren:
...
>>> sorted(phone, key=itemgetter(1))
[('Tina', 'Baker', '4289'), ('John', 'Miller', '4567'), ('Randy', '
Steiner', '4103')]
>>> # nach dem 2-ten Index sortieren:
...
>>> sorted(phone, key=itemgetter(2))
[('Randy', 'Steiner', '4103'), ('Tina', 'Baker', '4289'), ('John', '
Miller', '4567')]
16.12
Aufgaben
1. Aufgabe:
Schreiben Sie eine Funktion, die die Reihenfolge einer Liste umkehrt. Verzichten Sie
aber auf die Benutzung der Listen-Methode „reverse” und versuchen Sie stattdessen,
nur die Methoden „pop” und „append” zu benutzen.
>>> liste = ["a","b","c","d"]
>>> rev(liste)
["d","c","b","a"]
Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 487
2. Aufgabe:
Schreiben Sie mithilfe der Methoden extend() und append() eine Funktion flatten(),
die eine verschachtelte Liste (oder ein Tupel) in eine flache Liste überführt. type() wird
auch benötigt.
>>> flatten([(1,2), "Python", ["a",[1,7]], 1, 1.3])
[1, 2, 'Python', 'a', 1,7,1, 1.3]
Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 487
155
156
16 Listen und Tupel im Detail
3. Aufgabe:
Sortieren Sie eine Liste von Zahlen in alphabetischer Reihenfolge, d.h. das gilt:
1 < 10 < 111 < 19 < 2
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 488
4. Aufgabe:
Schreiben Sie eine Funktion, die die Buchstabenhäufigkeit eines Strings bestimmt. Die
Häufigkeiten sollen sortiert ausgegeben werden. Dabei sollen keine Sonderzeichen
und Ziffern berücksichtigt werden.
Beispiel:
s = "Monty Python"
x = letter_frequency(s)
print x
Ausgegeben wird:
[('o', 2), ('n', 2), ('t', 2), ('y', 2), ('h', 1), ('m', 1), ('p',
1)]
Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 488
5. Aufgabe:
Gegeben ist eine Liste von Verkäufern mit ihren getätigten Verkäufen für ein bestimmtes Produkt. Die Liste besteht aus 4 Tupel der Art (Vorname, Nachname, Anzahl, Einzelpreis):
[ ('John', 'Miller', 46, 18.67),
('Randy', 'Steiner', 48, 27.99),
('Tina', 'Baker', 53, 27.23),
('Andrea', 'Baker', 40, 31.75),
('Eve', 'Turner', 44, 18.99),
('Henry', 'James', 50, 23.56)]
Sortieren Sie die Liste zuerst nach den Verkaufserlösen, also dem Produkt aus der
Anzahl und dem Einzelpreis.
Dann geben Sie die Liste nochmals nach den Nachnamen sortiert aus. Bei Gleichheit
des Nachnamens sortieren Sie anhand des Vornamens weiter.
Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 488
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
17
17.1
Modularisierung
Module
Modulare Programmierung ist eine Software-Designtechnik, die auf dem allgemeinen Prinzip des modularen Designs beruht. Modulares Design ist ein Ansatz, der sich
im Ingenieurwesen schon lange vor den ersten Computern als unausweichlich und unentbehrlich herausgestellt
hat. Unter modularem Design versteht man, dass man
ein komplexes System in kleinere selbständige Einheiten
oder Komponenten zerlegt. Diese Komponenten bezeichnet man üblicherweise als Module. Ein Modul kann unabhängig vom Gesamtsystem erzeugt und separat getestet
werden. In den meisten Fällen kann man ein Modul auch
in anderen Systemen verwenden.
Bild 17.1 Dateien
Heutzutage gibt es kaum ein Produkt, das nicht auf Modularisierung beruht, so wie Autos,
Mobiltelefone und so weiter. Computer gehören zu den Produkten, die bis zum Äußersten
modularisiert sind. Das, was für die Hardware ein Muss ist, stellt auch für die Software, die
auf ihr läuft, eine unvermeidliche Notwendigkeit dar.
Wenn man Programme schreiben will, die lesbar, zuverlässig und ohne hohen Aufwand
wartbar sind, geht es nicht ohne modulares Software-Design, insbesondere bei größeren
Software-Projekten. Es gibt verschiedene Konzepte, um Programme modular zu gestalten.
Der Ansatz der modularen Programmierung besteht darin, Programme systematisch in logische Teilblöcke, d.h. Module, aufzuspalten. Die Aufteilung eines Quelltexts in einzelne
Teile (Module) bezeichnet man als Modularisierung.
In Python unterscheiden wir zwei Arten von Modulen:
■
Bibliotheken (Libraries)
Stellen Datentypen oder Funktionen für alle Python-Programme bereit.
Es gibt:
– die umfangreiche Standardbibliothek
– eigene Module
– Module von Drittanbietern
■
lokale Module nur für ein Programm verfügbar.
158
17 Modularisierung
Ein Modul, manchmal auch als Bibliothek bezeichnet, egal ob aus der Standardbibliothek
oder ein eigenes, wird mit der import-Anweisung eingebunden.
Mit der folgenden Anweisung wird beispielsweise das math-Modul aus der Standardbibliothek importiert:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
import math
Es stellt, wie der Name vermuten lässt, mathematische Funktionen und Konstanten zur
Verfügung, so zum Beispiel die Konstante π (math.pi), die Sinus-Funktion (math.sin()) und
die Cosinus-Funktion (math.cos())1 :
>>> math.pi
3.141592653589793
>>> math.sin(math.pi/2)
1.0
>>> math.cos(math.pi/2)
6.123031769111886e-17
>>> math.cos(math.pi)
-1.0
Nach dem Schlüsselwort import können auch mehrere durch Komma getrennte Modulnamen folgen:
import math, random
import-Anweisungen können an jeder Stelle des Quellcodes stehen, aber man sollte sie
zugunsten der Übersichtlichkeit an den Anfang stellen.
17.1.1
Namensräume von Modulen
Wird eine Bibliothek wie z.B.
import math
importiert, dann stehen die Namen der Bibliothek in einem eigenen Namensraum zur Verfügung. Auf die sin()-Funktion des math-Moduls kann man zunächst nur über den vollen
Namen („fully qualified”) zugreifen, d.h.
>>> math.sin(3.1415)
9.265358966049024e-05
sin() ohne Präfix math ist nicht bekannt und führt zu einer entsprechenden Fehlermeldung:
>>> sin(3.1415)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'sin' is not defined
1
6.123031769111886e-17 ist natürlich „nahezu” Null, wie man es erwarten würde. Bei Rechnungen mit FloatWerten kommt es wie bei allen Programmiersprachen zu numerischen Ungenauigkeiten!
17.1 Module
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Möchte man gerne „bequem” auf Funktionen wie z.B. die Sinus-Funktion zugreifen können, also ohne das Präfix math., so kann man die entsprechenden Funktionen direkt importieren:
>>> from math import sin, pi
>>> print(pi)
3.141592653589793
>>> sin(pi/2)
1.0
>>>
Die anderen Methoden der Bibliothek stehen dann nicht zur Verfügung – weder mit vollem
Namen noch direkt. Man kann im obigen Fall nur auf sin und pi zugreifen.
Man kann auch eine Bibliothek komplett in den globalen Namensraum einbinden. Dabei werden dann gegebenenfalls bereits vorhandene gleichlautende Namen überschrieben, wie dies im folgenden Beispiel dargestellt wird:
>>> pi = 57.898
>>> print(pi)
57.898
>>> from math import *
>>> print(pi)
3.141592653589793
17.1.2
Namensräume umbenennen
Beim Import einer Bibliothek kann man auch einen neuen Namen für den Namensraum
wählen. Im Folgenden importieren wir math als m. Dies führt bei der Benutzung des mathModuls zu einer deutlichen Schreiberleichterung, ohne dass die Vorteile eines Namensraums aufgegeben werden:
>>> import math as m
>>> m.pi
3.141592653589793
>>> m.sin(m.pi)
17.1.3
Modularten
Es gibt verschiedene Modularten:
■
■
■
in Python geschriebene Modulendung: .py oder .pyc
Dynamisch geladene C-Module
Endung: .dll, .pyd, .so, .sl, usw.
C-Module, die mit dem Interpreter gelinkt sind
Um eine Liste dieser Module zu erhalten:
159
160
17 Modularisierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> import sys
>>> print(sys.builtin_module_names)
('__main__', '_ast', '_bisect', '_codecs', '_collections', '_datetime
', '_elementtree', '_functools', '_heapq', '_io', '_locale', '
_pickle', '_posixsubprocess', '_random', '_socket', '_sre', '
_string', '_struct', '_symtable', '_thread', '_warnings', '
_weakref', 'array', 'atexit', 'binascii', 'builtins', 'errno', '
fcntl', 'gc', 'grp', 'imp', 'itertools', 'marshal', 'math', '
operator', 'posix', 'pwd', 'pyexpat', 'select', 'signal', 'spwd',
'sys', 'syslog', 'time', 'unicodedata', 'xxsubtype', 'zipimport',
'zlib')
17.1.4
Suchpfad für Module
Wenn man ein Modul importiert, z.B. abc, sucht der Interpreter nach abc.py in der folgenden Reihenfolge:
■
im aktuellen Verzeichnis
■
PYTHONPATH
■
Falls PYTHONPATH nicht gesetzt ist, wird installationsabhängig im Default-Pfad gesucht, also unter Linux/Unix z.B. in /usr/lib/python3.5.
sys.path enthält die Verzeichnisse, in denen Module gesucht werden:
>>> import sys
>>> for dir in sys.path:
...
print(dir)
...
/usr/lib/python35.zip
/usr/lib/python3.5
/usr/lib/python3.5/plat-x86_64-linux-gnu
/usr/lib/python3.5/lib-dynload
/usr/local/lib/python3.5/dist-packages
/usr/lib/python3/dist-packages
>>>
In der folgenden interaktiven Sitzung sehen wir, wie man herausfinden kann, wo sich ein
zuvor geladenes Modul befindet:
>>> import numpy
>>> numpy.__file__
'/usr/lib/python3/dist-packages/numpy/__init__.py'
>>> import math
>>> math.__file__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'math' has no attribute '__file__'
17.1 Module
Wie wir oben sehen, ist das __file__-Attribut nicht vorhanden, wenn es sich bei dem Modul um ein C-Modul handelt, welches statisch an den Interpreter gelinkt ist.
17.1.5
Inhalt eines Modules
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Mit der built-in-Funktion dir() kann man sich die in einem Modul definierten Namen ausgeben lassen:
>>> import math
>>> dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos
', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', '
copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', '
expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', '
gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', '
isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf
', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', '
tanh', 'trunc']
Ohne Argumente liefert dir() die definierten Namen des Namensraums, in dem man sich
befindet:
$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>> import math
>>> a = 42
>>> b = 4.5
>>> lst = [34, "Salu"]
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '
__spec__', 'a', 'b', 'lst', 'math']
17.1.6
Eigene Module
Eigene Module zu schreiben, ist in Python extrem einfach. Viele tun es, ohne es zu wissen.
Jedes Python-Programm ist automatisch auch ein Modul.
Die beiden folgenden Funktionen fib(), die den n-ten Fibonacci-Wert zurückliefert, und
die Funktion fiblist() werden in einer Datei fibonacci.py gespeichert:
def fib(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
161
162
17 Modularisierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def fiblist(n):
fib = [0,1]
for i in range(1,n):
fib += [fib[-1]+fib[-2]]
return fib
Von einem anderen Programm oder von der interaktiven Shell kann man nun, falls fibonacci.py innerhalb des Suchpfads zu finden ist, die Datei mit den beiden FibonacciFunktionen als Modul aufrufen:
>>> import fibonacci
>>> fibonacci.fib(10)
55
>>> fibonacci.fiblist(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>> fibonacci.__name__
'fibonacci'
>>>
17.1.7
Dokumentation für eigene Module
Auch wenn es so ist, wie wir im vorigen Abschnitt gesagt haben, dass jedes PythonProgramm automatisch auch ein Modul ist, so sollte jedes Modul dennoch über ausreichend Kommentare verfügen. Das pydoc-Modul erzeugt automatisch eine Dokumentation
für jedes Modul.
Rufen wir beispielsweise help auf unser fibonacci-Modul auf, erhalten wir folgende Ausgabe:
Help on module fibonacci:
NAME
fibonacci
FUNCTIONS
fib(n)
fiblist(n)
FILE
/home/data/bodenseo/python/fibonacci.py
Wünschenswert wären jedoch noch allgemeine Informationen zum fibonacci-Modul und
zu den einzelnen Methoden. Eine allgemeine Beschreibung des Moduls kann man in einem Docstring zu Beginn einer Moduldatei verfassen. Die Funktionen dokumentiert man
wie üblich mit einem Docstring unterhalb der ersten Funktionszeile:
17.2 Pakete
""" Modul mit wichtigen Funktionen zur Fibonacci-Folge """
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def fib(n):
""" Iterative Fibonacci-Funktion """
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
def fiblist(n):
""" produziert Liste der Fibo-Zahlen """
fib = [0,1]
for i in range(1,n):
fib += [fib[-1]+fib[-2]]
return fib
Die help-Ausgabe sieht nun zufriedenstellend aus:
Help on module fibonacci:
NAME
fibonacci - Modul mit wichtigen Funktionen zur Fibonacci-Folge
FUNCTIONS
fib(n)
Iterative Fibonacci-Funktion
fiblist(n)
produziert Liste der Fibo-Zahlen
FILE
/home/data/bodenseo/python/fibonacci.py
17.2
Pakete
Beim Verfassen sprachlicher Dokumente steht
natürlich der Inhalt selbst im Vordergrund,
aber man wird diesen Inhalt wohl kaum an
seine Leserinnen und Leser bringen können,
wenn er nicht organisiert ist. Man gliedert in
Bücher, Kapitel, Unterkapitel, Abschnitte und
so weiter.
Ähnliches gilt natürlich auch für PythonProgramme. Werden sie nicht ordentlich gegliedert, d.h. modularisiert, wird es schwer, sie
Bild 17.2 Beispielpaket in der Übersicht
163
164
17 Modularisierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
zu verstehen und zu warten. Im vorigen Kapitel hatten wir Module kennengelernt, die sich
hervorragend eignen, Programme zu strukturieren bzw. zu modularisieren. Was aber, wenn
die Zahl unserer Module wächst? Wenn wir die Übersicht über unsere Module verlieren
könnten? Für diesen Fall hat Python das Paketkonzept bereitgestellt. Wir können mehrere
oder beliebig viele Module zu einem Paket „schnüren”. Der dazu erforderliche Mechanismus ist in Python wiederum denkbar einfach gelöst.
17.2.1
Einfaches Paket erzeugen
Um ein Paket in Python zu erstellen, sind nur zwei Dinge zu tun: Man erzeugt einen Unterordner in einem Verzeichnis, in dem Python Module erwartet bzw. sucht. In diesem neu
erzeugten Ordner muss man nun eine Datei mit dem Namen __init__.py anlegen. Die
Datei kann leer sein oder Initialisierungscode enthalten, der beim Einbinden des Pakets
einmalig ausgeführt wird. Pakete können außer Modulen auch Pakete enthalten.
Im Folgenden zeigen wir nun anhand eines einfachen Beispiels, wie man ein Paket erzeugt. Unser Paket soll simple_package heißen. Dazu erzeugen wir einen Ordner2 mit
dem Namen simple_package. In diesem Ordner legen wir eine leere Datei mit dem Namen
__init__.py an3 . Außerdem legen wir noch zwei einfache Module in diesem Verzeichnis
an: ein Modul namens „a.py” mit folgendem Inhalt
def f1():
print("Hello, f1 from module 'a' calling")
sowie ein weiteres Modul mit dem Name „b.py” und diesem Inhalt:
def foo():
print("Hello, foo from module 'b' calling")
Nun sind wir in der Lage, unser Paket zu benutzen:
>>> from simple_package import a,b
>>> a.f1()
Hello, f1 from module 'a' calling
>>> b.foo()
Hello, foo from module 'b' calling
Versucht man, das Paket simple_package direkt, also mittels „import simple_package”
zu importieren, erhält man eine Fehlermeldung. Dies liegt daran, dass die Module in einem
Paket nicht automatisch importiert werden:
>>> import simple_package
>>> simple_package.a.f1()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'a'
2
3
In Linux oder Unix können wir dies mit dem Befehl „mkdir simple_package” tun.
In Linux kann man dies am einfachsten mit einem touch-Kommando machen: „touch __init.py__”.
17.2 Pakete
Jetzt kann man das Initialisierungsverhalten von __init__.py ideal einsetzen. Wir fügen in
diese bisher leere Datei die Zeile „from simple_package import a, b’’ ein und können
nun obigen Versuch wiederholen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> import simple_package
>>> simple_package.a.f1()
Hello, f1 from module 'a' calling
Wie wir gesehen haben, erhalten wir keine Fehlermeldung und können auch auf die Module wie gewünscht zugreifen.
17.2.2
Komplexeres Paket
Im folgenden Beispiel zeigen wir den Aufbau eines umfangreicheren Pakets. Es handelt sich
um ein hypothetisches sound-Modul.4
sound
|-- effects
|
|-- echo.py
|
|-- __init__.py
|
|-- reverse.py
|
`-- surround.py
|-- filters
|
|-- equalizer.py
|
|-- __init__.py
|
|-- karaoke.py
|
`-- vocoder.py
|-- formats
|
|-- aiffread.py
|
|-- aiffwrite.py
|
|-- auread.py
|
|-- auwrite.py
|
|-- __init__.py
|
|-- wavread.py
|
`-- wavwrite.py
`-- __init__.py
Die gesamte Beispielstruktur mit den Dateien kann als zip-File von unserer Webseite heruntergeladen werden.5
Wenn wir das Paket „sound” mittels import sound importieren, wird nur das Paket sound,
aber nicht die Unterpakete effects, filters und formats importiert, wie man im Folgenden sieht:
4
5
https://docs.python.org/3/tutorial/modules.html
http://www.python-kurs.eu/buch/
165
166
17 Modularisierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> import sound
sound package is getting imported!
>>> sound
<module 'sound' from '/home/bernd/packages/sound/__init__.py'>
>>> sound.effects
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'sound' has no attribute 'effects'
Dies liegt daran, dass die __init__.py-Datei außer Strings für die Help-Funktionen und
einem print keinen Code zum Nachladen von Unterpaketen enthält:
"""An empty sound package
This is the sound package, providing hardly anything!"""
print("sound package is getting imported!")
Möchte man auch das Paket effects nutzen, so muss man es explizit mittels
import sound.effects importieren:
>>> import sound.effects
effects package is getting imported!
>>> sound.effects
<module 'sound.effects' from '/home/bernd/packages/sound/effects/
__init__.py'>
Man kann dies auch automatisch beim Import des sound-Moduls erledigen lassen, indem man die Codezeile import sound.effects in der Datei __init__.py im Verzeichnis
sound aufnimmt. Die Datei sieht dann wie folgt aus:
"""An empty sound package
This is the sound package, providing hardly anything!"""
import sound.effects
print("sound package is getting imported!")
Importieren wir jetzt das Paket sound in der interaktiven Python-Shell, sehen wir, dass nun
auch das Unterpaket effects automatisch geladen wird:
>>> import sound
effects package is getting imported!
sound package is getting imported!
17.2 Pakete
Statt mit einem absoluten Pfad hätte man das effects-Paket auch relativ zum sound-Paket
importieren können:
"""An empty sound package
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
This is the sound package, providing hardly anything!"""
from . import effects
print("sound package is getting imported!")
Es ist auch möglich, automatisch das Unterpaket formats nachzuladen, wenn wir effects
importieren. Wir können auch dies mit einem relativen Import erledigen, den wir in die
__init__.py-Datei des Verzeichnisses effects aufnehmen:
from .. import formats
Beim Import von sound werden dann die Unterpaket formats und effects automatisch
geladen:
>>> import sound
formats package is getting imported!
effects package is getting imported!
sound package is getting imported!
Als Letztes wollen wir nun zeigen, wie wir nur das Modul karaoke aus dem Paket filters
bei dem Import des Moduls effects laden können. Dazu fügen wir noch die Zeile
from ..filters import karaoke in die __init__.py-Datei des Verzeichnisses effects
ein. Die Datei sieht dann wie folgt aus:
"""An empty effects package
This is the effects package, providing hardly anything!"""
from .. import formats
from .. filters import karaoke
print("effects package is getting imported!")
Wenn wir nun sound importieren, erhalten wir folgende Ausgabe:
>>> import sound
formats package is getting imported!
filters package is getting imported!
Module karaoke.py has been loaded!
effects package is getting imported!
sound package is getting imported!
Wir können nun auf die Funktionen von karaoke zugreifen:
>>> sound.filters.karaoke.func1()
Funktion func1 has been called!
>>>
167
168
17 Modularisierung
17.2.3
Komplettes Paket importieren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
In diesem Unterkapitel benutzen wir wieder das sound-Beispiel des vorigen Kapitels.
Wir fügen noch ein Modul foobar in unserem sound-Verzeichnis hinzu, d.h. eine Datei
foobar.py.6
Wir werden nun demonstrieren, was passiert, wenn wir das sound-Paket mit * importieren. Man könnte sich vorstellen, dass Python automatisch alle Untermodule des Paketes
importiert. Dies ist allerdings nur auf den ersten Blick wünschenswert, da es möglicherweise lange dauern könnte und vor allen Dingen, weil es zu ungewollten Seiteneffekten
führen könnte.
>>> from sound import *
sound package is getting imported!
>>>
Wir erhalten die „beruhigende” Nachricht, dass das sound-Paket importiert worden ist.
Schauen wir allerdings mit der dir-Funktion nach, was importiert worden ist, sehen wir,
dass weder das Modul foobar nach die Unterpakete effect, filters und formats importiert worden sind:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__
', '__package__', '__spec__']
>>>
Das automatische Nachladen kann erreicht werden, wenn der Autor eines Pakets einen Paketindex anlegt. Einen Paketindex legt man an, indem man in der __init__-Datei eine Liste mit dem Namen __all__ definiert. Die import-Anweisung folgt folgender Konvention:
Die Liste __all__ wird als Liste der Modulnamen betrachtet, die importiert werden sollen, wenn ein Sternchen im Import verwendet worden ist, also from package import *.
Man muss dann allerdings dafür Sorge tragen, dass diese Liste sich auf dem neuesten Stand
befindet, wenn eine neue Version des Pakets veröffentlicht wird. Paket-Autoren brauchen
aber keinesfalls eine solche __all__-Liste bereitzustellen, wenn sie keinen Sinn in der Verwendung des Sternchens für ihr Paket sehen.
Die Datei sound/effects/__init__.py könnte beispielsweise folgende Code-Zeile enthalten:
__all__ = ["formats", "filters", "effects", "foobar"]
Importieren wir nun sound mittels *, erhalten wir folgende Ausgabe:
>>> from sound import *
formats package is getting imported!
filters package is getting imported!
effects package is getting imported!
The module foobar is getting imported
>>>
6
Das gesamte Paket kann als Datei sound4bar.tbz2 heruntergeladen werden.
17.2 Pakete
Obwohl nach dem Ausdruck bereits alles klar sein sollte, können wir es dennoch wieder
mit der Funktion dir testen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '
__spec__', 'effects', 'filters', 'foobar', 'formats']
>>>
Als nächste Frage stellt sich, was passiert, wenn wir in einem Unterpaket den SternchenOperator nutzen:
>>> from sound.effects import *
sound package is getting imported!
effects package is getting imported!
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '
__spec__']
>>>
Wie erwartet wurden die Module innerhalb von effects nicht automatisch importiert.
Deshalb fügen wir die folgende __all__-Liste der __init__-Datei des effects-Pakets
hinzu:
__all__ = ["echo", "surround", "reverse"]
Nun erhalten wir das beabsichtigte Ergebnis:
>>> from sound.effects import *
sound package is getting imported!
effects package is getting imported!
Module echo.py has been loaded!
Module surround.py has been loaded!
Module reverse.py has been loaded!
>>>
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '
__spec__', 'echo', 'reverse', 'surround']
>>>
169
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
18
18.1
Globale und lokale
Variablen
Einführung
Python behandelt globale und lokale Variablen auf
eigenwillige Art. Während in vielen anderen Programmiersprachen Variablen automatisch als global gelten, wenn man sie nicht explizit als lokal deklariert hat, ist dies in Python genau anders
herum. Die zugrunde liegende Idee besteht darin,
dass die Benutzung von globalen Variablen generell als schlechter Programmierstil betrachtet wird,
weil dadurch viele Fehler und Nebeneffekte auftreten können. In den meisten Fällen, in denen man
versucht ist, eine globale Variable zu verwenden,
kann man den gewünschten Effekt besser mittels ei- Bild 18.1 Mond und Erde
nes Funktionsparameters realisieren oder durch die
Rückgabe eines Werts mit der return-Anweisung. Wie auch in vielen anderen Fällen wird
hier durch das Design von Python ein guter Programmierstil gewissermaßen erzwungen.
Das bedeutet, dass jede Variable, die man innerhalb einer Funktion definiert, automatisch
einen lokalen Gültigkeitsbereich hat. Und das heißt wiederum, dass egal was man mit dieser Variable innerhalb der Funktion anstellt, dies keinen Einfluss auf andere Variablen außerhalb der Funktion hat, auch wenn diese den gleichen Namen haben. Der Funktionsrumpf ist also der Gültigkeitsbereich einer solchen Variablen.
Um keine Missverständnisse aufkommen zu lassen: Variablen müssen nicht deklariert werden, wie dies in anderen Sprachen wie C und Java üblich ist. Variablen werden in Python
implizit deklariert, wenn man sie definiert, d.h. ihnen einen Wert zuweist. Eine Variable
erhält automatisch den richtigen Datentyp.
172
18 Globale und lokale Variablen
18.2
Globale und lokale Variablen
in Funktionen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im folgenden Beispiel zeigen wir, wie globale Variablen innerhalb des Funktionsrumpfs
einer Funktion benutzt werden können, allerdings nur „lesend”, also ohne den Wert zu ändern:
def f():
print(s)
s = "I love Paris in the summer!"
f()
Die Variable s wird definiert, indem ihr die Zeichenkette „I love Paris in the summer!” zugeordnet wird. Diese Definition erfolgt vor dem Funktionsaufruf f(). Der Funktionsrumpf
von f() besteht nur aus der „print(s)”-Anweisung. Weil es keine lokale Variable s gibt, d.h.
keine Zuweisung an s innerhalb des Funktionsrumpfs von f, wird der Wert der globalen
Variable s benutzt. Dieser Wert kann natürlich nicht verändert werden, wie wir in diesem
Kapitel weiter unten sehen werden. Es wird also der String „I love Paris in the summer!”
ausgegeben.
Es stellt sich aber die Frage, was passiert, wenn wir den Wert von s innerhalb der Funktion
von f() verändern. Wird dies eine Auswirkung auf die globale Variable s haben? Wir testen
dies im folgenden kleinen Skript:
def f():
s = "I love London!"
print(s)
s = "I love Paris!"
f()
print(s)
Starten wir das Skript, erhalten wir folgende Ausgabe:
$ python3 global_lokal2.py
I love London!
I love Paris!
Wie sieht es aber aus, wenn wir das erste Beispiel mit dem zweiten Beispiel kombinieren,
d.h. wenn wir also zuerst auf s mittels print zugreifen in der Hoffnung, den globalen Wert
zu erhalten, und dann s einen neuen Wert zuweisen? Indem wir s einen Wert zuweisen
könnten, machten wir s zu einer lokalen Variable. Dadurch gäbe es s innerhalb des Funktionsrumpfs sowohl als globale als auch als lokale Variable. Python lässt diese Mehrdeutigkeit nicht zu, und es kommt zu einer Fehlermeldung, wie wir im folgenden, entsprechend
angepassten Beispiel sehen können:
def f():
print(s)
s = "I love London!"
print(s)
18.2 Globale und lokale Variablen in Funktionen
s = "I love Paris!"
f()
print(s)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Rufen wir das Programm auf, erhalten wir folgende Fehlermeldung:
$ python3 global_lokal3.py
Traceback (most recent call last):
File "global_lokal3.py", line 7, in <module>
f()
File "global_lokal3.py", line 2, in f
print(s)
UnboundLocalError: local variable 's' referenced before assignment
Eine Variable kann nicht sowohl lokal als auch global innerhalb des gleichen Blocks (hier
der Funktionsrumpf) sein. Deshalb betrachtet Python s als eine lokale Variable innerhalb
des Funktionsrumpfs. Da aber auf diese lokale Variable zugegriffen wird, bevor sie definiert
worden ist (sie also zum Zeitpunkt der print-Anweisung noch keinen Wert erhalten hat),
erfolgt die Fehlermeldung.
Man kann jedoch auf globale Variablen „schreibend” innerhalb einer Funktion zugreifen.
Dazu muss man sie jedoch explizit mittels des Schlüsselworts global als global deklarieren.
Wir demonstrieren dies im folgenden Beispiel:
def f():
global s
print(s)
s = "Zur Zeit nicht, aber Berlin ist auch toll!"
print(s)
s = "Gibt es einen Kurs in Paris?"
f()
print(s)
Als Ausgabe erhalten wir:
$ python3 global_lokal4.py
Gibt es einen Kurs in Paris?
Zur Zeit nicht, aber Berlin ist auch toll!
Zur Zeit nicht, aber Berlin ist auch toll!
Im folgenden Beispiel wollen wir nun noch zeigen, dass man auf lokale Funktionsvariablen
von außerhalb nicht zugreifen kann:
def f():
s = "I am globally not known"
print(s)
f()
print(s)
173
174
18 Globale und lokale Variablen
Wenn man dieses Skript startet, erhält man folgende Ausgabe mit Fehlermeldung:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
$ python3 global_lokal5.py
I am globally not known
Traceback (most recent call last):
File "global_lokal5.py", line 6, in <module>
print(s)
NameError: name 's' is not defined
Das folgende Beispiel zeigt eine wilde Kombination von lokalen und globalen Variablen
und Funktionsparametern, um die obigen Sachverhalte nochmals per Beispiel zu vertiefen:
def foo(x, y):
global a
a = 42
x,y = y,x
b = 33
b = 17
c = 100
print(a,b,x,y)
a,b,x,y = 1,15,3,4
foo(17,4)
print(a,b,x,y
Die Ergebnisse sollten mit den bisherigen Erläuterungen keine Überraschungen beinhalten:
$ python3 global_lokal6.py
42 17 4 17
42 15 3 4
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
19
19.1
Alles über Strings . . .
... fast alles
In den vorigen Kapiteln haben wir bereits Strings kennengelernt und einiges über sie erfahren. Nun möchten wir Ihnen weitere Details über Strings bieten, aber
nicht alle, wie es der Titel andeutet. Hier geht es nur
um Textstrings in Python und nicht um die aus der
Physik, der Musik oder die Bademode.
Die ersten Computer wurden entwickelt, um numerische Probleme zu lösen. Damals konnten sich viele noch nicht einmal vorstellen, dass man mit einem Computer nicht nur rechnen, sondern auch komplexe Textverarbeitung betreiben könnte. Schließlich
stammt das Wort Computer auch vom lateinischen
„computare”, was im Deutschen „berechnen” bedeutet. Wenn man nur allein an Suchmaschinen denkt,
sieht man, dass heutzutage ein Großteil der Probleme
auf Texten, also Strings basiert.
Bild 19.1 Alles Strings
So ist es nicht verwunderlich, dass Python insbesondere für den Datentyp string mächtige
Werkzeuge bereitstellt, damit man mit Programmen automatisch Textdateien bearbeiten
kann.
Einen guten Überblick über die Klasse „str” können wir uns mit der help-Funktion verschaffen, also help(str). Mit dir(str) erhalten wir die Namen der vorhandenen Methoden
und Attribute. Insgesamt immerhin 74:
>>> dir(str)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '
__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__
', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__',
'__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '
__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '
__rmul__', '__setattr__', '__sizeof__', '__str__', '
__subclasshook__', 'capitalize', 'center', 'count', 'encode', '
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
176
19 Alles über Strings . . .
endswith', 'expandtabs', 'find', 'format', 'format_map', 'index',
'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', '
islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', '
isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', '
partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', '
rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', '
swapcase', 'title', 'translate', 'upper', 'zfill']
>>> len(dir(str))
74
19.2
Aufspalten von Zeichenketten
Nehmen wir an, wir haben einen String,
der eine Zeile, einen Satz oder vielleicht
ein ganzes Buch enthält. Um einen solchen String zu bearbeiten, ist es sehr häufig nötig, ihn in kleinere Einheiten zu zerlegen, z.B. in einzelne Wörter. Zerlegt man
einen solchen String überall dort, wo Leerzeichen, Tabs oder ganz allgemeine, nicht
druckbare Zeichen stehen, hat man eine Zerlegung in Wörter, wenn man davon absieht, dass an manchen Wörtern
noch Satzzeichen wie Kommas oder Punkte „kleben”. Eine solche Funktionalität gibt
es in fast allen modernen Programmiersprachen. Python bietet für diesen Zweck
mehrere String-Methoden.
■
split([sep[, maxsplit]])
■
rsplit([sep[, maxsplit]])
■
splitlines([keepends])
■
partition(sep)
■
rpartition(sep)
19.2.1
Bild 19.2 Zerlegung von Strings
split
Die Methode split kann ohne Parameter aufgerufen werden. Wir demonstrieren die Arbeitsweise von split mit einer bissigen Definition von Ambrose Bierce1 :
>>> mammon = "The god of the world's leading religion. The chief
temple is in the holy city of New York."
1
aus „The Devil’s Dictionary” (Des Teufels Wörterbuch) von Ambrose Bierce
19.2 Aufspalten von Zeichenketten
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> mammon.split()
['The', 'god', 'of', 'the', "world's", 'leading', 'religion.', 'The',
'chief', 'temple', 'is', 'in', 'the', 'holy', 'city', 'of', 'New',
'York.']
Man kann erkennen, dass split den String in einzelne Komponenten aufgespalten und dafür Leerzeichen als Trenner verwendet hat. Was man nicht erkennen kann, ist eine andere
Eigenschaft des Default-Verhaltens von split. Es werden auch Teilstrings, die nur aus Leerzeichen, Zeilenvorschub (’\n’), Tabs (’\t’) oder anderen, sogenannten Whitespaces (also
nicht druckbaren Zeichen) bestehen, zu einer Trennstelle zusammengefasst:
>>> mammon = "The god \t \t of the world's \t leading religion. \n\r
The chief temple is in the holy city of New York."
>>> mammon.split()
['The', 'god', 'of', 'the', "world's", 'leading', 'religion.', 'The',
'chief', 'temple', 'is', 'in', 'the', 'holy', 'city', 'of', 'New',
'York.']
Die split-Methode verfügt noch über einen optionalen Parameter „sep”2 . Mit diesem Parameter können wir das Standardverhalten von split ändern, d.h. ein neues Trennzeichen
definieren.
Folgende Adressen mit Telefonnummer3 könnten aus einer Excel-Datei stammen und sollen nun in ihre einzelnen Komponenten zerlegt werden.
Frank;Meyer;Radolfzell;07732/43452
Peter;Rabe;Konstanz;07531/70021
Ottmar;Huber;Rosenheim;08031/7877-0
Anna;Rabe;Radolfzell;07732/2343
Oskar;Lindner;Konstanz;07531/890
Anna;List;München;089/3434544
Anna;list;München;089/3434544
Franziska;Huber;Rosenheim;08031/787878
Sarah;Rabe;Konstanz;07531/343454
Die Daten sind in der Datei „adressen.txt” gespeichert. Das folgende Python-Programm4
liest diese Datei Zeile für Zeile ein, spaltet die Zeilen an den Strichpunkten und gibt die
Zeilen in der Form „Nachname, Vorname, Ort, Telefonnummer” wieder aus:
fh = open("addresses.txt", encoding="iso-8859-1")
for address in fh:
address = address.strip()
(lastname, firstname, city, phone) = address.split(';')
print(firstname + " " + lastname + ", " + city + ", " +
2
3
4
phone)
eine Abkürzung für separator
Mögliche Ähnlichkeiten mit real existierenden Personen bzw. Telefonnummern sind rein zufällig und nicht
beabsichtigt.
Sie finden es unter dem Namen „split_addresses.py” in unserem Beispielverzeichnis.
177
178
19 Alles über Strings . . .
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Das Programm liefert folgende Ausgabe:
bernd@saturn:~/bodenseo/python/beispiele$ python3 split_addresses.py
Meyer Frank, Radolfzell, 07732/43452
Rabe Peter, Konstanz, 07531/70021
Huber Ottmar, Rosenheim, 08031/7877-0
Rabe Anna, Radolfzell, 07732/2343
Lindner Oskar, Konstanz, 07531/890
List Anna, München, 089/3434544
List Anna, München, 089/3434544
Huber Franziska, Rosenheim, 08031/787878
Rabe Sarah, Konstanz, 07531/343454
bernd@saturn:~/bodenseo/python/beispiele$
Nehmen wir an, dass wir lediglich den Vornamen und den Nachnamen aus den Zeilen der
obigen Datei benötigen. Dann ist die Aufspaltung zwischen Ort und Telefonnummer unnötig. Für diesen Zweck stellt die Methode split den optionalen Parameter „maxsplit” zur
Verfügung. Der Wert von „maxsplit” bestimmt, wie oft ein String aufgespalten werden soll,
beginnend von links:
>>> s = "red,green,blue,yellow,pink,brown,black,white"
>>> for i in range(1,8):
...
x = s.split(",",i)
...
print(len(x), x)
...
2 ['red', 'green,blue,yellow,pink,brown,black,white']
3 ['red', 'green', 'blue,yellow,pink,brown,black,white']
4 ['red', 'green', 'blue', 'yellow,pink,brown,black,white']
5 ['red', 'green', 'blue', 'yellow', 'pink,brown,black,white']
6 ['red', 'green', 'blue', 'yellow', 'pink', 'brown,black,white']
7 ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black,white']
8 ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white
']
19.2.2
Standardverhalten und „maxsplit”
Man möchte gerne die Anzahl der Aufsplittung begrenzen, aber gleichzeitig will man die
Standardeinstellung für den Trenner erhalten. Dies ist eine Problemstellung, die häufig auftritt und häufig falsch gelöst wird:
Viele kommen zuerst auf folgende Idee, die sich aber sofort als falsch erweist. Man versucht, „maxsplit” als Schlüsselwortparameter zu benutzen, aber die Fehlermeldung weist
einen darauf hin, dass split keine Schlüsselwortparameter kennt:
>>> s = "red\tgreen blue\nyellow \t pink brown"
>>> s.split(maxsplit=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: split() takes no keyword arguments
19.2 Aufspalten von Zeichenketten
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Eingabe für den Parameter einfach leer zu lassen und zu hoffen, dass Python dann
automatisch den Default-Wert einsetzt, funktioniert auch nicht. Dies ist übrigens nie möglich, also auch nicht in anderen Methoden und Funktionen:
>>> s = "red\tgreen blue\nyellow \t pink brown"
>>> s.split(,2)
File "<stdin>", line 1
s.split(,2)
^
SyntaxError: invalid syntax
Die nächste „Lösung” funktioniert zwar syntaktisch, löst aber unser Problem nur teilweise.
Man setzt an die erste Stelle für den Separator ein Leerzeichen ein:
>>> s = "red green blue yellow pink brown"
>>> s.split(" ",2)
['red', 'green', 'blue yellow pink brown']
>>> s = "red green blue yellow pink brown"
>>> s.split(" ",2)
['red', '', 'green blue yellow pink brown']
>>> s = "red\tgreen blue\nyellow \t pink brown"
>>> s.split(" ",2)
['red\tgreen', 'blue\nyellow', '\t pink brown']
Man sieht, dass es nur im ersten Fall funktioniert hatte, d.h. wenn alle Wörter nur durch
genau ein Leerzeichen getrennt sind. Gibt es mehrere Leerzeichen, Tab-Zeichen oder ein
Newline-Zeichen, funktioniert es nicht mehr.
Nun kommen wir endlich zur korrekten Lösung. Will man keinen Parameter angeben und
stattdessen den Default-Wert nehmen, dann gibt man an der entsprechenden Position einfach den speziellen Wert „None” ein. Damit können wir alle obigen Beispiele korrekt aufspalten:
>>> s = "red green blue yellow pink brown"
>>> s.split(None,2)
['red', 'green', 'blue yellow pink brown']
>>> s = "red green blue yellow pink brown"
>>> s.split(None,2)
['red', 'green', 'blue yellow pink brown']
>>> s = "red\tgreen blue\nyellow \t pink brown"
>>> s.split(None,2)
['red', 'green', 'blue\nyellow \t pink brown']
19.2.3
rsplit
rsplit ohne den Parameter „maxsplit” zu betrachten, macht wenig Sinn. rsplit spaltet einen
String von rechts beginnend auf. Gibt man für maxsplit keinen Parameter an, sind die Ergebnisse von split und rsplit nicht unterscheidbar.
179
180
19 Alles über Strings . . .
>>> s.split(",")
['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white']
>>> s.rsplit(",")
['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white']
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Ganz anders sieht es aus, wenn wir mit „maxsplit” arbeiten. Zur Demonstration des Unterschieds wiederholen wir das vorletzte Beispiel mit rsplit:
>>> s = "red,green,blue"
>>> s.rsplit(",")
['red', 'green', 'blue']
>>> for i in range(1,3):
...
x = s.rsplit(",",i)
...
print(len(x), x)
...
2 ['red,green', 'blue']
3 ['red', 'green', 'blue']
Es bringt einen großen Zeitvorteil, konsequent „maxsplit” zu benutzen, wenn man nur wenige Elemente eines Strings von links oder rechts braucht. Im folgenden Programm sammeln wir jeweils das erste Wort von jeder Zeile des Romans „Ulysses”. In der Funktion
„splitter_total” splitten wir jeweils die komplette Zeile, während wir in der Funktion „splitter_maxsplit” nur das erste Wort abspalten:
import time
def splitter_total():
fh = open("ulysses.txt")
res = []
for line in fh:
x = line.split()
if len(x):
res.append(x[0])
return res
def splitter_maxsplit():
fh = open("ulysses.txt")
res = []
for line in fh:
x = line.split(None, 1)
if len(x):
res.append(x[0])
return res
for i in range(5):
t1 = time.time()
splitter_total()
t1 = time.time() - t1
19.2 Aufspalten von Zeichenketten
t2 = time.time()
splitter_maxsplit()
t2 = time.time() - t2
print(t1, t2, t1/t2)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Starten wir das Programm, erkennen wir aus der Ausgabe, dass die Version mit „maxsplit”
ziemlich stabil in jedem Lauf um etwa den Faktor 1,5 schneller ist:
bernd@saturn:~/bodenseo/python/beispiele$ python3 splitter_test.py
0.06152200698852539 0.03992414474487305 1.5409724461643197
0.06169414520263672 0.04128098487854004 1.4944930549539404
0.059509992599487305 0.039869070053100586 1.4926355824258624
0.06016898155212402 0.04076218605041504 1.476098006071276
0.05954909324645996 0.03962898254394531 1.502665206718968
19.2.4
Folge von Trennzeichen
Wir hatten gezeigt, dass mehrere Leerzeichen, Tabs usw. beim Standardverhalten von split
und rsplit als ein einziger Trenner aufgefasst werden. Wie sieht es aber aus, wenn man den
Trenner explizit als Parameter angibt?
>>> s = "red
green blue yellow pink brown"
>>> s.split()
['red', 'green', 'blue', 'yellow', 'pink', 'brown']
>>> s.split(" ")
['red', '', '', '', 'green', '', 'blue', 'yellow', 'pink', 'brown']
>>> s = "red;;;green;blue;;yellow;pink;brown"
>>> s.split(";")
['red', '', '', 'green', 'blue', '', 'yellow', 'pink', 'brown']
Wir sehen, dass das Zusammenfassen mehrerer Trenner zu einem Trenner nur beim Standardtrenner von split funktioniert.
Dieses Verhalten ist sinnvoll, wenn man beispielsweise an Dateien im CSV-Format5 denkt.
Befinden sich zwischen zwei Trennern keine Zeichen, so heißt dies, dass dieses Datenfeld
leer ist. Im folgenden Listing fehlt in der zweiten Zeile der Vorname, in der vierten Zeile der
Ort und in der fünften Zeile die Telefonnummer:
Frank;Meyer;Radolfzell;07732/43452
;Rabe;Konstanz;07531/70021
Ottmar;Huber;Rosenheim;08031/7877-0
Anna;Rabe;;07732/2343
Oskar;Lindner;Konstanz;
5
Der Name des Dateiformats CSV steht für „comma-separated values”. Es handelt sich um ein Datenaustauschformat, in dem häufig Kommas zur Trennung von Datenfeldern (Spalten) in einer Zeile verwendet
werden. Statt Kommas können natürlich auch andere Zeichen wie beispielsweise ein Strichpunkt zur Trennung benutzt werden. Die Dateinamenserweiterung lautet üblicherweise .csv.
181
182
19 Alles über Strings . . .
Anna;List;München;089/3434544
Anna;List;München;089/3434544
Franziska;Huber;Rosenheim;08031/787878
Sarah;Rabe;Konstanz;07531/343454
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wenden wir split(";") darauf an, erhalten wir jeweils Listen der Länge 4. Fehlende Einträge sind mit leerem String markiert. Mit dem Programm „split_addresses_4.py” können wir dieses Verhalten überprüfen:
fh = open("addresses2.txt", encoding="iso-8859-1")
for address in fh:
address = address.strip()
elements = address.split(';')
print(elements)
Starten wir dieses Skript, erhalten wir die erwartete Ausgabe:
bernd@saturn:~/bodenseo/python/beispiele$ python3 split_addresses_4.py
['Frank', 'Meyer', 'Radolfzell', '07732/43452']
['', 'Rabe', 'Konstanz', '07531/70021']
['Ottmar', 'Huber', 'Rosenheim', '08031/7877-0']
['Anna', 'Rabe', '', '07732/2343']
['Oskar', 'Lindner', 'Konstanz', '']
['Anna', 'List', 'München', '089/3434544']
['Anna', 'list', 'München', '089/3434544']
['Franziska', 'Huber', 'Rosenheim', '08031/787878']
['Sarah', 'Rabe', 'Konstanz', '07531/343454']
bernd@saturn:~/bodenseo/python/beispiele$
19.2.5
splitlines
splitlines ist eine String-Methode, die einen Text mit Zeilenbegrenzern – also „\n” unter
Unix, „\r” beim Mac und „\r\n” unter Windows – in eine Liste von Zeilen zerlegt. Zeilenenden können auch von unterschiedlichen Betriebssystemen in einem String gemischt sein,
und splitlines erkennt sie als Zeilenenden. Im folgenden Beispiel wird „\r\n” korrekterweise als Windows-Zeilenende erkannt, und „\n\r” hinter line2 wird als Unix- und als MacZeilenende erkannt, d.h. nach line2 kommt also eine Leerzeile, die als leerer String in die
Ergebnisliste eingeht:
>>> s = "line1\nline2\n\rline3\r\nline4\rline5\n"
>>> s.splitlines()
['line1', 'line2', '', 'line3', 'line4', 'line5']
>>> s.splitlines(True)
['line1\n', 'line2\n', '\r', 'line3\r\n', 'line4\r', 'line5\n']
19.3 Zusammenfügen von Stringlisten mit join
19.2.6
partition
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Mit der Methode „partition” wird ein String an der ersten Stelle von links nach rechts aufgespaltet, an der der Trennstring „sep” steht. Das Ergebnis ist ein Tupel, dessen erste Komponente der Teilstring vor dem sep ist, die zweite Komponente der String sep ist und die
dritte Komponente der nach dem Vorkommen von sep stehende Teilstring:
>>> spruch = "Eine Katze, die jagt, hungert nicht!"
>>> spruch.partition(",")
('Eine Katze', ',', ' die jagt, hungert nicht!')
>>> spruch.rpartition(",")
('Eine Katze, die jagt', ',', ' hungert nicht!')
19.3
join
join ist eine String-Methode:
s.join(iterable)
Zurückgeliefert wird die Konkatenation der Elemente des iterierbaren Objekts „iterable”.
Die Elemente von „iterable” werden dabei so zusammengefügt, dass zwischen den Elementen jeweils der String „s” eingefügt wird. „s” ist dabei das String-Objekt, auf das die
Methode „join” angewendet wird.
>>> x = ["Python","Perl","Java"]
>>> "-".join(x)
'Python-Perl-Java'
>>> x = "48772"
>>> ".".join(x)
'4.8.7.7.2'
19.4
Suchen von Teilstrings
Für die Suche von Teilstrings gibt es verschiedene Möglichkeiten in Python:
19.4.1
„in” oder „not in”
„in” ist ideal, um die reine Existenz eines Substrings zu testen, wenn man nicht daran interessiert ist zu wissen, wo der Unterstring vorkommt:
>>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas
pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What
a good boy am I!" '
183
184
19 Alles über Strings . . .
>>> "corner" in rhyme
True
>>> "corner" not in rhyme
False
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
19.4.2
s.find(substring[, start[, end]])
Mit der Stringmethode „find” kann man prüfen, ob ein String „substring” in einem String
„s” vorkommt. „find” liefert eine -1 zurück, wenn „substring” nicht vorkommt; ansonsten
liefert sie die Position zurück, ab der „substring” in „s” beginnt:
>>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas
pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What
a good boy am I!" '
>>> rhyme.find("orner")
13
>>> rhyme.find("Jill")
-1
Gefunden wird das „orner” in dem Wort „Horner”. Der Name „Jill” kommt nicht in unserem
String vor, deshalb liefert „find” ein -1 zurück.
Mit den optionalen Parametern „start” und „end” kann man die Suche innerhalb des String
„s” einschränken. Wird nur ein Wert für „start” angegeben, wird in dem Teilstring s[start:]
gesucht. Wird sowohl ein Wert für „start” und „end” angegeben, dann wird in dem Teilstring s[start:end] gesucht. Wir verdeutlichen dies in folgendem Beispiel:
>>>
>>>
3
>>>
3
>>>
-1
19.4.3
s = "annxbny"
s.find("x")
s.find("x",2)
s.find("x",2,3)
s.rfind(substring[, start[, end]])
Die Stringmethode „rfind” funktioniert analog zu „find”, allerdings erfolgt die Suche von
rechts, also vom Ende des Strings:
>>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas
pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What
a good boy am I!" '
>>> rhyme.rfind("orner")
29
19.4 Suchen von Teilstrings
19.4.4
s.index(substring[, start[, end]])
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Funktioniert genau wie „find”, außer wenn „substring” nicht in „s” vorkommt. In diesem
Fall wird ein Ausnahmefehler generiert:
>>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas
pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What
a good boy am I!" '
>>> rhyme.index("orner")
13
>>> rhyme.index("orner") == rhyme.find("orner")
True
>>> rhyme.index("Jill")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: substring not found
19.4.5
s.rindex(substring[, start[, end]])
Funktioniert analog zu „index” und „rfind”.
>>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas
pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What
a good boy am I!" '
>>> rhyme.rindex("orner")
29
>>> rhyme.rindex("Jill")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: substring not found
19.4.6
s.count(substring[, start[, end]])
Zählt die Vorkommen eines Teilstrings „substring” in s. „start” und „end” verhalten sich
genau wie bei „find” und „index”:
>>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas
pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What
a good boy am I!" '
>>> rhyme.count("Jill")
0
>>> rhyme.count("orner")
2
>>> rhyme.count("Horner")
1
>>> rhyme.count("in")
3
185
186
19 Alles über Strings . . .
19.5
Suchen und Ersetzen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wenn man mit Texten arbeitet, besteht eine häufige Aufgabe darin, dass man eine Zeichenkette durch eine andere ersetzen will bzw. muss. Dies gilt auch für Bereiche, die nichts mit
dem Programmieren zu tun haben: so beispielsweise beim Erstellen von Briefen oder Rechnungen. Man benutzt einen alten Brief und tauscht nur Name und Adresse des Empfängers
aus.
Aber zurück zu Python und zum Programmieren. Zum Ersetzen eines Teilstrings „old” in
einem String „s” durch einen anderen Teilstring „new” gibt es die Methode replace:
s.replace(old, new[, count])
Ohne Angabe des optionalen Parameters „count” werden alle Vorkommen von old durch
new ersetzt. Mit dem optionalen Parameter „count” kann man steuern, wie viele Vorkommen von old durch new ersetzt werden sollen:
>>> ch = "Drei Chinesen mit dem Kontrabass"
>>> ch.replace("e","a")
'Drai Chinasan mit dam Kontrabass'
>>> ch.replace("e","a").replace("i","a")
'Draa Chanasan mat dam Kontrabass'
>>> ch.replace("e","a").replace("i","a").replace("o","a")
'Draa Chanasan mat dam Kantrabass'
19.6
Kleinbuchstaben und
Großbuchstaben
Man kann einen String mithilfe der Stringmethode „upper” komplett in Großbuchstaben
wandeln. Ebenso kann man einen String mithilfe der Stringmethode „lower” komplett in
Kleinbuchstaben wandeln:
>>> ch = "Drei Chinesen mit dem Kontrabass"
>>> ch.lower()
'drei chinesen mit dem kontrabass'
>>> ch.upper()
'DREI CHINESEN MIT DEM KONTRABASS'
19.7
capitalize und title
„capitalize” ist eine Funktion, die alle Buchstaben außer dem Anfangsbuchstaben eines
Strings in Kleinbuchstaben wandelt. Der Anfangsbuchstabe wird in einen Großbuchstaben
gewandelt, falls es sich um einen Kleinbuchstaben handelt.
19.8 Stripping Strings
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
„title” wandelt alle Buchstaben, die nicht Anfangsbuchstaben eines Wortes sind, in Kleinbuchstaben und alle Anfangsbuchstaben eines Wortes in einen Großbuchstaben um.
>>> ch = "Drei Chinesen mit dem Kontrabass"
>>> ch.capitalize()
'Drei chinesen mit dem kontrabass'
>>> ch.title()
'Drei Chinesen Mit Dem Kontrabass'
>>> ch = "Drei Chinesen mit dem kONTRABASS"
>>> ch.title()
'Drei Chinesen Mit Dem Kontrabass'
19.8
Stripping Strings
Häufig kommt es vor, dass unerwünschte Zeichen am Anfang oder am Ende eines Strings
stehen. Meistens sind es „Whitespaces“ wie Zeilenende, Tabs usw., die auf der rechten Seite
eines Strings stehen. Um diese Zeichen loszuwerden, gibt es die Stringmethoden „strip”,
„lstrip” und „rstrip”.
■
■
■
s.strip([chars])
Unerwünschte Zeichen werden auf beiden Seiten des Strings entfernt.
s.lstrip([chars])
Unerwünschte Zeichen werden nur auf der linken Seite des Strings entfernt.
s.rstrip([chars])
Unerwünschte Zeichen werden nur auf der rechten Seite des Strings entfernt.
>>> s = " \t \n \rMorgen kommt der Weihnachtsmann \t\n"
>>> s.strip()
'Morgen kommt der Weihnachtsmann'
>>> s.lstrip()
'Morgen kommt der Weihnachtsmann \t\n'
>>> s.rstrip()
' \t \n \rMorgen kommt der Weihnachtsmann'
>>>
>>> s = "69023 Frankfurt"
>>> s.strip("0123456789 ")
'Frankfurt'
19.9
■
Strings ausrichten
str.center(laenge[, fillchar])
Der String str wird mit fillchar bzw. Leerzeichen, falls kein fillchar gegeben ist, gleichermaßen von links und rechts auf die Länge laenge gebracht.
187
188
19 Alles über Strings . . .
■
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
str.ljust(laenge[, fillchar])
Der String str wird mit fillchar bzw. Leerzeichen, falls kein fillchar gegeben ist, links auf
die Länge laenge gebracht.
str.rjust(laenge[, fillchar])
Analog zu ljust von rechts.
str.zfill(laenge)
Spezialfall für numerische Werte. Der String wird rechts ausgerichtet und von links mit
Nullen gefüllt.
>>> s = "Hallo"
>>> s.center(15)
'
Hallo
'
>>> s.ljust(15)
'Hallo
'
>>> s.rjust(15)
'
Hallo'
>>>
>>> z = "123.99"
>>> z.zfill(10)
'0000123.99'
19.10
■
String-Tests
s.isalnum()
True, wenn alle Zeichen in s Buchstaben oder Ziffern sind.
>>> for word in ("mp3", "Hallo", "hello", "343", "767.43"):
...
print("%6s : %s" % (word, word.isalnum()))
...
mp3 : True
Hallo : True
hello : True
343 : True
767.43 : False
■
s.isalpha()
True, wenn alle Zeichen in s Buchstaben sind.
>>> for word in ("Hallo", "hello", "343", "767.43"):
...
print("%6s : %s" % (word, word.isalpha()))
...
Hallo : True
hello : True
343 : False
767.43 : False
19.10 String-Tests
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
s.isdigit()
True, wenn alle Zeichen in s Ziffern sind.
>>> for word in ("Hallo", "hello", "343", "767.43"):
...
print("%6s : %s" % (word, word.isdigit()))
...
Hallo : False
hello : False
343 : True
767.43 : False
■
s.islower()
True, wenn alle Buchstaben in s Kleinbuchstaben sind.
>>> for word in ("Hallo", "hello", "343", "767.43"):
...
print("%6s : %s" % (word, word.islower()))
...
Hallo : False
hello : True
343 : False
767.43 : False
■
s.isupper()
True, wenn alle Buchstaben in s Großbuchstaben sind.
>>> for word in ("Hallo", "hello", "343", "767.43", "HALLO"):
...
print("%6s : %s" % (word, word.isupper()))
...
Hallo : False
hello : False
343 : False
767.43 : False
HALLO : True
■
s.isspace()
True, wenn alle Zeichen in s Whitespaces sind.
>>> for word in ("Hallo", "hello", "343", "767.43", " \t\n", " "):
...
print("%6s : %s" % (word, word.isspace()))
...
Hallo : False
hello : False
343 : False
767.43 : False
: True
: True
189
190
19 Alles über Strings . . .
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
s.istitle()
True, wenn alle Wörter in s groß geschrieben sind.
>>> for word in ("Hallo", "hello", "343", "767.43", "HALLO"):
...
print("%6s : %s" % (word, word.istitle()))
...
Hallo : True
hello : False
343 : False
767.43 : False
HALLO : False
19.11
Aufgaben
1. Aufgabe:
Folgende Adressendatei (siehe addresses_mixed.txt) ist gegeben:
Frank,Meyer,Radolfzell;07732/43452
Peter,Rabe,Konstanz;07531/70021
Ottmar,Huber,Rosenheim;08031/7877-0
Anna,Rabe,Radolfzell;07732/2343
Oskar,Lindner,Konstanz;07531/890
Anna,List,München;089/3434544
Anna,List,München;089/3434544
Franziska,Huber,Rosenheim;08031/787878
Sarah,Rabe,Konstanz;07531/343454
Schreiben Sie ein Python-Programm, das diese Adressen zeilenweise in der Form
„Vorname, Nachname, Ort, Telefonnummer” ausgibt.
Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 499
2. Aufgabe:
Für die folgende Aufgabe benutzen wir wieder die Daten der Datei „adressen.txt”:
Frank;Meyer;Radolfzell;07732/43452
Peter;Rabe;Konstanz;07531/70021
Ottmar;Huber;Rosenheim;08031/7877-0
Anna;Rabe;Radolfzell;07732/2343
Oskar;Lindner;Konstanz;07531/890
Anna;List;München;089/3434544
Anna;List;München;089/3434544
Franziska;Huber;Rosenheim;08031/787878
Sarah;Rabe;Konstanz;07531/343454
19.11 Aufgaben
Schreiben Sie ein Python-Programm, das aus dieser Adressendatei eine Liste mit
Zweiertupels erstellt, die jeweils aus dem ersten und letzten Element einer Adresse,
also dem Vornamen und der Telefonnummer, bestehen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
[('Frank', '07732/43452'), ('Peter', '07531/70021'), ('Ottmar',
'08031/7877-0'), ('Anna', '07732/2343'), ('Oskar', '07531/890'), ('
Anna', '089/3434544'), ('Anna', '089/3434544'), ('Franziska',
'08031/787878'), ('Sarah', '07531/343454')]
Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 499
3. Aufgabe:
Der folgende String enthält Zeilenenden, die sowohl vom Mac als auch von Windows
und Unix bzw. Linux kommen:
Hat der alte Hexenmeister\nSich doch einmal wegbegeben!\r\nUnd nun
sollen seine Geister\r
Auch nach meinem Willen leben.\n
Bringen Sie diesen String mittels Python in eine Linux/Unix-Form, d.h. dass jede Zeile
nur von einem \n beendet wird.
Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 500
4. Aufgabe:
Schreiben Sie eine Funktion, die die Position des n-ten Vorkommens eines Strings
„sub” in einem anderen String ausgibt. Falls „sub” nicht vorkommt, soll -1 zurückgeliefert werden.
>>>
>>>
4
>>>
12
>>>
20
from findnth import findnth
findnth("abc xyz abc xyz abc xyz", "xyz", 1)
findnth("abc xyz abc xyz abc xyz", "xyz", 2)
findnth("abc xyz abc xyz abc xyz", "xyz", 3)
Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 500
5. Aufgabe:
Schreiben Sie eine Funktion, die das n-te Vorkommen eines Strings „sub” durch einen
String „replacement” ersetzt.
Die Funktion soll als Ergebnis den veränderten String zurückliefern, bzw. falls replacement nicht im String vorkommt, soll der unveränderte Original-String zurückgegeben
werden.
Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 501
191
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
20
Ausnahmebehandlung
Fehler werden häufig in anderen Sprachen mit Fehlerrückgabewerten oder globalen Statusvariablen behandelt.
Traditionelle Fehlerbehandlung bzw. Fehlervermeidung wird
meistens in bedingten Anweisungen behandelt, so wie im folgenden Codefragment, in dem eine Division durch 0 verhindert werden soll:
if y != 0:
z = x / y
Bild 20.1
Eleganter geht es mit den in Python vorhandenen Ausnahme- Ausnahmebehandlung
behandlungen. Unter einer Ausnahmebehandlung1 versteht
man ein Verfahren, die Zustände, die während einer Fehlersituation herrschen, an andere
Programmebenen weiterzuleiten. Dadurch ist es möglich, per Programm einen Fehlerzustand gegebenenfalls zu „reparieren”, um anschließend das Programm weiter auszuführen.
Ansonsten würden solche Fehlerzustände in der Regel zu einem Abbruch des Programms
führen. Man verwendet den Begriff „Ausnahme”, um schon mit der sprachlichen Bezeichnung klarzumachen, dass es sich um einen außerordentlichen Zustand handelt, also die
„Ausnahme von der Regel”. Viele Programmiersprachen wie C++, Objective-C, PHP, Java,
Ruby und Python besitzen integrierte Mechanismen mit eigenen formalen syntaktischen
Strukturen, um Ausnahmebehandlungen zu ermöglichen.
Die Realisierung der Ausnahmebehandlung sieht meist so aus, dass wenn eine Ausnahmesituation auftritt, automatisch Informationen und Zustände gespeichert werden, die zum
Zeitpunkt der Ausnahme bzw. vor der Ausnahme geherrscht hatten. In den meisten Sprachen, so in C++, Java, PHP und auch in Python, werden Codeteile, die mit der Ausnahmebehandlung ausgeführt werden sollen, in einem try-Block zusammengefasst.
Die Ausnahmebehandlung in Python ist sehr ähnlich zu derjenigen in Java. Der Code, der
das Risiko für eine Ausnahme beherbergt, wird in einen try-Block eingebettet. Aber während in Java Ausnahmen durch catch-Konstrukte abgefangen werden, geschieht dies in Python durch das Schlüsselwort except. Semantisch funktioniert es aber genauso. Man kann
auch Ausnahmen selbst erzeugen: Mit der raise-Anweisung ist es möglich, eine bestimmte
Ausnahme entstehen zu lassen.
1
Häufig auch unter dem englischen Begriff „exception handling” bekannt.
194
20 Ausnahmebehandlung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Schauen wir uns ein einfaches Beispiel an. Ein Benutzer soll eine Integer-Zahl eingeben.
Wenn wir nur ein input() benutzen, wird die Eingabe als String interpretiert, den wir dann
in ein Integer wandeln müssen. Bei der Anwendung des cast-Operators, also bei der Wandlung des eingegebenen Strings in eine Integer-Zahl, kann es jedoch zu einem Fehler kommen, wenn der String kein gültiges Integer-Format aufzeigt. Es wird dann der AusnahmeFehler „ValueError” generiert:
>>> n = int(input("Please enter a number: "))
Please enter a number: 23.5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '23.5'
>>>
Das folgende Skript implementiert unter Verwendung einer Ausnahmebehandlung eine
Lösung des obigen Problems in Form einer robusten Eingabeaufforderung. Man wird solange aufgefordert, eine Zahl einzugeben, bis diese einer korrekten Integer-Zahl entspricht:
while True:
try:
zahl = input("Zahl eingeben: ")
zahl = int(zahl)
break
except ValueError as e:
print("error message: ", e)
print("Error. Keine Zahl!")
Funktionsweise:
■
■
■
■
Ausführung der try-Klausel (Anweisungen zwischen den Schlüsselworten try und except)
Die except-Klausel wird übersprungen, wenn keine Ausnahme auftritt.
Tritt eine Ausnahme während der Ausführung der try-Klausel auf, wird der Rest der Klausel übersprungen. Stimmt der Typ der Ausnahme mit dem Schlüsselwort von except
überein, wird die except-Klausel ausgeführt.
Tritt eine Ausnahme auf, die nicht mit der Ausnahme in der except-Klausel übereinstimmt, wird sie nach außen an weitere try-Anweisungen weitergereicht. Wenn keine
Behandlung erfolgt, so ist es eine unbehandelte Ausnahme.
Ein Beispiel zum Abfangen von Divisionen durch die Zahl 0:
>>> zahlen = [3.7832, 8.5, 1.9, 0, 4.5]
>>> for x in zahlen:
...
try:
...
print(x, 1.0/x)
...
except ZeroDivisionError:
...
print(str(x) + " hat kein inverses Element")
...
3.7832 0.2643264960879679
8.5 0.11764705882352941
20.1 Abfangen mehrerer Exceptions
1.9 0.5263157894736842
0 hat kein inverses Element
4.5 0.2222222222222222
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
20.1
Abfangen mehrerer Exceptions
Es können auch mehrere Exceptions nach einer try-Anweisung abgefangen werden, wie
wir im folgenden Beispiel zeigen:
import sys
try:
f = open('integers.txt')
s = f.readline()
i = int(s.strip())
except IOError as err:
(errno, strerror) = err.args
print("I/O error({0}): {1}".format(errno, strerror))
except ValueError:
print("No valid integer in line.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
Die Anweisung raise in der letzten Zeile führt die Ausnahme, die wir gerade abgefangen hatten, nochmals aus. Dadurch kann man diese Ausnahme an anderer Stelle wieder
auffangen oder man erhält einen Programmabbruch mit der eigentlichen Fehlermeldung.
In den beiden ersten except-Fällen behandeln wir bestimmte Fehler, also IOError und
ValueError, und im letzten except sind wir unspezifisch. Da wir dort keine Fehlernamen
angeben, spricht dieser Fall auf alle Fehler an, die noch nicht vorher in einer except-Klausel
behandelt worden sind. Haben wir keinen solchen „unspezifischen” except-Fall und tritt
ein Fehler auf, den wir nicht behandeln, dann wird die Standardausnahmebehandlung von
Python ausgelöst, d.h. das Programm wird abgebrochen und die entsprechende Fehlermeldung ausgegeben. Das Programm verhält sich dann so, als hätte es überhaupt keine
try-except-Anweisung gegeben. Jede try-Anweisung muss von mindestens einem exceptFall oder einer finally-Anweisung, die wir erst im Folgenden besprechen werden, gefolgt
werden.
195
196
20 Ausnahmebehandlung
20.2
except mit mehrfachen Ausnahmen
Eine einzelne except-Anweisung kann auch gleichzeitig mehrere Fehler abfangen. Die verschiedenen Fehlerarten werden dann in einem Tupel gelistet, wie wir im folgenden Beispiel
sehen:
try:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
f = open('integers.txt')
s = f.readline()
i = int(s.strip())
except (IOError, ValueError):
print("An I/O error or a ValueError occurred")
except:
print("An unexpected error occurred")
raise
20.3
Die optionale else-Klausel
Das try ... except-Sprachkonstrukt hat eine optionale else-Klausel, die – falls vorhanden
– nach allen except-Klauseln stehen muss. Dort befindet sich Code, der ausgeführt wird,
wenn die try-Klausel keine Ausnahme auslöst.
Im Folgenden verlangen wir solange die Eingabe eines Dateinamens, bis sich dieser zum
Lesen öffnen lässt. Der else-Teil der try-Anweisung wird nur ausgeführt, wenn es keinen
Ausnahmefehler gegeben hat. Deshalb dürfen wir dann auch auf das Datei-Handle f zugreifen:
while True:
filename = input("Dateiname: ")
try:
f = open(filename, 'r')
except IOError:
print(filename, " lässt sich nicht öffnen")
else:
print(filename, ' hat ', len(f.readlines()), ' Zeilen ')
f.close()
break
20.4 Fehlerinformationen über sys.exc_info
20.4
Fehlerinformationen
über sys.exc_info
Die genauen Fehlerinformationen kann man sich mit der Methode exc_info des sysModuls anzeigen lassen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
import sys
try:
i = int("Hallo")
except:
(type, value, traceback) = sys.exc_info()
print("Unexpected error:")
print("Type: ", type)
print("Value: ", value)
print("traceback: ", traceback)
raise
Die Ausgabe des obigen Programms:
bernd@saturn:~/bodenseo/python/beispiele$ python3
exception_sys_exc_info.py
Unexpected error:
Type: <class 'ValueError'>
Value: invalid literal for int() with base 10: 'Hallo'
traceback: <traceback object at 0xb728834c>
Traceback (most recent call last):
File "exception_sys_exc_info.py", line 4, in <module>
i = int("Hallo")
ValueError: invalid literal for int() with base 10: 'Hallo'
bernd@saturn:~/bodenseo/python/beispiele$
20.5
Exceptions generieren
Man kann auch selbst Exceptions generieren:
>>> raise SyntaxError("Sorry, mein Fehler!")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
SyntaxError: Sorry, mein Fehler!
Man kann auch selbst Exception-Klassen definieren. Klassen werden wir jedoch erst in Kapitel 21 (Grundlegende Aspekte) behandeln, sodass das folgende Beispiel für viele noch
unverständlich bleiben wird:
197
198
20 Ausnahmebehandlung
class MyException(Exception):
pass
raise MyException("Was falsch ist, ist falsch!")
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Startet man dieses Programm, erhält man folgende Ausgabe:
$ python3 exception_eigene_klasse.py
Traceback (most recent call last):
File "exception_eigene_klasse.py", line 4, in <module>
raise MyException("Was falsch ist, ist falsch!")
__main__.MyException: Was falsch ist, ist falsch!
20.6
Finalisierungsaktion
Bisher haben wir die try-Anweisungen immer nur im Zusammenspiel mit except-Klauseln
benutzt. Aber es gibt noch eine andere Möglichkeit für try-Anweisungen. Die try-Anweisung
kann von einer finally-Klausel gefolgt werden.
Man bezeichnet diese Form auch als Finalisierungs- oder Terminierungsaktionen, weil sie
immer unter allen Umständen ausgeführt werden müssen, und zwar unabhängig davon,
ob eine Ausnahme im try-Block aufgetreten ist oder nicht.
try:
x = float(input("Your number: "))
inverse = 1.0 / x
finally:
print("Ich werde immer ausgegeben, ob Fehler oder nicht")
print("Mich sieht man nur, wenn es keinen Fehler gab!")
In den folgenden Programmläufen demonstrieren wir einen Fehlerfall und einen Durchlauf ohne Ausnahmen:
bernd@saturn:~/bodenseo/python/beispiele$ python3 finally_exception.py
Your number: 42
Ich werde immer ausgegeben, ob Fehler oder nicht
Mich sieht man nur, wenn es keinen Fehler gab!
bernd@saturn:~/bodenseo/python/beispiele$ python3 finally_exception.py
Your number: 0
Ich werde immer ausgegeben, ob Fehler oder nicht
Traceback (most recent call last):
File "finally_exception.py", line 3, in <module>
inverse = 1.0 / x
ZeroDivisionError: float division by zero
bernd@saturn:~/bodenseo/python/beispiele$
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Teil II
Objektorientierte Programmierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21
Grundlegende Aspekte
In diesem Kapitel werden wir die wichtigsten Aspekte der Objektorientierung anhand eines
Vergleichs mit Bibliotheken und mit einer einfachen Roboter-Klasse demonstrieren.
21.1
Bibliotheksvergleich
Auch wenn Python ohne Wenn und Aber
eine objektorientierte Programmiersprache ist, sind wir in den vorhergehenden
Kapiteln nur indirekt auf die objektorientierte Programmierung (OOP) eingegangen. Wir haben Objekte und Methoden
von Klassen benutzt, ohne eigentlich von
ihrer Existenz zu wissen. Mit Python lassen sich kleine Skripte oder Programme
einfach und effizient schreiben, auch ohne dass man sie objektorientiert model- Bild 21.1 Früchte
liert. Gerade totale Programmieranfänger
finden es erfahrungsgemäß einfacher, wenn sie nicht sofort mit allen Prinzipien der OOP
konfrontiert werden. Sie haben genügend Probleme, Zuweisungen, bedingte Anweisungen oder Schleifen zu verstehen und vor allem richtig anzuwenden. Aber in vielen Situationen stellt die OOP eine deutliche qualitative Verbesserung der Implementierung eines
Problems dar.
In diesem Kapitel geben wir nun eine grundlegende Einführung in den objektorientierten
Ansatz von Python. OOP ist eine der mächtigsten Programmiermöglichkeiten von Python,
aber wie wir gesehen haben, muss man sie dennoch nicht nutzen, d.h. man kann auch umfangreiche und effiziente Programme ohne Verwendung von OOP-Techniken schreiben.
Auch wenn viele Programmierer und Informatiker die OOP für eine moderne Errungenschaft halten, so gehen ihre Wurzeln bis in die 1960er-Jahre zurück. Die erste Programmiersprache, die Objekte verwendete, war „Simula 67” von Ole-Johan Dahl und Kirsten
Nygard.
202
21 Grundlegende Aspekte
Wir werden die fundamentalen Prinzipien der objektorientierten Programmierung und ihre Realisierung in Python kennenlernen, also Datenabstraktion, Datenkapselung, Verstecken von Informationen, Polymorphismus, Vererbung, Mehrfachvererbung, Metaklassen
und abstrakte Klassen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Bevor wir mit der eigentlichen OOP in Python beginnen, wollen wir die grundsätzlichen
Ideen der OOP am Beispiel von öffentlichen Bibliotheken verdeutlichen. Eine Bibliothek
enthält eine geordnete Sammlung von Büchern, Zeitschriften und Zeitungen, Hörbüchern,
Filmen und so weiter.
Generell gibt es zwei verschiedene Möglichkeiten,
wie man die Bestände in einer Bibliothek zur Nutzung bereitstellen kann. In der Freihandaufstellung
handelt es sich um eine für alle Benutzer frei zugängliche Aufstellung der Medien. Die Magazinaufstellung ist hingegen eine Aufstellungsart, bei der
die Bestände nur Bibliotheksmitarbeitern zugänglich sind. In einer solchen Magazinaufstellung werden Bücher häufig nicht nach Fachgruppen aufgestellt. So können Bücher beispielsweise aus Platzgründen auch unter Berücksichtigung ihres Formats aufgestellt werden. So können ebenfalls zur
Platzoptimierung Kompaktregalanlagen oder sogar
Hochregale eingesetzt werden.
Hier beginnt nun unsere Analogie zur Objektorientierten Programmierung. Die Bücher, Hörbücher,
Filme usw. entsprechen den Objekten, während die Bild 21.2 Eierschale und Bibliothek
Bibliothek inklusive der Art, wie sie ihre Bestände
(„Daten”) bereitstellt, einer Klasse entspricht.1 Auf
die Daten einer Klasse kann meistens nur über spezielle Methoden zugegriffen werden,
was den Mitarbeiterinnen und Mitarbeitern in der Bibliothek entspricht. Die Tatsache, dass
man auf die „Daten”, also die Bücher, nicht direkt zugreifen kann, bezeichnet man als Datenabstraktion. Einerseits sind die Daten gekapselt, d.h. Benutzer können und dürfen das
Magazin nicht betreten, andererseits sind die Bücher auch nicht sichtbar, d.h. sie sind vor
dem Benutzer versteckt, was man meistens auch in der OOP als „Geheimnisprinzip”2 bezeichnet. Die Vorteile in der Bibliothek sind denjenigen bei der Datenspeicherung in einer
Klasse sehr ähnlich:
■
■
■
1
2
Schutz vor falschem Einsortieren
Entspricht der falschen Benutzung einer Datenstruktur.
Diebstahl oder Vandalismus
Der Benutzer von Daten einer Implementierung benutzt diese wissentlich oder unwissentlich falsch.
Platzoptimierung
Hat man komfortable Methoden als Schnittstellen für die Klassenbenutzer, kann man
Genaugenommen müssten wir sagen eine bestimmte Bibliothek – in unserem Bild die „Library of Appeal
for Ontario” – ist eine Instanz der Klasse Bibliothek, also dem „Konzept”.
engl. ,Information Hiding”
21.2 Objekte und Instanzen einer Klasse
die dahinterliegenden Daten auch in komplexeren und damit gegebenenfalls auch
schwerer verständlichen Strukturen speichern.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
Raumklima
Die Daten müssen nicht benutzerfreundlich repräsentiert sein, da die Benutzerfreundlichkeit in den Methoden der Klasse liegt.
21.2
Objekte und Instanzen einer Klasse
Vielleicht war Ihnen bereits aufgefallen, dass bei der Verwendung der type-Funktion immer auch das Wort „class” auftaucht. Alle Datentypen, Funktionen, Module usw. sind in
Python Klassen:
>>> b = 24.1257
>>> type(b)
<class 'float'>
>>>
>>> def foobar(x):
...
return x + 42
...
>>> type(foobar)
<class 'function'>
>>>
>>> import math
>>> type(math)
<class 'module'>
Eine der vielen in Python integrierten Klassen ist die uns bestens vertraute list-Klasse.
Sie stellt eine Fülle von Methoden zur Verfügung, mit deren Hilfe wir beispielsweise Listen
aufbauen, Elemente anschauen, verändern und entfernen können:
>>>
>>>
>>>
6
>>>
>>>
>>>
>>>
abc
x = [3, 6, 9]
y = [45, "abc"]
print(x[1])
x[1] = 99
x.append(42)
last = y.pop()
print(last)
Die Variablen x und y referenzieren zwei Instanzen der list-Klasse. Vereinfacht haben wir
bisher gesagt, „x und y sind Listen”. Im Folgenden werden wir die Begriffe „Objekt” und
„Instanz” synonym benutzen, wie dies auch in anderen Einführungen üblich ist.3
3
Nach der offiziellen Python-Referenz wird in Python eigentlich alles als Objekt bezeichnet, also beispielsweise Zahlen, Strings, Listen, aber auch Funktionen und Module. Im englischen Original heißt es: „Objects
203
204
21 Grundlegende Aspekte
21.3
Kapselung von Daten und Methoden
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
pop und append aus dem obigen Beispiel sind Methoden der list-Klasse. pop liefert uns
das „oberste” bzw. das Element mit dem höchsten Index der Liste zurück und entfernt dieses Element. Wir wissen allerdings nicht, wie die Listen intern im Speicher abgelegt sind.
Wir brauchen diese Information auch nicht, da uns die list-Klasse Methoden zur Verfügung stellt, auf die gespeicherten Daten „indirekt” zuzugreifen. Methoden sind von besonderer Wichtigkeit im Zusammenhang mit der Datenkapselung. Wir werden uns später
genauer mit der Datenkapselung beschäftigen. Sie verhindert den direkten Zugriff auf die
interne Datenstruktur, d.h. man kann nur über definierte Schnittstellen zugreifen, die Methoden bzw. die Properties. Die „internen Daten” einer Instanz sind die Attribute, auf die
wir später zu sprechen kommen.
21.4
Eine minimale Klasse in Python
Die wichtigsten Begriffe der objektorientierten Programmierung und ihrer Umsetzung in Python werden wir im Folgenden an einem Beispiel „Roboterklasse”
demonstrieren. Wir beginnen mit der einfachstmöglichen Klasse in Python, die wir
Roboter nennen.
class Roboter:
pass
An diesem Beispiel können wir den grund- Bild 21.3 Evolution der Roboter
legenden syntaktischen Aufbau einer Klasse erkennen: Eine Klassendefinition besteht aus zwei Teilen: dem Kopf und dem Körper.
Der Kopf besteht meist nur aus einer Zeile: dem Schlüsselwort class, gefolgt von einem
Leerzeichen, einem beliebigen Namen – in unserem Fall Roboter –, einer kommaseparierten Auflistung von Oberklassen in Klammern und als letztes Zeichen ein Doppelpunkt. Gibt
es keine Oberklassen, entfällt die Angabe der Oberklassen und der Klammern.4 Der Körper
einer Klasse besteht aus einer eingerückten Folge von Anweisungen die wie in unserem
Beispiel auch nur aus einer einzigen pass-Anweisung bestehen kann.
Damit haben wir bereits eine einfache Python-Klasse mit dem Namen Roboter definiert.
Wir können diese Klasse auch benutzen:
4
are Python’s abstraction for data. All data in a Python program is represented by objects or by relations between objects. (In a sense, and in conformance to Von Neumann’s model of a “stored program computer“,
code is also represented by objects.) Every object has an identity, a type and a value.”
Prinzipiell kann auch ein leeres Klammernpaar vor dem Doppelpunkt stehen. In Python 2 musste der Klassenname von „(object)” gefolgt werden. Dies bedeutet, dass die Klasse von der allgemeinen Klasse „object” erbt. Dies kann man auch in Python 3 so schreiben, ist aber nicht mehr nötig, da alle Klassen automatisch von „object” erben.
21.5 Eigenschaften und Attribute
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class Roboter:
pass
if __name__ == "__main__":
x = Roboter()
y = Roboter()
y2 = y
print(y == y2)
print(y == x)
Wir haben in obigem Beispiel zwei verschiedene Roboter x und y geschaffen. Außerdem
haben wir mit y2 = y ein Alias y2 für y erzeugt, d.h. ein weiterer Name für das gleiche
Objekt, also eine Referenz.5 Das Programm liefert folgende Ausgaben:
True
False
21.5
Eigenschaften und Attribute
Unsere Roboter haben keinerlei Eigenschaften – noch nicht einmal Namen, wie dies für
„ordentliche” Roboter üblich ist. Als weitere Eigenschaften wären beispielsweise eine Typbezeichnung, Baujahr und so weiter denkbar. Eigenschaften werden in der objektorientierten Programmierung als Attribute bezeichnet. 6
Einer Instanz kann man beliebige Attributnamen zuordnen. Sie werden mit einem Punkt
an den Namen der Instanz angeschlossen. Man bezeichnet diese Attribute als Instanzattribute. Im Folgenden erzeugen wir dynamisch Attribute für den Roboternamen name und
das Baujahr baujahr. Bitte beachten Sie, dass dies noch nicht die Art und Weise ist, wie
man normalerweise Attribute in Klassen verwendet:
>>>
...
...
>>>
>>>
>>>
>>>
>>>
>>>
>>>
5
6
class Roboter:
pass
x = Roboter()
y = Roboter()
x.name = "Marvin"
x.baujahr = 1979
y.name = "Caliban"
y.baujahr = 1993
In der if-Anweisung haben wir die Systemvariable __name__ benutzt. Sie wird automatisch entweder auf
den String __
main__¨ gesetzt oder enthält den Namen des Moduls, wenn das Modul, in dem __name__
¨
vorkommt, importiert wird. Näheres dazu in Kapitel Tests und Fehler, Seite 365.
Der Begriff Attribut stammt vom lateinischen „attribuere”, was „zuweisen” oder „zuteilen” bedeutet. Dieser
Begriff wird übrigens auch in der Philosophie verwendet, wo er die einem Gegenstand oder Objekt zugewiesene Eigenschaft bezeichnet.
205
206
21 Grundlegende Aspekte
>>> print(x.name)
Marvin
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Nur für diejenigen, die gerne wissen wollen, was „intern” vorgeht. Die Instanzen besitzen
Dictionaries __dict__ , in denen die Attribute verwaltet werden:
>>> x.__dict__
{'baujahr': 1979, 'name': 'Marvin'}
>>> y.__dict__
{'baujahr': 1993, 'name': 'Caliban'}
>>>
Attribute können übrigens auch dem Klassenobjekt selbst zugeordnet werden. Diese bezeichnet man dann als Klassenattribute. Klassenattribute sind Attribute, die gewissermaßen gemeinsam für alle Instanzen sind. Beispielsweise können wir ein Klassenattribut
anzahl einführen, das die Anzahl aller Instanzen beinhaltet. Es ist klar, dass dieses Attribut
nichts mit einem einzelnen Roboter zu tun hat:
>>>
...
...
>>>
>>>
>>>
>>>
1
class Roboter:
pass
Roboter.anzahl = 0
x = Roboter()
Roboter.anzahl += 1
print(Roboter.anzahl)
Klassenattribute können auch von Instanzen derselben Klasse angesprochen werden:
>>> class Roboter:
...
pass
...
>>> Roboter.marke = "Kuka"
>>>
>>> x = Roboter()
>>> x.marke
'Kuka'
>>> y = Roboter()
>>> y.marke = "Atlas"
>>> x.marke
'Kuka'
>>> y.marke
'Atlas'
Aus dem obigen Beispiel können wir ersehen, dass wir bei einer Roboterinstanz solange
dasselbe Markenattribut wie das Klassenattribut haben, bis wir für diese Instanz ein eigenes Attribut erstellen. Was genau passiert, können wir ersehen, wenn wir uns die entsprechenden __dict__-Dictionarys anschauen. Wir sehen, dass auf das Klassenattribut nur
dann bei einer Instanz zugegriffen wird, wenn es kein solches Attribut im __dict__ der
Instanz gibt:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.5 Eigenschaften und Attribute
>>> x.__dict__
{}
>>> y.__dict__
{'marke': 'Atlas'}
>>>
>>> Roboter.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None, '__weakref__
': <attribute '__weakref__' of 'Roboter' objects>, '__dict__': <
attribute '__dict__' of 'Roboter' objects>, 'marke': 'Kuka'})
Auch Instanzen anderer Klassen, wie beispielsweise der Funktions- und Modulklassen,
können wir dynamisch Attribute zuordnen:7
>>>
...
...
>>>
>>>
0
>>>
>>>
def f(x):
return x + 42
f.calls = 0
print(f.calls)
import math
math.mathematicians = ["Evariste Galois", "Andrew Wiles"]
Attribute bei Funktionen können zum Beispiel als Ersatz für statische Variablen benutzt
werden. In der folgenden Funktion wird das Attribut aufrufe benutzt, um zu zählen, wie
oft die Funktion aufgerufen wird:
def f(x):
if hasattr(f, "aufrufe"):
f.aufrufe += 1
else:
f.aufrufe = 1
return x + 3
for i in range(10):
f(i)
print(f.aufrufe)
Alternativ können wir die Funktion f auch unter Benutzung der Funktion getattr schreiben. getattr liefert den Wert eines Attributs zurück, wenn ein Attribut existiert, ansonsten
einen Default-Wert – in unserem Fall 0:
def f(x):
f.aufrufe = getattr(f, "aufrufe", 0) + 1
return x + 3
Wenn wir Instanzattribute für unsere Roboterklasse erzeugen wollen, so müssen wir dies
unmittelbar in der Klassendefinition tun. Instanzattribute sind die Eigenschaften, die die
7
Allerdings funktioniert dies nicht für alle Klassen. Es geht beispielsweise nicht bei der int-, float- und strKlasse. Dies wird nach dem Kapitel über slots klar.
207
208
21 Grundlegende Aspekte
einzelnen Instanzen beschreiben, d.h. so haben unsere Roboter im Allgemeinen verschiedene Namen und sicherlich eine verschiedene Seriennummer. Wir benötigen Methoden,
um Instanzattribute in einer Klassendefinition zu erzeugen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.6
Methoden
Um zu zeigen, wie man Methoden in einer Klasse definiert,
werden wir unsere leere Roboterklasse um eine Methode
sage_hallo erweitern. Eine Methode unterscheidet sich äußerlich nur in zwei Aspekten von einer Funktion:
■
■
Sie ist eine Funktion, die innerhalb einer class-Definition
definiert ist.8
Der erste Parameter einer Methode ist immer eine Referenz auf die Instanz, von der sie aufgerufen wird. Diese Referenz wird üblicherweise mit „self” benamt. 9
Wir erweitern nun unsere Roboterklasse um die Methode
sage_hallo, die beim Aufruf einfach nur „Hallo” schreibt:
Bild 21.4 Hi, ich bin Marvin
class Roboter:
def sage_hallo(self):
print("Hallo")
if __name__ == "__main__":
x = Roboter()
x.sage_hallo()
Wir sehen im Code, dass der Parameter „self” nur bei der Definition einer Methode erscheint. Beim Aufruf wird er nicht angegeben. Im Vergleich zu Funktionsaufrufen ist das
zunächst einmal befremdlich, d.h. wir definieren eine Methode mit einem Parameter „self”
und rufen sie scheinbar ohne Parameter auf. Aber wenn wir genauer auf den Aufruf schauen, sehen wir, dass wir ja nicht nur sage_hallo() aufrufen, sondern dass die Instanz x
vor dem Punkt erscheint. Darin liegt das Geheimnis: Es ist gewissermaßen so, als hätten
wir sage_hallo(x) aufgerufen. In anderen Worten, wir übergeben eine Referenz auf die
Instanz x an self.
Zum weiteren Verständnis: Eigentlich müsste man die Methode einer Klasse über den
Klassennamen aufrufen. In diesem Fall wird die Instanz als Argument übergeben, also
Roboter.sage_hallo(x). Weil dies aber zum einen besonders unhandlich ist und außerdem nicht den üblichen Gepflogenheiten in der OOP entspricht, bindet Python alle
Methoden automatisch an die Klasseninstanzen.
8
9
Der Name der Funktion ist ein Klassenattribut!
Dies ist nur eine Konvention. Prinzipiell könnte man einen beliebigen Namen wählen, also auch „this”, was
Java oder C++-Programmierern vielleicht besser gefallen würde.
21.7 Instanzvariablen
21.7
Instanzvariablen
Unsere Roboter sollen noch freundlicher werden. Sie sollen nicht nur „Hallo” sagen, sondern sich dabei auch mit ihrem Namen und Baujahr vorstellen. Dazu ändern wir unsere
Methode sage_hallo entsprechend ab. Damit sind wir wieder zurück bei den Instanzattributen. Denn eine Instanz muss sich ihren Namen und Baujahr merken können.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class Roboter:
def sage_hallo(self):
print("Hallo, mein Name ist " + self.name + ",")
print("gebaut im Jahre " + str(self.baujahr))
if __name__ == "__main__":
x = Roboter()
x.name = "Marvin"
x.baujahr = 1979
y = Roboter()
y.name = "Caliban"
y.baujahr = 1993
x.sage_hallo()
y.sage_hallo()
Auch wenn die Ausgabe des obigen Programms klar sein sollte, zeigen wir im Folgenden
die Ergebnisse:
Hallo,
gebaut
Hallo,
gebaut
mein Name ist Marvin,
im Jahre 1979
mein Name ist Caliban,
im Jahre 1993
In unserer obigen Implementierung der Roboterklasse verbirgt sich noch ein Designproblem. Wenn wir einen Roboter neu schaffen, müssen wir jedes Mal drei Anweisungen
durchführen. Zuerst müssen wir einen Roboter mit x = Roboter() instanziieren und
dann müssen wir die Attribute setzen, also x.name = "Marvin" und x.baujahr = 1979.
Dieses Vorgehen ist umständlich und fehlerträchtig und entspricht vor allen Dingen nicht
dem üblichen Vorgehen in der OOP.
21.8
Die __init__-Methode
Schön wäre es, wenn wir direkt bei der Instanziierung den Namen und das Baujahr setzen
bzw. übergeben könnten, also x = Roboter("Marvin", 1979). Für diesen Zweck bietet
Python eine Methode mit dem Namen __init__. Der Name dieser Methode ist festgelegt
und kann nicht frei gewählt werden! __init__ gehört zu den sogenannten magischen Methoden, von denen wir in den folgenden Kapiteln noch weitere kennenlernen werden. Die
209
210
21 Grundlegende Aspekte
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
__init__-Methode dient, wie der Name nahelegt, der Initialisierung einer Instanz. Python
besitzt keinen expliziten Konstruktor bzw. Destruktor, wie man sie in Java oder C++ kennt.
Der eigentliche Konstruktor wird implizit von Python gestartet. Die __init__-Methode
wird jedoch unmittelbar nach dem eigentlichen Konstruktor gestartet, und dadurch entsteht der Eindruck, als handele es sich um einen Konstruktor. Die __init__-Methode kann
an beliebiger Stelle in der Klassendefinition stehen, sollte aber nach Konvention immer die
erste Methode direkt unter dem Klassenheader sein.
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
def sage_hallo(self):
print("Hallo, mein Name ist " + self.name)
if __name__ == "__main__":
x = Roboter("Marvin", 1979)
y = Roboter("Caliban", 1993)
x.sage_hallo()
y.sage_hallo()
Schreibt man x = Roboter("Marvin", 1979), dann verhält sich das logisch gesehen so,
als würde man erst eine Instanz x instanziieren, also x = Roboter()10 , und dann die
__init__-Methode aufrufen mit x.__init__("Bob", 2014).
Der Benutzung der Klasse sieht nun deutlich „aufgeräumter” und klarer aus. Zuvor wollen
wir jedoch noch eine Fehlermeldung demonstrieren, die oft bei Anfängern Fragen aufwirft.
Wir versuchen, in der folgenden interaktiven Python-Shell einen Roboter zu erzeugen. Statt
der korrekten Instanziierung x = Roboter("Marvin", 1979) rufen wir Roboter ohne Parameter auf:
>>> from roboter import Roboter
>>> x = Roboter()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 3 arguments (1 given)
Wir wissen, dass wir einen Namen und ein Baujahr bei der Definition eines neuen Roboters
angeben müssen. Auf den ersten Blick erscheint deshalb die Fehlermeldung „__init__()
takes exactly 3 arguments (1 given)” verwunderlich. Wir hatten doch kein Argument übergeben, und zwei hätten wir übergeben müssen? Schauen wir auf die genaue Definition von
__init__(), klärt sich der scheinbare Widerspruch sofort, denn diese Methode hat wirklich drei Parameter: self, name, baujahr „self” wird implizit generiert, daher die Meldung
„1 given”.
10
Dies geht natürlich nicht mehr wegen unserer __init__-Methode, die zwei Argumente beim Aufruf zwingend fordert!
21.9 Destruktor
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.9
Destruktor
Für eine Klasse kann man auch die Methode __del__ definieren. Sie wird aufgerufen, bevor eine Instanz zerstört wird. Sie wird häufig auch als Destruktor bezeichnet, obwohl es
sich eigentlich nicht um den Destruktor handelt. Wenn man eine Instanz einer Klasse mit
del löscht, wird die Methode __del__ vor dem eigentlichen Destruktor aufgerufen. Allerdings nur, falls es keine weitere Referenz auf diese Instanz gibt. Destruktoren werden selten
benutzt, da man sich normalerweise nicht um das Aufräumen im Speicher kümmern muss.
Im Folgenden sehen wir ein Beispiel mit __init__ und __del__:
class Roboter():
def __init__(self, name):
print(name + " wurde erschaffen!")
def __del__(self):
print ("Roboter wurde zerstört")
if __name__ == "__main__":
x = Roboter("Tik-Tok")
y = Roboter("Jenkins")
z = x
print("Deleting x")
del x
print("Deleting z")
del z
del y
Obiges Programm liefert folgendes Ergebnis:
Tik-Tok wurde erschaffen!
Jenkins wurde erschaffen!
Deleting x
Deleting z
Deleting Robot
Deleting Robot
Die Verwendung der __del__-Methode ist sehr problematisch. Kommt man auf die Idee,
den Robotern „ein persönliches Ende” zu bereiten wie im folgenden Programm, erhalten
wir eine Fehlermeldung:
class Roboter():
def __init__(self, name):
print(name + " wurde erschaffen!")
211
212
21 Grundlegende Aspekte
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __del__(self):
print(self.name + " sagt bye-bye. ")
print("Es gibt " + self.name + " ihn nun nicht mehr!")
if __name__ == "__main__":
x = Roboter("Tik-Tok")
y = Roboter("Jenkins")
z = x
print("Deleting x")
del x
print("Deleting z")
del z
del y
Wir erhalten eine Ausgabe mit Fehlermeldungen:
Tik-Tok wurde erschaffen!
Jenkins wurde erschaffen!
Deleting x
Deleting z
Exception AttributeError: "'Roboter'
in <bound method Roboter.__del__
xb71c664c>> ignored
Exception AttributeError: "'Roboter'
in <bound method Roboter.__del__
object has no attribute 'name'"
of <__main__.Roboter object at 0
object has no attribute 'name'"
of <__main__.Roboter object at 0
xb71d732c>> ignored
Wir greifen auf das Attribut „name” zu, was aber in __del__ in diesem Fall bereits nicht
mehr vorhanden ist. Wir werden später nochmals intensiv auf diese Problematik eingehen.
21.10
21.10.1
Datenkapselung, Datenabstraktion
und Geheimnisprinzip
Definitionen
Unter Datenkapselung versteht man den Schutz von Daten bzw. Attributen vor dem unmittelbaren Zugriff. Der Zugriff auf die Daten bzw. Attribute erfolgt meistens über entsprechende Methoden, die man auch als Zugriffsmethoden bezeichnet. Diese Methoden
dienen dazu, invariante Interfaces für die Klassenbenutzung zu schaffen und Implementierungsdetails zu verbergen. Dadurch soll gewährleistet werden, dass man jederzeit Änderungen an der Implementierung vornehmen kann, ohne dass sich die Benutzersicht, also
das Interface, ändert. Deshalb werden in der OOP für Attribute meistens zwei Zugriffsme-
21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
thoden bereitgestellt: eine Methode, die einem den Wert des Attributs liefert – als Getter11
oder als Abfragemethode bezeichnet –, und eine andere, mit deren Hilfe man den Wert
eines Attributs verändern kann – als Setter12 oder als Änderungsmethode benannt. Wir
werden in den weiteren Ausführungen zur OOP zeigen, dass es in Python eine überlegene
Alternative zu dem „zwanghaften” Getter-Setter-Vorgehen von Java und C++ gibt.
Bild 21.5 Datenabstraktion = Datenkapselung + Geheimnisprinzip
Wenn wir von „Schutz von Daten bzw. Attributen vor dem unmittelbaren Zugriff” reden,
dann bedeutet das natürlich nicht automatisch auch, dass die Daten außen nicht sichtbar
sind. Ist dies gewünscht, dann spricht man vom Geheimnisprinzip13 . Unter dem Geheimnisprinzip versteht man also das Verbergen von internen Informationen und Implementierungsdetails nach außen. Eine andere Sichtweise des Geheimnisprinzips besagt, dass die
Benutzer einer Klasse nicht mit unnötigen Implementierungsdetails belastet werden. Die
außen unbedingt nötigen Daten und Informationen werden nur über definierte Schnittstellen bzw. Methoden nach außen sichtbar gemacht. Sind Daten „sichtbar”, bedeutet das
allerdings nicht in jedem Fall, dass diese auch benutzbar, also lesbar bzw. veränderbar sind.
Kann man auf Daten nicht zugreifen, spricht man von Datenkapselung. So gesehen beleuchten Datenkapselung und Geheimnisprinzip die zwei Seiten einer Medaille. Beide dienen dazu zu verhindern, dass Anwender der Klasse Implementierungsdetails nutzen. Um
beiden Seiten gerecht zu werden, benutzen viele deshalb auch den allgemeineren Begriff
„Datenabstraktion”. Man könnte folgende Gleichung aufstellen:
Datenabstraktion = Datenkapselung + Geheimnisprinzip
Allerdings werden die drei Begriffe in der Literatur zumeist synonym verwendet, was zu
einem bestimmten Grad auch für dieses Buch gilt.
Wie sieht es nun mit der Datenabstraktion in unserer Beispielklasse aus? Betrachten wir
dazu die folgende interaktive Python-Session. Wir nehmen dabei an, dass unsere Roboterklasse als Datei unter dem Namen roboter.py existiert:
>>> from roboter import Roboter
>>> x = Roboter("Marvin", 1979)
>>> print(x.name)
Marvin
11
12
13
Englisch „to get” im Sinne von etwas „holen” oder „besorgen”
Englisch „to set” im Sinne von etwas „festsetzen”, „festlegen” oder „einstellen”
Im Englischen unter „Information Hiding” bekannt
213
214
21 Grundlegende Aspekte
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> x.name = "Egon"
>>> print(x.name)
Egon
Wir sehen, dass wir als Benutzer der Klasse Roboter lesend und schreibend auf die Attribute zugreifen können. Dies ist auch in Ordnung so, da es sich bei den Attributen name und
baujahr um öffentliche (engl. public) Attribute handelt, d.h. man kann und darf sie als Anwenderin oder Anwender der Klasse benutzen. Dies bedeutet, dass es in unserer Beispielklasse keine Datenabstraktion gibt. Es gab hierzu auch noch keine Notwendigkeit. Stellen
wir uns nun vor, dass uns als Designer der Roboterklasse eine Beschwerde der Robotergewerkschaft erreicht. Sie verlangen, dass wir verhindern, dass Roboter „Egon” genannt
werden, da sie diesen Namen als nicht statthaft für Roboter empfinden.
Zur Lösung dieses Problems gibt es in Python generell zwei Möglichkeiten. Einmal kann
man es über spezielle Zugriffsmethoden, die Änderungsmethoden (auch Getter genannt)
und die Abfragemethoden (häufig auch als Setter-Methoden bezeichnet) bewerkstelligen;
zum anderen kann man – was der pythonische Weg ist – dies mit Properties erreichen.
21.10.2
Zugriffsmethoden
Die eigentlichen Attribute, also in unserem Beispiel der Name und das Baujahr, werden
nun in private-Attributen versteckt, d.h. __name. Zwei Unterstriche vor einem Attributnamen machen den Namen zu einem privaten Attribut. Damit können Benutzer der Klasse
nicht mehr darauf zugreifen. Dennoch kann man die Werte als Benutzer der Klasse über
die Zugriffsmethoden abfragen und verändern. In unserem Fall get_name und set_name.
Wir sehen im folgenden Programm, dass wir auch in der __init__-Methode set_name benutzen, da wir auch dort sicherstellen wollen, dass ein Roboter nicht „Egon” genannt werden kann. Das Problem mit der Robotergewerkschaft haben wir gelöst, indem ein Roboter
zwangsweise in „Marvin” getauft wird, falls ihn jemand „Egon” nennen sollte.
class Roboter:
def __init__(self, name, baujahr):
self.set_name(name)
self.baujahr = baujahr
def sage_hallo(self):
print("Hallo, mein Name ist " + self.get_name())
def get_name(self):
return self.__name
def set_name(self, name):
if name == "Egon":
self.__name = "Marvin"
else:
self.__name = name
21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip
if __name__ == "__main__":
x = Roboter("Egon", 1979)
x.sage_hallo()
x.set_name("Henry")
x.sage_hallo()
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Als Ergebnis erhalten wir:
Hallo, mein Name ist Marvin
Hallo, mein Name ist Henry
Mit diesem Vorgehen haben wir nun zwar die Robotergewerkschaft zufriedengestellt, aber
wir haben die Prinzipien des objektorientierten Programmierens verletzt. Eine Klasse kann
man auch als Schnittstelle ansehen, die zwar in neuen Versionen durch Hinzunahme von
weiteren Methoden erweitert werden darf, aber bestehende Methoden oder Attribute dürfen ihr Schnittstellenverhalten nicht ändern. In unserem Fall ist dies aber passiert. Wir hatten ein public-Attribut name, was es nun nicht mehr gibt. Dies bedeutet, dass Programme,
die unsere Roboter-Klasse benutzen (zum Beispiel Zuweisungen der Art y = x.name" enthalten) Ausnahmen erzeugen. Im nächsten Unterkapitel lernen wir die Properties kennen,
mit denen sich dieses Problem vermeiden lässt.
21.10.3
Properties
Wir haben unsere Roboter-Klasse so abgeändert, dass wir auf den Roboternamen nur noch
über die Methoden get_name und set_name zugreifen können.
Stellen wir uns vor, dass wir einen Roboter x und y haben. Nun soll x so wie y heißen.
Dies können wir mit der Anweisung x.set_name(y.get_name()) erreichen. Deutlich bequemer und vielleicht auch ein wenig leichter lesbar gestaltete sich die Umbenennung, als
wir noch direkt auf ein public-Attribut zugreifen konnten, also als wir die Umbenennung
über x.name = y.name vornehmen durften. Mit den Properties können wir diese bequeme Schreibweise wieder ermöglichen, ohne die Kapselung des privaten Attributs __name
zu verletzen.
Durch den Aufruf name = property(get_name, set_name) erzeugen wir das propertyObjekt name. Das erste Argument von property muss der Name des Getters sein und das
zweite entsprechend der Name des Setters. Man kann nun name wie ein public-Attribut
nutzen, aber in Wirklichkeit werden entsprechend die Getter und Setter und Funktionen
verwendet. Wenn wir also x.name = y.name schreiben, dann wird diese Zuweisung über
den Property-Mechanismus in x.set_name(y.get_name()) gewandelt. Auch wenn es nun
wieder so aussieht, als erzeugten wir in der __init__-Methode ein public-Attribut name,
handelt es sich auch hier um den Aufruf der Property, d.h. es verhält sich so, als hätten wir
self.set_name(name) geschrieben.
215
216
21 Grundlegende Aspekte
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def sage_hallo(self):
print("Hallo, mein Name ist " + self.name)
def get_name(self):
return self.__name
def set_name(self, name):
if name == "Egon":
self.__name = "Marvin"
else:
self.__name = name
name = property(get_name, set_name)
if __name__ == "__main__":
x = Roboter("Marvin", 2017)
y = Roboter("Eva", 2017)
x.sage_hallo()
x.name = "Adam"
y.name = x.name
y.sage_hallo()
Wenn wir das Programm laufen lassen, erkennen wir, dass unsere Namensumbenennung
erfolgreich gewesen ist:
Hallo, mein Name ist Marvin
Hallo, mein Name ist Adam
In unserer obigen Implementierung gibt es aber noch einen Design-Fehler. Klassennutzer können nun sowohl über die Property name als auch über die Methoden get_name
und set_name den Namen eines Roboters lesen und ändern. Es ist aber ein Grundsatz der
Python-Philosophie, dass es nur einen Weg geben sollte. Wir erreichen dies, indem wir die
beiden Methoden zu privaten machen. Bevor wir jedoch unser Beispiel entsprechend anpassen, wollen wir zunächst die Unterschiede zwischen privaten, geschützten und öffentlichen Attributen im folgenden Kapitel erklären.
21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.10.4
Public-, Protected- und Private-Attribute
Wer kennt nicht die Klischees in Filmen und Erzählungen von schießwütigen Farmern, die sofort losballern, wenn jemand ihr Grundstück betritt.
Natürlich hat diese Person willentlich oder auch versehentlich das ansonsten kaum zu übersehene Schild mit der Aufschrift „Private Propterty NO Trespassing” übersehen. Besonders irritierend ist dabei häufig die
Bild 21.6 No
Tatsache, dass die Grundstücke in keinster Weise eingezäunt sind. Aber Trespassing
es gibt auch die Variante, in denen die Grundstücke mit hohen Zäunen,
manchmal gar mit Stacheldraht gesichert sind. Aber egal ob der Zugang durch bauliche
Maßnahmen erschwert wurde oder nicht, sobald man ein solches Grundstück betritt, begeht man Hausfriedensbruch.14
Im Gegensatz zum Privatbesitz, zu dem einem der Zutritt verwehrt werden kann, gibt es
auch öffentliche Räume wie Straßen oder Plätze, die von allen genutzt werden dürfen.
Dazwischen gibt es noch eine besondere Form: Privatbesitz darf genutzt
werden, aber nur auf eigene Gefahr. Sie ahnen es sicherlich schon. Wir
brauchen diese Arten von Zugangsberechtigungen für unsere Attribute.
Wählt man Namen ohne führenden oder führende Unterstriche als Attribute einer Klasseninstanz, so sind diese öffentlich zugänglich, d.h. von Bild 21.7 Auf
außen, also außerhalb der Klassendefinition. Wichtig ist vor allen Dingen eigene Gefahr
die Tatsache, dass sie nicht nur von außen, also von den Benutzern der
Klasse, genutzt werden können, sondern dass es sich bei dieser Nutzung um einen ordnungsgemäßen Gebrauch der Klasse handelt. Möchte man verhindern, dass ein Attribut
lesend oder schreibend von den Benutzern der Klasse genutzt werden kann, stellt Python
zwei Möglichkeiten zur Verfügung. Jedes Attribut, welches mit genau einem Unterstrich
beginnt, ist „protected”. In diesem Fall kann man zwar immer noch von außen lesend
und schreibend auf das Attribut zugreifen, aber durch den Unterstrich hat man klar gemacht, dass dies „verboten” oder „nicht erwünscht” ist. Dies entspricht in etwa unserem
„No Trespassing”-Schild auf einem Grundstück, das ansonsten keinerlei bauliche Maßnahmen hat, die ein Eindringen verhindern. Die „baulichen Maßnahmen” erfolgen erst, wenn
man den Namen eines Attributs mit zwei Unterstrichen beginnen lässt. Ein solches Attribut
ist ein „private”-Attribut, auf das von außen nicht zugriffen werden kann.15
In der Tabelle auf der nächsten Seite haben wir die verschiedenen Attributarten nochmals
zusammengefasst:
Schauen wir uns das Verhalten der verschiedenen Attribute in einer Beispielklasse an:
class A():
def __init__(self):
self.__priv = "Ich bin privat"
self._prot = "Ich bin protected"
self.pub = "Ich bin öffentlich"
14
15
Naja, ist sicherlich nicht ganz juristisch korrekt, und wir haben auch bewusst offen gelassen, um welches
Land oder welchen Staat es geht.
Es gibt jedoch einen Weg, wie man dennoch von außen zugreifen kann: Auf ein Attribut wie in unserem
Beispiel __baujahr kann man für eine Instanz x mittels x._Roboter__baujahr zugreifen. Dies sollte man
jedoch keinesfalls tun!
217
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
218
21 Grundlegende Aspekte
Namen
Bezeichnung
Bedeutung
name
Public
Attribute ohne führende Unterstriche sind sowohl innerhalb einer
Klasse als auch von außen les- und schreibbar.
_name
Protected
Man kann zwar auch von außen lesend und schreibend zugreifen,
aber der Entwickler macht damit klar, dass man diese Member nicht
benutzen sollte. Protected-Attribute sind insbesondere bei Vererbungen wichtig.
__name
Private
Sind von außen nicht sichtbar und nicht benutzbar.
Obige Beispielklasse speichern wir unter attributes.py und testen sie wie folgt:
>>> from attributes import A
>>> x = A()
>>> x.pub
'Ich bin öffentlich'
>>> x.pub = "Man kann meinen Wert ändern und das ist gut so"
>>> x.pub
'Man kann meinen Wert ändern und das ist gut so'
>>>
>>> x._prot
'Ich bin protected'
>>> x._prot = "Das darf man nur in erbenden Klassen tun!"
>>> x._prot
'Das darf man nur in erbenden Klassen tun!'
>>>
>>> x.__priv
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__priv'
Interessant ist vor allen Dingen die Fehlermeldung
AttributeError: ’A’ object has no attribute ’__priv’.
Eigentlich würde man als Fehlermeldung erwarten, dass man auf das Attribut __priv nicht
zugreifen darf, da es „private” ist. Stattdessen kommt die obige Meldung, die so aussieht,
als gäbe es kein Attribut __priv in der Klasse A.
21.10.5
Weitere Möglichkeiten der Properties
Wir kommen nun zurück auf unser Beispiel vom Unterkapitel 21.10.3 (Properties):
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip
def sage_hallo(self):
print("Hallo, mein Name ist " + self.name)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __get_name(self):
return self.__name
def __set_name(self, name):
if name == "Egon":
self.__name = "Marvin"
else:
self.__name = name
name = property(__get_name, __set_name)
Aus dem bisher Gesagten und aus den ansonsten in der Literatur und anderen Tutorials vorherrschenden Beispielen könnte man leicht den Eindruck gewinnen, dass
es zwischen Properties und privaten Attributen immer eine Eins-zu-eins-Beziehung
gibt. Also zu einer Property gibt es genau ein privates Attribut und umgekehrt. Dass
dem nicht so ist, wollen wir im folgenden Beispiel zeigen. Wir definieren dazu zwei private Attribute self.__leistung_koerper, als Maß für das körperliche Wohlbefinden,
und self.__leistung_psyche, als Maß für das psychische Wohlbefinden. Die Property
„befinden” verknüpft dann diese beiden privaten Attribute unter Benutzung der privaten
Methode __befinden, die in salopper Weise das allgemeine Wohlbefinden des Roboters
verkündet:
class Roboter:
def __init__(self, name, baujahr, lk = 0.5, lp = 0.5 ):
self.name = name
self.baujahr = baujahr
self.__leistung_koerper = lk
self.__leistung_psyche = lp
def __befinden(self):
s = self.__leistung_koerper + self.__leistung_psyche
if s <= -1:
return "Fühle mich miserabel!"
elif s <= 0:
return "Fühle mich schlecht!"
elif s <= 0.5:
return "Könnte besser sein!"
elif s <= 1:
return "Es geht mir gut!"
else:
return "Super!"
befinden = property(__befinden)
219
220
21 Grundlegende Aspekte
if __name__ == "__main__":
x = Roboter("Marvin", 1979, 0.2, 0.4 )
y = Roboter("Caliban", 1993, -0.4, 0.3)
print(x.befinden)
print(y.befinden)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Obiges Programm liefert folgende Ergebnisse zurück:
Es geht mir gut!
Fühle mich schlecht!
Eine Property kann neben Getter und Setter auch eine Deleter-Methode als drittes Argument sowie einen Docstring als viertes Argument aufnehmen:
property(fget=None, fset=None, fdel=None, doc=None)
Wir demonstrieren dies im folgenden Beispiel:
class P:
def __init__(self,x):
self.__x = x
print(x)
def __getX(self):
try:
return self.__x
except AttributeError:
return None
def __setX(self, x):
self.__x = x
print(hasattr(self,"__x"))
def __delX(self):
del self.__x
doc = "Die Eigenschaft x, wozu auch immer sie gut sein mag!"
x = property(__getX, __setX, __delX, doc)
Importieren wir das Programm mit from properties import P16 , erhalten wir mit
help(P) am Schluss folgende Ausgabe:
| Data and other attributes defined here:
|
| doc = 'Die Eigenschaft x, wozu auch immer sie gut sein mag!'
(END)
16
Programm muss unter properties.py abgespeichert sein.
21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip
Wegen der Deleter-Methode mussten wir die __getX-Methode umschreiben, da es nun
möglich ist, dass das Attribut __x nicht existiert, wenn es vorher durch __delX gelöscht
worden ist. Wir geben nun None zurück, wenn das Attribut __x nicht existiert.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.10.6
Properties mit Dekorateuren
Properties lassen sich mithilfe von Dekorateuren deutlich einfacher schreiben. Wir demonstrieren dies exemplarisch an unserer Roboter-Klasse. Die Namen der Getter- und der
Setter-Funktionen benennt man in den gewünschten Namen der Property um, also in unserem Fall schreiben wir name statt __get_name und __set_name. Außerdem dekorieren
wir die Getter-Methode mit @property, d.h. wir schreiben diese Zeile über den Funktionsheader. Die Setter-Methode wird mit "@" + gewünschter Property-Name + "setter" dekoriert. Die Zeile name = property(__get_name, __set_name) ist nun nicht mehr nötig
bzw. nicht mehr möglich. Der Code für unsere Roboter-Klasse sieht nun wie folgt aus:
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
if name == "Egon":
self.__name = "Marvin"
else:
self.__name = name
Die P-Klasse, die auch einen deleter und einen Docstring enthält, können wir wie folgt mit
der Dekorationstechnik schreiben. In diesem Fall dient der Docstring der Setter-Methode
als Docstring für die Property:
class P:
def __init__(self,x):
self.x = x
@property
def x(self):
""" Die Eigenschaft x,
wozu auch immer sie gut sein mag!"""
try:
return self.__x
except AttributeError:
return None
@x.setter
def x(self, x):
self.__x = x
221
222
21 Grundlegende Aspekte
@x.deleter
def x(self):
del self.__x
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.11
__str__- und __repr__
Wir schauen uns nun an, was passiert, wenn wir eine Roboter-Instanz in einer printFunktion verwenden:
>>> x = Roboter("Marvin", 2017)
>>> print(x)
<robots.Roboter object at 0x7f5e5e9110b8>
Es ist erfreulich, dass wir keinen Fehler erzeugt haben, aber die Ausgabe ist weder schön
noch aussagekräftig. Die print-Funktion kann eigentlich nur Strings ausgeben; deshalb hat
print str(x) aufgerufen, die dann obigen String generiert hatte.
Die str-Funktion ist uns im Laufe des Buches schon öfters begegnet. Wir hatten gesehen,
dass wir mit ihr verschiedenste Datentypen in eine Stringdarstellung wandeln können. Es
gibt noch eine weitere Funktion, die eine Stringdarstellung eines Objekts zurückliefert.
Dies ist die repr-Funktion. In vielen Fällen sind die Ergebnisse beider Funktionen sogar
identisch.
>>> farben = ["rot", "grün", "blau"]
>>> str(farben)
"['rot', 'grün', 'blau']"
>>> repr(farben)
"['rot', 'grün', 'blau']"
>>> print(farben)
['rot', 'grün', 'blau']
>>> s = "Aller Anfang\nist schwer!"
>>> str(s)
'Aller Anfang\nist schwer!'
>>> repr(s)
"'Aller Anfang\\nist schwer!'"
>>> print(s)
Aller Anfang
ist schwer!
Wendet man auf ein Objekt die Funktion str oder repr an, sucht Python in der Klassendefinition dieses Objekts nach Methoden mit den entsprechenden Namen __str__ und
__repr__. Sind sie vorhanden, werden sie entsprechend aufgerufen. Im Folgenden definieren wir eine Klasse A, in der wir weder __repr__ noch __str__ definieren. In diesem Fall
wird sowohl bei str und repr, aber auch bei einem einfachen print oder der direkten Ausgabe über die interaktive Shell, eine Default-Ausgabe gewählt, d.h. <__main__.A object at
0xb720a64c>:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.11 Die __str__- und die __repr__-Methode
>>> class A:
...
pass
...
>>> a = A()
>>> print(repr(a))
<__main__.A object at 0xb720a64c>
>>> print(str(a))
<__main__.A object at 0xb720a64c>
>>> a
<__main__.A object at 0xb720a64c>
Besitzt eine Klasse eine Funktion __str__, dann wird diese angewendet, wenn auf ein
Objekt dieser Klasse str angewendet oder wenn ein Objekt dieser Klasse in einer printFunktion verwendet wird. __str__ wird jedoch nicht angewendet, wenn man repr auf ein
Objekt anwendet oder wenn man sich direkt den Wert eines Objekts in der interaktiven
Python-Shell angeben lässt:
>>> class A:
...
def __str__(self):
...
return "42"
...
>>> a = A()
>>> print(repr(a))
<__main__.A object at 0x7f3c46b54710>
>>> print(str(a))
42
>>> a
<__main__.A object at 0x7f3c46b54710>
Besitzt eine Klasse nur eine Funktion __repr__, aber nicht __str__, dann wird __repr__
immer angewendet, d.h. wenn auf ein Objekt dieser Klasse str oder repr angewendet wird
oder wenn ein Objekt dieser Klasse in einer print-Funktion oder direkt in der Python-Shell
für eine Ausgabe verwendet wird:
>>>
...
...
...
>>>
>>>
42
>>>
42
>>>
42
class A:
def __repr__(self):
return "42"
a = A()
print(repr(a))
print(str(a))
a
Eine häufig gestellte Frage lautet, wann man __repr__ und wann __str__ benutzen bzw.
implementieren sollte. __str__ ist immer dann die richtige Methode, wenn es um die Ausgabe der Daten für Endbenutzer geht. __repr__ hingegen ist wichtig, wenn es um die in-
223
224
21 Grundlegende Aspekte
terne Darstellung von Daten geht. Wenn wir auf ein Objekt o die Funktion repr anwenden,
dann erhalten wir eine Stringrepräsentation des Objekts o, aus der man durch Anwendung
der Funktion eval wieder das ursprüngliche Objekt o erzeugen kann, genauer gesagt erzeugt man natürlich eine Kopie des ursprünglichen Objekts o.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Es gilt also: type(o) == type(eval(repr(o)))
Eigentlich müsste auch o == eval(repr(o)) gelten, aber da wir noch keine Vergleichsmethoden für unsere Klassenobjekte definiert haben, wird nur die Gleichheit auf die ObjektID geprüft, d.h. Kopien von Objekten gelten also als ungleich. Obige Typgleichheit gilt nicht
in jedem Fall, aber dennoch für die meisten Standardklassen. Bei __repr__ liegt der Fokus
auf der Eindeutigkeit und Unmissverständlichkeit der Datendarstellung! Ideal ist, wenn
sich ein durch repr erzeugter String wieder mittels eval in eines dem ursprünglichen Objekt äquivalentes Objekt wandeln lässt. str andererseits soll einen für menschliche Benutzer leicht verständlichen String liefern! Das bisher Gesagte wollen wir nochmals an einem
Beispiel verdeutlichen. Dazu benutzen wir das datetime-Modul:
>>> import datetime
>>> today = datetime.datetime.now()
>>> str_s = str(today)
>>> eval(str_s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1
2017-07-12 14:29:37.235032
^
SyntaxError: invalid token
>>> repr_s = repr(today)
>>> t = eval(repr_s)
>>> type(t)
<class 'datetime.datetime'>
Wir sehen, dass wir den String repr_s , den wir mittels repr erhalten, wieder mit eval in
ein datetime.datetime-Objekt wandeln können. Den durch str erzeugten String str_s
kann man jedoch nicht mehr rückwandeln.
Wir können nun unsere Roboterklasse um eine repr-Methode erweitern. Um die Darstellung übersichtlich zu halten, haben wir im folgenden Code alle anderen vorher definierten
Methoden weggelassen:
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
def __repr__(self):
return "Roboter('"+self.name+"',"+ str(self.baujahr)+ ")"
if __name__ == "__main__":
x = Roboter("Marvin", 1979)
21.11 Die __str__- und die __repr__-Methode
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
x_str = str(x)
print(x_str)
print("Typ von x_str: ", type(x_str))
neu = eval(x_str)
print(neu)
print("Typ von neu:", type(neu))
Die Anweisung print(x_str) gibt den String Roboter("Marvin",1979) aus. Wir können
diesen String wieder mittels eval(x_str) in eine Instanz neu der Klasse Roboter wandeln.
Starten wir das Programm, erhalten wir folgende Ausgaben:
Roboter("Marvin",1979)
Typ von x_str: <class 'str'>
Roboter("Marvin",1979)
Typ von neu: <class '__main__.Roboter'>
Nun erweitern wir unsere Klasse noch um eine benutzerfreundliche __str__-Methode:
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
def __repr__(self):
return "Roboter('"+self.name+"',"+ str(self.baujahr)+ ")"
def __str__(self):
return "Name: "+self.name+", Baujahr: "+ str(self.baujahr)
if __name__ == "__main__":
x = Roboter("Marvin", 1979)
x_str = str(x)
print(x_str)
print("Typ von x_str: ", type(x_str))
neu = eval(x_str)
print(neu)
print("Typ von neu:", type(neu))
Wenn wir das Programm starten, erhalten wir einen Fehler, da sich der String x_str nun
nicht mehr mittels eval in ein Objekt der Klasse Roboter wandeln lässt.
Name: Marvin, Baujahr: 1979
Typ von x_str: <class 'str'>
Traceback (most recent call last):
File "/home/data/workspace/PyRobots/robots3.py", line 20, in <module
>
neu = eval(x_str)
225
226
21 Grundlegende Aspekte
File "<string>", line 1
Name: Marvin, Baujahr: 1979
SyntaxError: invalid syntax
^
Dies liegt daran, dass wir nun eine eigene __str__-Methode definiert haben, die eine andere Ausgabe erzeugt, die sich nicht mehr mit eval verwenden lässt.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im folgenden Programm erzeugen wir mit der Anweisung x_repr = repr(x) einen „evalfähigen” String:
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
def __repr__(self):
return "Roboter('"+self.name+"',"+ str(self.baujahr)+ ")"
def __str__(self):
return "Name: "+self.name+", Baujahr: "+ str(self.baujahr)
if __name__ == "__main__":
x = Roboter("Marvin", 1979)
x_str = str(x)
print(x_str)
print("Typ von x_str: ", type(x_str))
x_repr = repr(x)
print(x_repr, type(x_repr))
neu = eval(x_repr)
print(neu)
print("Typ von neu:", type(neu))
Dieses Programm liefert uns nun die gewünschten Ausgaben:
Name: Marvin, Baujahr: 1979
Typ von x_str: <class 'str'>
Roboter("Marvin",1979) <class 'str'>
Name: Marvin, Baujahr: 1979
Typ von neu: <class '__main__.Roboter'>
Wir erweitern nun unsere Roboterklasse um die Methode __eq__. Diese Methode wird aufgerufen, wenn zwei Roboterobjekte mittels == auf Gleichheit getestet werden.
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
21.12 Klassenattribute
def __repr__(self):
return "Roboter('" + self.name + \
"',"+ str(self.baujahr)+ ")"
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __str__(self):
return "Name: " + self.name + \
", Baujahr: "+ str(self.baujahr)
def __eq__(self, other):
return self.name == other.name and self.baujahr == other.
baujahr
if __name__ == "__main__":
x = Roboter("Marvin", 1979)
neu = eval(repr(x))
print(x == neu)
print(id(x) == id(neu))
Die Ausgabe des Programms zeigt nun, dass wir repr korrekt implementiert haben, d.h. das
nun die Bedingung x == eval(repr(x)) erfüllt ist:
True
False
21.12
Klassenattribute
Bisher hatte jede Instanz einer Klasse ihre eigenen Attribute, die sich von denen
anderer Instanzen unterschieden. Man bezeichnet dies als „nicht-statisch” oder „dynamisch”, da sie für jede Instanz einer Klasse dynamisch erstellt werden. So hatten wir
beispielsweise den Namen eines Roboters
mithilfe des Instanzattributs self.__name
gespeichert. Instanzattribute sind Attribute, die für jede Instanz in der Regel einen
verschiedenen Wert annehmen, so wie ja
jeder Roboter sinnvollerweise einen anderen Namen haben sollte.
Bild 21.8 Asimovs Gesetze als Klassenattribut
Wie kann man jedoch Informationen speichern, die sich nicht auf ein bestimmtes Objekt
beziehen, sondern für die ganze Klasse relevant sind? Also Attribute, die für alle Instanzen
gleich sind. Solche Attribute könnten für unsere Roboterklasse beispielsweise der Name
des Herstellers, die Anzahl aller erzeugten Roboter oder wie in unserem folgenden Beispiel
227
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
228
21 Grundlegende Aspekte
die Asimovschen Gesetze17 sein. Diese Attribute bezeichnet man als statische Attribute. Sie
existieren unabhängig von den Instanzen oder anders ausgedrückt sind sie für alle Instanzen gleich. Statische Attribute werden auch als Klassenattribute bezeichnet, weil sie, wie
bereits gesagt, Eigenschaften bezeichnen, die für die ganze Klasse gelten und nicht nur für
einzelne Objekte der Klasse. Ein Klassenattribut existiert pro Klasse nur einmal, wird also
nur einmal angelegt. Instanzattribute werden für jedes Objekt angelegt. Statische Attribute
werden außerhalb der Instanzmethoden direkt im class-Block definiert. Außerdem muss
jedem Klassenattribut ein Initialwert zugewiesen werden. Es ist Usus, die statischen Member direkt unterhalb der class-Anweisung zu positionieren.
Wir könnten beispielsweise die Asimovschen Gesetze als Klassenattribut in unserer Roboterklasse formulieren, denn diese Gesetze gelten für jede Instanz, also für jeden Roboter,
gleichermaßen. Wir benutzen für die Implementierung ein Tupel mit Strings, d.h. jedes
Element des Tupels entspricht einem Gesetz:
class Roboter:
Gesetze = (
"""Ein Roboter darf kein menschliches Wesen wissentlich
verletzen oder durch Untätigkeit gestatten, dass
einem menschlichen Wesen wissentlich Schaden zugefügt wird.""",
"""Ein Roboter muss den ihm von einem Menschen gegebenen
Befehlen gehorchen - es sei denn, ein solcher Befehl
würde mit Regel eins kollidieren.""",
"""Ein Roboter muss seine Existenz beschützen, so lange
dieser Schutz nicht mit Regel eins oder zwei
kollidiert.""")
def __init__(self, name, baujahr):
self.__name = name
self.__baujahr = baujahr
# weitere Definitionen wie gehabt
Wir können entweder direkt über den Klassennamen mit print(Roboter.Gesetze) oder
über eine Instanz eines Roboters auf die Robotergesetze zugreifen:
>>> from robots import Roboter
>>> for nummer, text in enumerate(Roboter.Gesetze):
...
print(str(nummer+1) + ":\n" + text)
...
1:
Ein Roboter darf kein menschliches Wesen wissentlich
verletzen oder durch Untätigkeit gestatten, dass
einem menschlichen Wesen wissentlich Schaden zugefügt wird.
2:
Ein Roboter muss den ihm von einem Menschen gegebenen
17
Isaac Asimov formulierte in seiner Kurzgeschichte „Runaround” (Astounding, 1942) die Robotergesetze. Sie
werden deshalb auch als „Asimovsche Gesetze” bezeichnet.
21.13 Statische Methoden
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Befehlen gehorchen - es sei denn, ein solcher Befehl
würde mit Regel eins kollidieren.
3:
Ein Roboter muss seine Existenz beschützen, so lange
dieser Schutz nicht mit Regel eins oder zwei
kollidiert.
Im folgenden Beispiel zeigen wir, wie man Instanzen mittels einer Klassenvariablen zählen
kann. Dazu erhöhen wir die Variable counter bei der Initialisierung jeder neuen Instanz.
Wird eine Instanz gelöscht, wird die Methode __del__ aufgerufen, in der in unserem Beispiel die Klassenvariable counter um 1 vermindert wird:
class C:
counter = 0
def __init__(self):
type(self).counter += 1
def __del__(self):
type(self).counter -= 1
if __name__ == "__main__":
x = C()
print("Anzahl der Instanzen:
y = C()
print("Anzahl der Instanzen:
del x
print("Anzahl der Instanzen:
del y
print("Anzahl der Instanzen:
" + str(C.counter))
" + str(C.counter))
" + str(C.counter))
" + str(C.counter))
Im Prinzip hätten wir auch C.counter statt type(self).counter schreiben können, denn type(self) wird zu „C” ausgewertet. Wir erhalten folgende Ausgabe:
$ python3 counter.py
Anzahl der Instanzen:
Anzahl der Instanzen:
Anzahl der Instanzen:
Anzahl der Instanzen:
21.13
1
2
1
0
Statische Methoden
Der Dekorator zur Erzeugung von statischen Methoden ist sehr umstritten in der PythonWelt. Die Kritik konzentriert sich vor allem darauf, dass es keine praktischen Anwendungen für diese Dekoration gäbe. In den meisten Fällen wird diese Technik für Funktionen
229
230
21 Grundlegende Aspekte
verwendet, die nichts mit den Instanzen der Klasse zu tun haben, die man aber dennoch
gerne innerhalb der Klasse definieren will, weil man sie dort gut gebrauchen kann.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.13.1
Einleitendes Beispiel
Wir definieren eine Funktion die_antwort18 und eine Klasse Philosoph. Beide haben
nichts miteinander zu tun, außer dass sie im gleichen Modul definiert sind. Allerdings ist
die_antwort natürlich eine wichtige Funktion für eine Philosophenklasse, da sie ja den
Sinn des Lebens berechnet.
def die_antwort():
return 42
class Philosoph:
pass
Machen wir die Funktion die_antwort zu einer Methode der Klasse Philosoph, dann können wir sie nur über eine Philosophen-Instanz aufrufen:
class Philosoph:
def die_antwort(self):
return 42
plato = Philosoph()
print(plato.die_antwort())
print(Philosoph.die_antwort(plato))
Das ergibt natürlich keinen Sinn, da die „Berechnungen” dieser Funktion nicht von Instanzen abhängen. Wir können jedoch die Methode die_antwort als @staticmethod dekorieren. Dann brauchen wir keine Instanz mehr zu definieren. Wir können die Funktion direkt
über den Klassennamen ohne Instanzparameter aufrufen, wie wir im folgenden Beispiel
sehen können:
class Philosoph:
@staticmethod
def die_antwort():
return 42
print(Philosoph.die_antwort())
plato = Philosoph()
print(plato.die_antwort())
18
Im Roman „Per Anhalter durch die Galaxis” des Autors Douglas Adam ist „42” die von einem Supercomputer nach einigen Millionen Jahren Rechenzeit gegebene Antwort auf die Frage „nach dem Leben, dem
Universum und dem ganzen Rest”.
21.13 Statische Methoden
Wie wir sehen, können wir die Methode weiterhin über eine Instanz aufrufen – auch wenn
das nicht sinnvoll ist. Der bessere Weg besteht im direkten Aufruf von
Philosoph.die_antwort().
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.13.2
Getter und Setter für private Klassenattribute
Im vorigen Abschnitt hatten wir Klassenattribute als öffentliche Attribute verwendet.
Selbstverständlich können und sollten wir auch Klassenattribute als private Attribute definieren können, also mit doppeltem vorangestellten Unterstrich. Das bedeutet aber, dass
diese privaten Klassenattribute von außen nicht mehr gelesen oder manipuliert werden
können. Soll dies möglich sein, müssen wir entsprechende Methoden bereitstellen. Dazu
könnte man auch Instanzmethoden verwenden:
class Roboter:
__counter = 0
def __init__(self):
Roboter.__counter += 1
@staticmethod
def get_anzahl():
return Roboter.__counter
@staticmethod
def set_anzahl(anzahl):
Roboter.__counter = anzahl
if __name__ == "__main__":
x = Roboter()
print(x.get_anzahl())
y = Roboter()
print(x.get_anzahl())
Roboter.set_anzahl(12)
print(x.get_anzahl())
Wir erhalten die Ausgabe:
1
2
12
Neben statischen Methoden gibt es auch Klassenmethoden. Anders als statische Methoden
sind Klassenmethoden an eine Klasse gebunden. Das erste Argument einer Klassenmethode ist eine Referenz auf die Klasse, d.h. das Klassenobjekt. Aufrufen kann man sie über den
Klassennamen oder eine Instanz. Wir werden jedoch erst im Kapitel Vererbung auf Klassenmethoden zu sprechen kommen, da man zu ihrem Verständnis auch das Prinzip der
Vererbung verstanden haben muss.
231
232
21 Grundlegende Aspekte
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
21.14
Public-Attribute statt private
Attribute
In den meisten objektorientierten Programmiersprachen ist es ein ungeschriebenes Gesetz, dass man möglichst alle Attribute als „private” deklariert und für sie „Getter” und
„Setter” schreibt. Häufig sieht es dann aber so aus wie in unseren anfänglichen primitiven
Beispielen. Mit dem Getter und Setter reichten wir den entsprechenden Wert nur durch,
also zum Beispiel so:
class A:
def __init__(self, val):
self.__x = val
def getx(self):
return self.__x
def setx(self,val):
self.__x = val
Der ideale Python-Weg sieht wegen der Möglichkeiten, die sich durch die Properties ergeben, ganz anders aus. In Python definiert man diese Klasse zunächst einmal mit einem
public-Attribut:
class A:
def __init__(self, val):
self.x = val
Wir können direkt – also ohne Benutzung von Methoden – auf dieses Attribut zugreifen,
wie wir im Folgenden sehen:
>>> a = A(10)
>>> print(a.x)
10
>>> a.x = 102
Eine Horrorvorstellung für C++- oder Java-Programmierer, denn sie sehen Probleme, wenn
man später doch Zugriffsfunktionen braucht, um bestimmte Tests oder Wandlungen in
andere Datenformate vorzunehmen. Eine unberechtigte Sorge, denn man kann einfach
ein public-Attribut in eine Property wandeln. Die eigentliche Information speichern wir
in unserem Beispiel dann in einem privaten Attribut __x, für das wir Getter- und SetterMethoden schreiben. Diese Getter und Setter werden aber auch als private Methoden definiert, denn wir wollen die Information nur über die zu definierende Property x für die
Benutzer der Klasse verfügbar machen.19 Die Property x verhält sich für die Benutzer der
19
Üblicherweise benutzt man die Dekoratorsyntax für Properties, die wir im letzten Abschnitt eingeführt haben. In der hier verwendeten Syntax erkennt man jedoch leichter den Zusammenhang zu den Gettern und
Settern.
21.15 Magische Methoden und Operatorüberladung
Klasse völlig gleich wie das bisherige öffentliche Attribut x. Sie haben also das Gefühl, es
weiterhin mit einem öffentlichen Attribut zu tun zu haben. Stellen wir uns im obigen Beispiel vor, dass wir sicherstellen wollen, dass die Werte sich nur zwischen 0 und 100 bewegen
dürfen.20 Wir schreiben unsere Klasse entsprechend um:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class A:
def __init__(self, val):
self.__x = val
def __getx(self):
return self.__x
def __setx(self,val):
if val > 100:
self.__x = 100
else:
self.__x = val
x = property(__getx, __setx)
Wie wir sehen, haben die Benutzer unserer Klasse immer noch die gleiche „Sichtweise”,
d.h. das Interface ist gleich geblieben. Für sie gibt es weiterhin ein „öffentliches” Attribut x,
obwohl es sich jetzt eigentlich um eine Property handelt:
>>> a = A(10)
>>> print(a.x)
10
>>> a.x = 102
21.15
21.15.1
Operatorüberladung
Einführung
Die sogenannten magischen Methoden sind weder obskur noch haben sie etwas mit Magie
zu tun. Wir haben bereits einige kennengelernt. Instanzen werden beispielsweise mit der
__init__-Methode initialisiert, nachdem eine andere Methode __new__, die man normalerweise nicht definieren muss, eine Instanz erzeugt hatte.
20
Wir gehen davon aus, dass numerische Werte übergeben werden!
233
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
234
21 Grundlegende Aspekte
Bei der Benutzung einer Klasse erzeugt man eine Instanz, indem man nur den Klassennamen
mit einer gegebenenfalls leeren Parameterliste aufruft, also beispielsweise x = A(). Diese Anweisung wird automatisch („magisch”)
von Python durch entsprechende Aufrufe von
__new__ und __init__ ersetzt. Andere „magische” Methoden, die wir bereits kennengelernt
haben, sind __str__ und __repr__.
Die Operatorüberladung ist uns bereits häufig
im Laufe dieses Buches begegnet, ohne dass
es uns vielleicht bewusst war. Wir haben beispielsweise das Plus-Zeichen „+” sowohl für die
Addition von verschiedenen numerischen Werten, also Float- und Integer-Werten, aber auch
für die Konkatenation von Strings benutzt:
>>> a = 3 + 4
>>> a
7
>>> 3 + 5.543
8.543
>>> s = "Hello"
>>> print(s + " World")
Hello World
Bild 21.9 Einige magische Methoden
Bild 21.10 Überladen des +-Operators
Python erlaubt es, dass wir auch für eigene Klassen den „+”-Operator überladen können.
Um dies tun zu können, müssen wir jedoch den internen Mechanismus verstehen, der die
Überladung bewirkt. Für jedes Operatorzeichen gibt es eine spezielle Methode. Für „+” lautet der Name der Methode beispielsweise __add__, und für „-” lautet der Name __sub__.
Bei __add__ und __sub__ handelt es sich um binäre Operatoren, weshalb die Methoden
auch zwei Parameter benötigen: „self” und „other”. Steht in einem Skript
x + y
und sind x und y von der Klasse K, dann ruft Python die Methode __add__ mit
x.__add__(y)
auf, falls es eine solche Methode in der Klasse K gibt, ansonsten erfolgt die Fehlermeldung
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'K' and 'K'
21.15 Magische Methoden und Operatorüberladung
21.15.2
Übersicht magische Methoden
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Binäre Operatoren:
Operator
Methode
+
object.__add__(self, other)
-
object.__sub__(self, other)
*
object.__mul__(self, other)
/
object.__truediv__(self, other)
//
object.__floordiv__(self, other)
%
object.__mod__(self, other)
**
object.__pow__(self, other[, modulo])
«
object.__lshift__(self, other)
»
object.__rshift__(self, other)
&
object.__and__(self, other)
^
object.__xor__(self, other)
|
object.__or__(self, other)
Erweiterte Zuweisungen:
Operator
Methode
+=
object.__iadd__(self, other)
-=
object.__isub__(self, other)
*=
object.__imul__(self, other)
/=
object.__itruediv__(self, other)
//=
object.__ifloordiv__(self, other)
%=
object.__imod__(self, other)
**=
object.__ipow__(self, other[, modulo])
«=
object.__ilshift__(self, other)
»=
object.__irshift__(self, other)
&=
object.__iand__(self, other)
^=
object.__ixor__(self, other)
|=
object.__ior__(self, other)
Vergleichsoperatoren:
Operator
Methode
<
object.__lt__(self, other)
<=
object.__le__(self, other)
==
object.__eq__(self, other)
!=
object.__ne__(self, other)
>=
object.__ge__(self, other)
>
object.__gt__(self, other)
235
236
21 Grundlegende Aspekte
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Unäre Operatoren:
Operator
Methode
-
object.__neg__(self)
+
object.__pos__(self)
abs()
object.__abs__(self)
~
object.__invert__(self)
complex()
object.__complex__(self)
int()
object.__int__(self)
long()
object.__long__(self)
float()
object.__float__(self)
oct()
object.__oct__(self)
hex()
object.__hex__(self)
21.15.3
Beispielklasse: Length
In der folgenden Beispielklasse „Length” wollen wir exemplarisch zeigen, wie man für eine eigene Klasse eine Addition mittels des „+”-Operators durch Überladung der __add__Methode definieren kann. In der Klassendefinition befinden sich auch die magischen Methoden __str__ und __repr__, die wir bereits am Anfang des Kapitels besprochen hatten.
Instanzen der Klasse Length sind Entfernungen. Die Attribute einer Instanz stellen die Länge der Strecke self.value und die Maßeinheit für diese Länge self.unit dar.
Die Klasse ermöglicht einem das einfache Ausrechnen von Ausdrücken mit gemischten
Maßeinheiten der folgenden Art:
2.56 m + 3 yd + 7.8 in + 7.03 cm
Unter Benutzung unserer zu schreibenden Klasse sieht das dann so aus:
>>> from unit_conversions import Length as L
>>> print(L(2.56,"m") + L(3,"yd") + L(7.8,"in") + L(7.03,"cm"))
5.57162
Die Klasse sieht wie folgt aus:
class Length:
__metric = {"mm" : 0.001,
"cm" : 0.01,
"m" : 1,
"km" : 1000,
"in" : 0.0254,
"ft" : 0.3048,
"yd" : 0.9144,
"mi" : 1609.344 }
21.15 Magische Methoden und Operatorüberladung
def __init__(self, value, unit = "m" ):
self.value = value
self.unit = unit
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def converse2metres(self):
return self.value * Length.__metric[self.unit]
def __add__(self, other):
l = self.converse2metres() + other.converse2metres()
return Length(l / Length.__metric[self.unit], self.unit )
def __str__(self):
return str(self.converse2metres())
def __repr__(self):
return str((self.value, self.unit))
if __name__ == "__main__":
x = Length(4)
print(x)
print(repr(x))
y = Length(4.5, "yd") + x
print(repr(y))
print(y)
Starten wir das Programm, erhalten wir folgende Ausgaben:
4
(4, 'm')
(8.87445319335083, 'yd')
8.114799999999999
Wir benötigen die Methode __iadd__, um die erweiterte Zuweisung zu implementieren:
def __iadd__(self, other):
l = self.converse2metres() + other.converse2metres()
self.value = l / Length.__metric[self.unit]
return self
Damit sind wir dann in der Lage, Zuweisungen der folgenden Art durchzuführen:
x += Length(1)
x += Length(4, "yd")
In der ersten erweiterten Zuweisung wird 1 Meter zum Length-Objekt x hinzugezählt. Eigentlich wäre es doch viel schöner, wenn wir direkt eine Integer- oder eine Float-Zahl zu einem Length-Objekt hinzuzählen könnten, und unsere Implementierung würde diese Zahl
gewissermaßen wie ein Length-Objekt in der Einheit „Meter” behandeln. Dies lässt sich
237
238
21 Grundlegende Aspekte
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
ganz einfach realisieren. Dazu ändern wir lediglich unsere __add__-Methode so ab, dass
sie den Typ von „other” überprüft:
def __add__(self, other):
if type(other) == int or type(other) == float:
l = self.converse2metres() + other
else:
l = self.converse2metres() + other.converse2metres()
return Length(l / Length.__metric[self.unit], self.unit )
def __iadd__(self, other):
if type(other) == int or type(other) == float:
l = self.converse2metres() + other
else:
l = self.converse2metres() + other.converse2metres()
self.value = l / Length.__metric[self.unit]
return self
Wenn man mit dieser Klasse eine Weile arbeitet, stellt sich mit Sicherheit eine neue Begehrlichkeit ein. Versucht man, eine Integer- oder eine Float-Zahl auf der linken Seite und
ein Length-Objekt auf der rechten Seite bei der Addition zu verwenden, erhält man eine
Fehlermeldung:
>>> from unit_conversions import Length
>>> x = Length(3, "yd") + 5
>>> x = 5 + Length(3, "yd")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'Length'
Auch für diese Problemstellung gibt es natürlich eine Lösung in Python, und zwar in Form
der Methode __radd__. Führt der Aufruf von int.__add__(5,Length(3, "yd")) zu einer
Ausnahme, sucht Python nach der Methode __radd__ in der Klasse Length, da unser zweiter Parameter (Length(3, "yd")) eine Instanz der Klasse Length ist. Existiert diese Methode, wird sie mit Length.__radd__(Length(3, "yd"), 5) aufgerufen, was dem Aufruf
5 + Length(3, "yd") entspricht.
Bild 21.11 Vererbung von
Personeneigenschaften
Im nebenstehenden Diagramm veranschaulichen wir den Zusammenhang zwischen
__add__ und __radd__.
Die Implementierung von __radd__ würde damit exakt gleich aussehen wie __add__:
def __radd__(self, other):
if type(other) == int or type(other) == float:
l = self.converse2metres() + other
21.15 Magische Methoden und Operatorüberladung
else:
l = self.converse2metres() + other.converse2metres()
return Length(l / Length.__metric[self.unit], self.unit )
Es empfiehlt sich deshalb, die Implementierung von __radd__ auf einen Aufruf von
__add__ zu reduzieren:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __radd__(self, other):
return Length.__add__(self,other)
239
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
22
22.1
Bruchklasse
Brüche à la 1001 Nacht
Bevor wir mit der Arbeit an unserem Modul und mit den Brüchen beginnen, möchten wir Sie noch in die Welt Aladins und
Sinbads entführen. An einem Zwischenstopp an einer Oase finden wir drei Männer mit einer Herde Kamele. Sie jammern
und wehklagen. Der älteste der drei mit
langem Bart und Turban wendet sich an
Sie und sagt:
„Wir sind drei Brüder und haben diese 35
Kamele als unser Erbe erhalten. Nach dem
Wunsch meines Vaters soll die Hälfte der
Herde mir als dem ältesten Sohn gehören.”
Bild 22.1 Walter Heubach: Lagernde Karawane
mit Dromedaren
„Und ein Drittel soll mir gehören!”, sagt der
mittlere der drei Brüder.
„Ich als Jüngster soll mich mit einem Neuntel zufriedengeben!”, sagt der letzte der drei.
„Die Hälfte von 35 ist 17 und ein halb, und wir wollen kein Kamel zerteilen!”, fährt der
Älteste fort.
„Die Schwierigkeiten, ein Drittel und ein Neuntel der Herde zu bestimmen, sind auch nicht
kleiner!”, jammern die beiden anderen unisono.
Plötzlich haben Sie einen genialen Einfall und schlagen den ratlosen Brüdern Folgendes
vor:
Sie geben Ihr einziges Kamel, das Sie noch weiter durch die Wüste tragen soll, der Herde
hinzu und sagen zum ältesten Bruder: „Vorher hättest du 17 und ein halbes Kamel erhalten
sollen, doch nun sind es 18!”
Zum mittleren der Brüder sagen Sie: „Vorher hättest du 11 und zwei Drittel Kamele erhalten
sollen, nun werden es 12 sein. Du hast also auch keinen Grund zum Klagen.”
Und zum Jüngsten sagen Sie: „Du wirst nun 4 Kamele statt 3 und noch einen Teil von einem
weiteren erhalten und hast auch keinen Grund zu Klage!”
Die Brüder ziehen nun voller Freude mit ihren 34 Kamelen von dannen, und Sie haben nun
zwei Kamele statt einem!
242
22 Bruchklasse
22.2
Zurück in die Gegenwart
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Ja, damals zu Sinbads Zeiten war Bruchrechnen noch
eine hohe und wenig erforschte Kunst, und mit solchen Geschichten konnte man die Leute leicht verzaubern. Sicherlich haben Sie das Problem gleich erkannt.
1 1 1
+ + =?
2 3 9
Wir wollen nun eine Python-Klasse für das RechBild 22.2 Brüche als Kuchennen mit Brüchen entwickeln, wobei wir uns aber auf
diagramm
die vier Grundrechenarten „Addition”, „Subtraktion”,
„Multiplikation” und „Division” beschränken wollen.
Zunächst müssen wir uns aber die Frage stellen, wie wir einen Bruch in Python darstellen
wollen. Ein Bruch besteht ja bekanntlich aus einem Zähler, einem Bruchstrich und einem
Nenner. Mathematisch relevant sind natürlich nur Zähler und Nenner, während der Bruchstrich bloß ein Repräsentationsdetail ist.
Zur Repräsentierung eines Bruchs in unserer Bruchklasse genügt es also, wenn wir ein Attribut für den Nenner und eines für den Zähler definieren. Im folgenden Beispiel finden Sie
die __init__-Methode, und wir erzeugen Instanzen für die Brüche 13 und 25 :
class Bruch(object):
def __init__(self, z, n):
self.zaehler = z
self.nenner = n
if __name__ == "__main__":
x = Bruch(1, 3)
y = Bruch(2, 5)
Wir benötigen nun noch die Methode __str__1 , damit wir unsere Brüche in schöner Form
ausgeben können:
def __str__(self):
return str(self.zaehler) + '/' + str(self.nenner)
Die Ausgabe von Brüchen sieht nun wie folgt aus:
$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more
information.
>>> from brueche import Bruch
>>> x = Bruch(2, 3)
1
siehe 21.11 (Die __str__- und die __repr__-Methode)
22.2 Zurück in die Gegenwart
>>> print(x)
2/3
Zur Bruchrechnung gehört das Kürzen. Unter dem Kürzen eines Bruchs versteht man, dass
man Zähler und Nenner des Bruchs durch die gleiche Zahl dividiert. Kürzen dient der Vereinfachung von Brüchen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Der Wert des Bruchs bleibt beim Kürzen gleich. Man erhält lediglich eine neue Darstellung
derselben Bruchzahl. So sind zum Beispiel die folgenden Brüche alle gleich:
1 2 3
, , ,
3 6 9
und so weiter.
Die Zahl, durch die man kürzt, wird als Kürzungszahl bezeichnet. Ein Bruch ist in vollständig gekürzter Form, wenn Zähler und Nenner teilerfremd sind. Also in obiger Folge der
Bruch 13 . Um einen Bruch in eine vollständig gekürzte Form zu wandeln, muss man den
größten gemeinsamen Teiler, abgekürzt ggT, ermitteln.
Der Euklidische Algorithmus ist ein effizientes Verfahren, um den größten gemeinsamen
Teiler zweier Zahlen zu berechnen. Bei diesem Algorithmus wird in aufeinanderfolgenden
Schritten jeweils eine Division mit Rest durchgeführt, wobei der Rest im nächsten Schritt
zum neuen Divisor wird und der vorige Divisor zum Dividenden. Der Divisor, bei dem sich
Rest 0 ergibt, ist der größte gemeinsame Teiler der Ausgangszahlen.
Beispiel:
>>>
170
>>>
51
>>>
17
>>>
0
561 % 391
391 % 170
170 % 51
51 % 17
Also ist 17 der größte gemeinsame Teiler der Zahlen 561 und 391. Obige Berechnung hätte
man interessanterweise auch mit 391%561 starten können. Im nächsten Schritt hat man
dann 561%391, und die weitere Rechnung läuft wie oben weiter.
Wir können unser Programm nun um eine Methode ggT, die obiges Verfahren anwendet, und eine Methode kuerze erweitern. Außerdem rufen wir kuerze bei der Initialisierung eines Bruchs auf, damit ein Bruch immer in vollständig gekürzter Form gespeichert
wird. Da die Methode ggT nichts mit Bruchinstanzen zu tun hat, definieren wir sie als
staticmethod. Unsere Klasse sieht nun wie folgt aus:
class Bruch(object):
def __init__(self, z, n):
self.__z = z
self.__n = n
self.kuerze()
243
244
22 Bruchklasse
def __str__(self):
return str(self.__z) + '/' + str(self.__n)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
@staticmethod
def ggT(a, b):
while b != 0:
a, b = b, a%b
return a
def kuerze(self):
g = Bruch.ggT(self.__z, self.__n)
self.__z = self.__z // g
self.__n = self.__n // g
if __name__ == "__main__":
x = Bruch(2, 6)
y = Bruch(391, 561)
print(x, y)
Startet man obiges Programm, erhält man folgende Ausgabe:
$ python3 brueche.py
1/3 23/33
Man sieht, dass die Brüche vollständig gekürzt wurden. Aber das Wichtigste fehlt noch in
unserer Bruchklasse: Wir können Brüche definieren und ausgeben, aber wir können keine
Operationen auf den Brüchen ausführen.
22.3
Rechenregeln
Wie auch beim Rechnen mit natürlichen Zahlen2 oder ganzen Zahlen3 werden beim Rechnen in den vier Grundrechenarten jeweils zwei Brüche miteinander verknüpft, und man
erhält als Ergebnis einen neuen Bruch. Wir haben bereits im vorigen Abschnitt gezeigt,
dass sich der Wert eines Bruchs nicht ändert, wenn man Zähler und Nenner durch einen
gemeinsamen Teiler dividiert. Man spricht dann von Kürzen. Ebenso verändert sich ein
Bruch nicht, wenn man Zähler und Nenner mit der gleichen ganzen Zahl multipliziert. In
diesem Fall spricht man von Erweitern.
22.3.1
Multiplikation von Brüchen
Als Erstes zeigen wir, wie man Brüche multipliziert, da dies am einfachsten ist:
2
3
N = 1, 2, 3, ...
Z = −3, −2. − 1, 01, 2, 3, ...
22.3 Rechenregeln
Brüche werden multipliziert, indem man ihre Zähler und Nenner miteinander multipliziert. Das Produkt der Zähler wird zum Zähler des Ergebnisses, das Produkt der Nenner
wird zum Nenner des Ergebnisses:
Beispiel:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
2 3 2·3
6
· =
=
3 5 3 · 5 15
Wie wir sehen, kann das Ergebnis der obigen Multiplikation noch durch den größten gemeinsamen Teiler von 6 und 15, also 3, gekürzt werden:
6
2
=
15 5
Nun wissen wir genug, um unsere Bruchklasse um eine Multiplikation zu erweitern. Dazu
müssen wir die Methode __mul__ bereitstellen4 :
def __mul__(self,other):
p = Bruch(self.__z * other.__z,
self.__n * other.__n)
return p
In der Methode __mul__ erzeugen wir einen neuen Bruch P. Dieser wird mit der Initialisierung sofort auf das Ergebnis der Multiplikation gesetzt und dabei auch gekürzt:
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from brueche import Bruch
>>> x = Bruch(2, 3)
>>> y = Bruch(4, 15)
>>> z = x * y
>>> print(z)
8/45
Intern wird ein Ausdruck „x * y” in den Aufruf x.__mul__(y) gewandelt, d.h. damit entspricht x dem „self” und y dem „other”.
22.3.2
Division von Brüchen
Die Division von Brüchen ist ähnlich einfach wie die Multiplikation. Durch einen Bruch
wird dividiert, indem man mit seinem Kehrwert multipliziert.
Beispiel:
2 5 2 3 2·3
6
: = · =
=
3 3 3 5 3 · 5 15
4
Siehe Kapitel Magische Methoden und Operatorüberladung, Seite 233
245
246
22 Bruchklasse
Unsere Methode für die Division zweier Brüche lässt sich nahezu analog zur Multiplikation
realisieren. Der Name für die Division in Python 3 lautet __truediv__5 :
def __truediv__(self, other):
p = Bruch(self.__z * other.__n,
self.__n * other.__z)
return p
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im Folgenden zeigen wir, wie man Brüche in unserer Bruchklasse dividieren kann:
$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from brueche import Bruch
>>> x = Bruch(2, 3)
>>> y = Bruch(15, 4)
>>> print(x/y)
8/45
22.3.3
Addition von Brüchen
Im Vergleich zur Multiplikation und Division von Brüchen gestaltet sich die Addition ebenso wie die Subtraktion etwas schwieriger. Bevor man zwei Brüche addieren kann, müssen sie zuerst gleichnamig gemacht werden, d.h. sie müssen so erweitert werden, dass anschließend die beiden Nenner gleich sind. Dann können die Zähler einfach addiert werden:
2 3 2 4 3 3 2 · 4 + 3 · 3 17
+ = · + · =
=
3 4 3 4 4 3
3·4
12
Entsprechend implementieren wir die Python-Methode für die Addition (__add__):
def __add__(self,other):
s = Bruch(self.__z*other.__n + other.__z * self.__n,
self.__n*other.__n)
return s
22.3.4
Subtraktion von Brüchen
Die Subtraktion läuft analog zur Addition, d.h. erst müssen die Brüche gleichnamig gemacht werden und dann kann man die Zähler subtrahieren:
5
Vor Python 3 hieß diese Methode __div__.
22.4 Integer plus Bruch
def __sub__(self,other):
s = Bruch(self.__z*other.__n - other.__z * self.__n,
self.__n * other.__n)
return s
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
22.3.5
Vergleichsoperatoren
Im Folgenden wollen wir noch die Methoden für die Vergleichsoperatoren von Brüchen
angeben, also für „==”, „!=”, „>”, „>=”, „<”, „<=”:
def __eq__(self, other):
return self.__z * other.__n == other.__z * self.__n
def __ne__(self, other):
return not self.__eq__(other)
def __gt__(self, other):
return self.__z * other.__n > other.__z * self.__n
def __ge__(self, other):
return self.__z * other.__n >= other.__z * self.__n
def __lt__(self, other):
return self.__z * other.__n < other.__z * self.__n
def __le__(self, other):
return self.__z * other.__n <= other.__z * self.__n
22.4
Integer plus Bruch
Mit unserer Bruchklasse sind wir in der Lage, jeweils zwei Brüche zu addieren, zu subtrahieren, zu multiplizieren und zu dividieren. Wie sieht es aber aus, wenn wir einen Bruch
mit einer ganzen Zahl verknüpfen wollen, also beispielsweise ein Ausdruck der Form 13 + 5
oder umgekehrt herum 5 + 13 ?
Wir schreiben jetzt unsere bisherige __add__-Methode so um, dass Sie auch mit einer
Integer-Zahl als Argument für den other-Operator klarkommt. Wir machen dies, indem
wir aus der Integer-Zahl einen Bruch mit dem Nenner 1 erzeugen. Außerdem geben wir im
Fall, dass das rechte Argument vom +-Operator weder ein Bruch noch eine Integer-Zahl ist,
den speziellen Wert NotImplemented zurück. Dies ist ein build-in-Wert von Python:
def __add__(self, other):
if type(other) == int:
return self + Bruch(other, 1)
elif type(other) == Bruch:
return Bruch(self.__z*other.__n + other.__z * self.__n,
self.__n * other.__n)
else:
return NotImplemented
247
248
22 Bruchklasse
Damit unsere Bruchklasse auch Ausdrücke der Form „Integer-Zahl + Bruch” ausrechnen kann, müssen wir noch die Methode __radd__ definieren. Da self beim Aufruf von
__radd__ vom Typ int ist und other vom Typ „Bruch”, können wir einfach self + other
ausrechnen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __radd__(self, other):
return self + other
22.4.1
Die Bruchklasse im Überblick
Unsere Bruchklasse ist nun „fertig”. Allerdings müsste man noch die Methoden von „add”
analog für die anderen Operatoren vollständig implementieren:
class Bruch(object):
def __init__(self, z, n):
self.__z = z
self.__n = n
self.kuerze()
def __repr__(self):
return "Bruch(" + str(self.__z)+', ' + str(self.__n) + ")"
def __str__(self):
return str(self.__z) + '/' + str(self.__n)
@staticmethod
def ggT(a,b):
while b != 0:
a, b = b, a % b
return a
def kuerze(self):
g = Bruch.ggT(self.__z, self.__n)
self.__z = self.__z // g
self.__n = self.__n // g
def __mul__(self, other):
p = Bruch(self.__z * other.__z,
self.__n * other.__n)
return p
def __truediv__(self, other):
p = Bruch(self.__z * other.__n,
self.__n * other.__z)
return p
22.4 Integer plus Bruch
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __add__(self, other):
if type(other) == int:
return self + Bruch(other, 1)
elif type(other) == Bruch:
return Bruch(self.__z*other.__n + other.__z * self.__n,
self.__n * other.__n)
else:
return NotImplemented
def __radd__(self, other):
return self + other
def __sub__(self,other):
s = Bruch(self.__z*other.__n - other.__z * self.__n,
self.__n * other.__n)
return s
def __eq__(self, other):
return self.__z * other.__n == other.__z * self.__n
def __ne__(self, other):
return not self.__eq__(other)
def __gt__(self, other):
return self.__z * other.__n > other.__z * self.__n
def __ge__(self, other):
return self.__z * other.__n >= other.__z * self.__n
def __lt__(self, other):
return self.__z * other.__n < other.__z * self.__n
def __le__(self, other):
return self.__z * other.__n <= other.__z * self.__n
if __name__ == "__main__":
x = Bruch(2, 3)
y = Bruch(3, 4)
z = x * y
print(z)
print(x + y)
print(x + 3)
print(3 + x)
if x < y:
print("Es gilt: x < y")
else:
print("Es gilt: x >= y")
249
250
22 Bruchklasse
Lässt man obiges Programm laufen, erhält man folgende Ausgaben:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
$ python3 brueche.py
1/2
17/12
11/3
11/3
Es gilt: x < y
22.5
Fraction-Klasse
Wie so häufig haben wir uns die Arbeit mit der Bruchklasse umsonst gemacht, denn Python
bietet eine umfangreiche Bruchklasse Fraction in dem Modul fractions:
>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> y = Fraction(2, 5)
>>> print(x)
1/3
>>> x + y
Fraction(11, 15)
>>> print((x - y) * Fraction(4, 7))
-4/105
Der besondere Vorteil der Fraction-Klasse liegt in der Genauigkeit, da die Integers nahezu beliebig groß werden können. Im folgenden Beispiel erhalten wir einen Fehler von
5.329070518200751e-15, wenn wir mit float statt mit Fraction rechnen:
from fractions import Fraction
x =
y =
xf,
for
Fraction(19, 599)
Fraction(17, 171)
yf = float(x), float(y)
i in range(1, 10000):
x += y / i
xf += yf / i
print(i, float(x), xf, float(x) - xf)
Als Ausgabe erhalten wir:
9999 2.370739206885817 2.3707392068858106 6.217248937900877e-15
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
23
23.1
Vererbung
Oberbegriffe und Oberklassen
Jeder kennt Oberbegriffe aus der natürlichen Sprache. Wenn wir von Fahrzeugen reden,
dann können dies Autos, Lastwagen, Motorräder, Busse, Züge, Schiffe, Flugzeuge oder auch
Fahrräder und Roller sein. Autos kann man auch wieder nach verschiedenen Kriterien klassifizieren, z.B. nach der Antriebsart, also Benziner, mit Diesel oder mit Strom angetriebene
Autos. In der objektorientierten Programmierung gibt es etwas Ähnliches. Man spricht in
der OOP von Vererbung und von Ober- und Unterklassen. Statt Oberklasse benutzt man
auch die Synonyme Basisklasse, Elternklasse und Superklasse. Für Unterklasse gibt es auch
die Synonyme abgeleitete Klasse, Kindklasse und Subklasse.
Ein weiteres Beispiel eines Oberbegriffs stellt
Person dar. Häufig werden auch Klassen modelliert, die Eigenschaften von Personen in Datenstrukturen abbilden. Allgemeine Daten für
Personen sind Name, Vorname, Wohnort, Geburtsdatum, Geschlecht und so weiter. Wahrscheinlich würde man jedoch nicht Daten wie
Personalnummer, Steuernummer, Religionszugehörigkeit oder Ähnliches aufnehmen. Nehmen wir nun an, dass man die allgemeine
Personenklasse für die Firmenbuchhaltung benutzen will. Man merkt schnell, dass nun die
Information über die Religionszugehörigkeit, Bild 23.1 Vererbung von Personendie Steuerklasse oder Kinderzahl benötigt wird, eigenschaften
um das Nettogehalt korrekt zu berechnen. Vor
allen Dingen benötigt man firmenspezifische Daten für die Angestellten wie beispielsweise Personalnummern und Abteilungszugehörigkeiten. Die Personendaten können wir aber
auch beispielsweise für eine Klasse Clubmitglieder verwenden. Auch hier benötigen wir
gegebenenfalls zusätzliche Informationen, z.B. Eintrittsdatum, Funktion im Verein und so
weiter.
Ganz allgemein kann man sagen, dass die Vererbung eine Beziehung zwischen einer allgemeinen Klasse (einer Oberklasse oder einer Basisklasse) und einer spezialisierten Klasse
(der Unterklasse, manchmal auch Subklasse genannt) definiert.
252
23 Vererbung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
23.2
Ein einfaches Beispiel
Wir wollen nun die Personenklasse implementieren. Wir beschränken uns allerdings nur
auf die Instanzattribute für den Vornamen (vorname), Nachnamen (nachname) und das
Geburtsdatum (geburtsdatum) für eine Person. Die Klasse Angestellter erbt von Person und wird um ein zusätzliches Instanzattribut Personalnummer (personalnummer)
ergänzt. Die Methode __str__ von Person wird in der Klasse Angestellter überlagert.
Allerdings benutzen wir in der Definition der Methode __str__ von Angestellter die Methode __str__ von Person. Der Aufruf kann entweder über super().__str__() oder über
Person.__str__(self) erfolgen:
class Person:
def __init__(self, vorname, nachname, geburtsdatum):
self.vorname = vorname
self.nachname = nachname
self.geburtsdatum = geburtsdatum
def __str__(self):
ret = self.vorname + " " + self.nachname
ret += ", " + self.geburtsdatum
return ret
class Angestellter(Person):
def __init__(self, vorname, nachname, geburtsdatum, personalnummer
):
Person.__init__(self, vorname, nachname, geburtsdatum)
# alternativ:
#super().__init__(vorname, nachname, geburtsdatum)
self.personalnummer = personalnummer
def __str__(self):
#return super().__str__() + " " + self.personalnummer
return Person.__str__(self) + " " + self.personalnummer
if __name__ == "__main__":
x = Angestellter("Homer", "Simpson", "09.08.1969", "007")
print(x)
Das obige Programm liefert die folgende Ausgabe:
Homer Simpson, 09.08.1969 007
23.3 Überladen, Überschreiben und Polymorphie
23.3
Überladen, Überschreiben und
Polymorphie
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Es ist möglich, in der überlagernden Methode die Methode, die man überlagert hat, aufzurufen.
Im folgenden Beispiel zeigen wir, wie eine Funktion f überschrieben wird. Man sieht dabei,
dass dies nichts mit Objektorientierung zu tun hat:
>>> def f(x):
...
return x + 3
...
>>> f(4)
7
>>>
>>> f
<function f at 0x7ff8bff1a158>
>>>
>>> # Wir überschreiben nun die Funktion f:
...
>>> def f(x):
...
return x * 3.4
...
>>> f(3)
10.2
Nach dem Überschreiben gibt es keine Möglichkeit mehr, auf die alte Definition zurückzugreifen!
Im Zusammenhang mit objektorientierter Programmierung haben Sie möglicherweise
auch schon einmal etwas von „Überladen” gehört. Überladen kennt man beispielsweise in
C++ und Java. Um es gleich vorweg zu sagen: Es gibt kein Überladen von Funktionen und
Methoden in Python. Es wird auch nicht benötigt, wie wir im Folgenden zeigen wollen.
Überladen von Methoden wird in statisch getypten Sprachen wie Java und C++ benötigt,
um die gleiche Funktion mit verschiedenen Typen zu definieren.
Betrachten wir beispielsweise die Funktion multi, die wir im folgenden Code definieren:
>>> def multi(irgendwas, faktor):
...
return factor * irgendwas
...
>>> multi(3, 2)
6
>>> multi(3.4, 2)
6.8
>>> multi(".", 10)
'..........'
>>> multi(["spam"], 5)
['spam', 'spam', 'spam', 'spam', 'spam']
253
254
23 Vererbung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Parameter irgendwas und faktor sind nicht mit einem Typ versehen. Beim Parameter faktor kann man gegebenenfalls darauf schließen, dass die Funktion hier eine ganze
Zahl erwartet, aber irgendwas kann wie der Name sagt, „irgendetwas” sein. Wir können die
Funktion multi also mit beliebigen Argumenten aufrufen. Dies bedeutet aber nicht immer,
dass dies im Sinne des Programmierers ist oder dass es fehlerfrei vonstattengeht:
>>> multi(5, ["spam"])
['spam', 'spam', 'spam', 'spam', 'spam']
>>> multi("spam", "spam")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in multi
TypeError: can't multiply sequence by non-int of type 'str'
In Java und C++ müssen für alle gewollten Typkombinationen jeweils Funktionen definiert
werden, die zwar den gleichen Namen haben, aber unterschiedliche Parametertypen. Wir
zeigen dies exemplarisch für zwei Typkombinationen in C++:
#include <iostream>
#include <cstdlib>
using namespace std;
int multi(int irgendwas, int faktor) {
return irgendwas * faktor;
}
double multi(double irgendwas, int faktor) {
return irgendwas * faktor;
}
int main() {
cout << multi(10, 3) << endl;
cout << multi(10.3, 4) << endl;
return 0;
}
Parameterüberladung gibt es in C++ und Java auch in der Form, dass die gleiche Funktion
oder Methode mehrfach definiert wird, jedoch mit einer unterschiedlichen Zahl von Parametern. Im folgenden C++-Programm definieren wir eine Funktion f, die ein oder zwei
Integer-Parameter nehmen kann:
#include <iostream>
using namespace std;
int f(int n)
return n
}
int f(int n,
return n
}
int main() {
{
+ 42;
int m) {
+ m + 42;
23.3 Überladen, Überschreiben und Polymorphie
cout << "f(3): " << f(3) << endl;
cout << "f(3, 4): " << f(3, 4) << endl;
return 0;
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
}
Bei den letzten beiden C++-Beispielen handelt es sich um Polymorphismen. Das Wort Polymorphie oder Polymorphismus stammt aus dem Griechischen und bedeutet Vielgestaltigkeit. Polymorphismus bezeichnet bei Methoden die Möglichkeit, dass man bei gleichem
Namen Methoden mit verschiedenen Parametern definieren kann. So wurde die Funktion
„nachfolger” zweimal definiert: einmal mit einem int und das andere Mal mit double als
Parameter. Im zweiten Beispiel wurde f einmal mit einem Parameter und beim zweiten Mal
mit zwei Parametern definiert.
Methoden und Funktionen in Python haben bereits eine implizite Typpolymorphie wegen
des dynamischen Typkonzepts von Python.
Die zweite Art von Polymorphie, d.h. gleicher Name bei unterschiedlicher Parameteranzahl, lässt sich in Python nicht direkt implementieren. Das folgende Beispiel zeigt, was
passiert, wenn wir obiges C++-Programm mit der f-Funktion in Python analog nachbilden:
>>> def f(n):
...
return n + 42
...
>>> def f(n,m):
...
return n + m + 42
...
>>> f(3,4)
49
>>> f(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 2 arguments (1 given)
>>>
In Python kann man das polymorphe Verhalten jedoch mit Default-Parameter realisieren:
def f(n, m=None):
if m:
return n + m +42
else:
return n + 42
Wir haben aber damit nur eine Funktionsdefinition und können nicht von Polymorphie
sprechen. Ganz allgemein lassen sich Polymorphien in der Parameteranzahl in Python mit
einem *-Argument für eine beliebige Anzahl von Parametern realisieren:
def f(*x):
if len(x) == 1:
return x[0] + 42
else:
return x[0] + x[1] + 42
255
256
23 Vererbung
23.4
Vererbung in Python
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Einige Konzepte der Vererbung möchten wir zunächst an einer abstrakten Oberklasse
mit abstrakten Unterklassen demonstrieren. Dazu definieren wir eine Klasse A mit einer
__init__-Methode und einer Methode m. Dann definieren wir eine Unterklasse B, die von
A erbt und keine eigenen Methoden hat:
class A:
def __init__(self):
self.content = "42"
print("__init__ von A wurde ausgeführt")
def m(self):
print("m von A wurde aufgerufen")
class B(A):
pass
if __name__ == "__main__":
x = A()
x.m()
y = B()
y.m()
Ruft man diese Klasse direkt auf, erhalten wir folgende Ausgabe:
$ python3 inheritance1.py
__init__ von A wurde ausgeführt
m von A wurde aufgerufen
__init__ von A wurde ausgeführt
m von A wurde aufgerufen
Die Ergebnisse der ersten beiden Anweisungen hinter der if-Anweisung stellen nichts
Neues dar. Aber wenn wir ein Objekt der Klasse B erzeugen, wie dies in der Anweisung
y = B() geschieht, dann können wir an der Ausgabe erkennen, dass die __init__Methode der Klasse A ausgeführt wird, da B keine eigene __init__-Methode besitzt. Ebenso erkennen wir, dass beim Aufruf der Methode m für das Objekt y der Klasse B die Methode
m von A aufgerufen wird, denn B hat keine Methode m.
Was passiert, wenn wir die Klasse B nun auch mit einer __init__-Methode und einer Methode m ausstatten? Wir testen dies im folgenden Beispiel. Die Klasse A und unsere Tests
bleiben gleich, wir ändern lediglich die Klasse B wie folgt:
class B(A):
def __init__(self):
self.content = "43"
print("__init__ von B wurde ausgeführt")
def m(self):
print("m von B wurde aufgerufen")
23.4 Vererbung in Python
Rufen wir das veränderte Skript nun auf, erhalten wir die folgende Ausgabe:
$ python3 inheritance2.py
__init__ von A wurde ausgeführt
m von A wurde aufgerufen
__init__ von B wurde ausgeführt
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
m von B wurde aufgerufen
Wir erkennen nun, dass die __init__-Methode von B und nicht mehr die von A beim Erzeugen einer Instanz von B aufgerufen wird. Außerdem wird nun die Methode m von B statt
der Methode m von A benutzt, d.h. wir überschreiben die Methode m von A in B.
Im folgenden Beispiel ändern wir nun die Instanzattribute der Klassen A und B. Wir haben
in A ein Attribut self.contentA und in B eines mit dem Namen self.contentB.
class A(object):
def __init__(self):
self.contentA = "42"
print("__init__ von A wurde ausgeführt")
def m(self):
print("m von A wurde aufgerufen")
class B(A):
def __init__(self):
self.contentB = "43"
print("__init__ von B wurde ausgeführt")
def m(self):
print("m von B wurde aufgerufen")
if __name__ == "__main__":
x = A()
x.m()
y = B()
y.m()
print("self.contentB von y: " + str(y.contentB))
print("self.contentA von y: " + str(y.contentA))
Die Ausgabe des Programms zeigt, was passiert:
$ python3 inheritance3.py
__init__ von A wurde ausgeführt
m von A wurde aufgerufen
__init__ von B wurde ausgeführt
m von B wurde aufgerufen
self.contentB von y: 43
Traceback (most recent call last):
File "inheritance3.py", line 25, in <module>
print("self.contentA von y: " + str(y.contentA))
AttributeError: 'B' object has no attribute 'contentA'
257
258
23 Vererbung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wie erwartet, erhalten wir für self.contentB den Wert 43 als Ausgabe. Da die __init__Methode der Klasse A nicht aufgerufen wird, sollte es uns nicht wundern, dass wir beim
Versuch, self.contentA auszugeben, einen AttributeError mit dem Text ’B’ object
has no attribute ’contentA’ erhalten, denn es gibt ja kein Attribut self.contentB in
den Instanzen von B.
Wir können uns auch vorstellen, dass wir spezielle Attribute, die wir bei der Initialisierung
eines Objekts einer Basisklasse einführen, auch in der Unterklasse verwenden wollen. Also
hätten wir in unserem Beispiel vielleicht gerne das Attribut contentA genutzt. Wir können
dies erreichen, indem wir in der __init__-Methode von B die __init__-Methode von A
aufrufen. Dies geschieht durch die Anweisung A.__init__(self), d.h. Name der Basisklasse plus Aufruf der __init__-Methode. Die Klasse A und die Tests bleiben gleich. Wir
müssen lediglich die __init__-Methode in B wie folgt ändern:
class B(A):
def __init__(self):
A.__init__(self)
self.contentB = "43"
print("__init__ von B wurde ausgeführt")
Rufen wir das Programm mit obiger Änderung auf, sehen wir, dass unser AttributeError
verschwunden ist. Außerdem erkennen wir, dass die Instanz von y sowohl ein Attribut
contentB als auch ein Attribut contentA enthält:
$ python3 inheritance4.py
__init__ von A wurde ausgeführt
m von A wurde aufgerufen
__init__ von A wurde ausgeführt
__init__ von B wurde ausgeführt
m von B wurde aufgerufen
self.contentB von y: 43
self.contentA von y: 42
Dieses Vorgehen lässt sich analog auf andere Methoden übertragen. Im Folgenden rufen
wir in m von B auch die Methode m von A auf:
def m(self):
A.m(self)
print("m von B wurde aufgerufen")
Wir testen dieses Skript, das unter dem Namen inheritance5.py abgespeichert ist, in der
interaktiven Python-Shell:
>>> from inheritance5 import A, B
>>> y = B()
__init__ von A wurde ausgeführt
__init__ von B wurde ausgeführt
>>> y.m()
m von A wurde aufgerufen
m von B wurde aufgerufen
23.5 Klassenmethoden
23.5
Klassenmethoden
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Statische Methoden darf man nicht mit Klassenmethoden verwechseln. Klassenmethoden
sind auch nicht an Instanzen gebunden, aber anders als statische Methoden sind Klassenmethoden an eine Klasse gebunden. Das erste Argument einer Klassenmethode ist eine
Referenz auf die Klasse, d.h. das Klassenobjekt. Aufrufen kann man sie über den Klassennamen oder eine Instanz:
class Roboter:
counter = 0
def __init__(self):
type(self).counter += 1
@classmethod
def anzahl_roboter(cls):
print("Wert von cls: ", cls, end=", ")
return cls.counter
if __name__ == "__main__":
print(Roboter.anzahl_roboter())
x = Roboter()
print(Roboter.anzahl_roboter())
print(x.anzahl_roboter())
Das Ergebnis sieht nun wie folgt aus:
Wert von cls:
Wert von cls:
Wert von cls:
<class '__main__.Roboter'>, 0
<class '__main__.Roboter'>, 1
<class '__main__.Roboter'>, 1
Für Klassenmethoden gibt es zwei Einsatzgebiete: zum einen die sogenannte Fabrikmethode1 , auf die wir hier nicht näher eingehen, und zum anderen die Fälle, in denen aus
statischen Methoden auf andere statische Methoden zugegriffen werden muss. Ohne Klassenmethoden, also in statischen Methoden, müsste man den Klassennamen fest kodieren,
was insbesondere bei der Vererbung ein Nachteil sein kann.
In der folgenden Bruchklasse2 benutzen wir eine statische Methode ggT, die den größten gemeinsamen Teiler von zwei Zahlen berechnet, und eine Klassenmethode kuerze,
die zwei Zahlen in gekürzter Form zurückgibt. Bevor wir in __init__ die Werte für
Zähler und Nenner abspeichern, berechnen wir die gekürzte Form von beiden durch
den Aufruf self.kuerze(z,n). Hätten wir kuerze als statische Methode definiert oder,
wie man normalerweise sagt, mit @staticmethod dekoriert, dann müssten wir die Zeile g = cls.ggT(zaehler, nenner) durch g = Bruch.ggT(zaehler, nenner) austauschen:
1
2
Dies sind Methoden, die neue Instanzen generieren.
Wir werden später eine vollständige Bruchklasse definieren.
259
260
23 Vererbung
class Bruch(object):
def __init__(self, z, n):
self.__z, self.__n = self.kuerze(z, n)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __str__(self):
return str(self.__z)+'/' + str(self.__n)
@staticmethod
def ggT(a,b):
while b != 0:
a, b = b, a%b
return a
@classmethod
def kuerze(cls, zaehler, nenner):
g = cls.ggT(zaehler, nenner)
return (zaehler // g, nenner // g)
x = Bruch(8, 24)
print(x)
Wir erhalten „1/3” als Ergebnis.
In unserem nächsten Beispiel wollen wir den Vorteil der Klassenmethoden bei der Verarbeitung demonstrieren. Wir definieren eine Klasse „Pets” mit einer Methode „about”. Die
Klassen „Dogs” und „Cats” erben von dieser Klasse. Sie erben auch die Methode „about”. In
unserer ersten Implementierung dekorieren wir die „about”-Methode als „staticmethod”,
um die Nachteile dieses Vorgehens zu zeigen:
class Pets:
name = "Haustiere"
@staticmethod
def about():
print("In dieser Klasse geht es um {}!".format(Pets.name))
class Dogs(Pets):
name = "Hunde"
class Cats(Pets):
name = "Katzen"
p = Pets()
p.about()
d = Dogs()
d.about()
c = Cats()
c.about()
23.6 Standardklassen als Basisklassen
Wir erhalten die folgende Ausgabe:
In dieser Klasse geht es um Haustiere!
In dieser Klasse geht es um Haustiere!
In dieser Klasse geht es um Haustiere!
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Insbesondere im Fall von c.about() und d.about() hätten wir aussagekräftigere Sätze erwartet oder uns gewünscht.
Das „Problem” ist, dass die Methode „about” nicht weiß, dass sie von einer Instanz der
Klasse Dogs oder Cats aufgerufen wird.
Im folgenden Code dekorieren wir die Methode nun mit dem „classmethod”-Dekorator:
class Pets:
name = "Haustiere"
@classmethod
def about(cls):
print("In dieser Klasse geht es um {}!".format(cls.name))
class Dogs(Pets):
name = "Hunde"
class Cats(Pets):
name = "Katzen"
p = Pets()
p.about()
d = Dogs()
d.about()
c = Cats()
c.about()
Die Ausgabe sieht nun wie gewünscht aus:
In dieser Klasse geht es um Haustiere!
In dieser Klasse geht es um Hunde!
In dieser Klasse geht es um Katzen!
23.6
Standardklassen als Basisklassen
Statt selbstdefinierter Klassen kann man auch Standardklassen wie beispielsweise int,
float, dict oder list als Basisklasse verwenden, um neue Klassen abzuleiten. Man kann
beispielsweise zusätzliche Methoden für Standardklassen definieren.
261
262
23 Vererbung
Die Verarbeitung von Stapelspeichern3 wird in Python, wie wir gesehen haben, mittels append und pop realisiert. Üblicherweise, d.h. in anderen Programmiersprachen, werden
meistens die Funktionen push4 und pop5 verwendet.
Wir wollen im folgenden Beispiel die Klasse „list” um eine Methode „push” erweitern, die
genau das Gleiche macht wie append:
class Plist(list):
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __init__(self, l):
list.__init__(self, l)
def push(self, item):
self.append(item)
if __name__ == "__main__":
x = Plist([3,4])
x.push(47)
print(x)
3
4
5
auch Kellerspeicher genannt, im Englischen stack
deutsch: einkellern
deutsch: auskellern
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
24
24.1
Mehrfachvererbung
Einführung
Erbt eine abgeleitete Klasse direkt von mehr als einer Basisklasse, spricht man in der objektorientierten Programmierung von Mehrfachvererbung. Ein sequentielles, mehrstufiges
Erben wird dagegen nicht als Mehrfachvererbung bezeichnet. Nicht alle objektorientierten
Programmiersprachen unterstützen Mehrfachverarbeitung. So kennt Java1 sie beispielsweise nicht, wohingegen C++ sie unterstützt. Ein Argument gegen die Einbeziehung einer Mehrfachverarbeitung im Design einer Programmiersprache besteht darin, dass sie
das Design von Klassen unnötig verkompliziert und undurchsichtig machen. Besonders
bekannt sind die Mehrdeutigkeiten, die im sogenannten Diamond-Problem beschrieben
werden. Wir gehen auf dieses Problem später ein.
Syntaktisch realisiert Python die Mehrfachvererbung wie folgt: Soll eine Klasse von mehreren Basisklassen erben, gibt man die Basisklassen, durch Kommata getrennt, zwischen
dem Klammernpaar hinter dem Klassennamen an:
class UnterKlasse(Basis1, Basis2, Basis3, ...):
pass
Wie bei der einfachen Vererbung können Attribute und Methoden mit gleichen Namen in
der Unterklasse und in Basisklassen vorkommen. Die obigen Basisklassen Basis1, Basis2
usw. können natürlich ihrerseits wieder von anderen Basisklassen geerbt haben. Dadurch
erhält man eine Vererbungshierarchie, die man sich als einen Baum vorstellen kann, den
Vererbungsbaum.
Bild 24.1 Vererbungsbaum bei
Mehrfachvererbung
1
Man hat das Problem in Java mit Mehrfach-Schnittstellenvererbung umgangen. Eine Klasse kann in Java
von beliebig vielen Schnittstellen erben.
264
24 Mehrfachvererbung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
24.2
Beispiel: CalendarClock
Wir wollen die Prinzipien der Mehrfachvererbung an einem Beispiel einführen.
Dazu implementieren wir zuerst zwei voneinander unabhängige Klassen: eine Klasse „Clock” und eine Klasse „Calendar”.
Dann schreiben wir eine Klasse „CalendarClock”, die aus einer Uhr und einem Kalender besteht. Dazu lassen wir die Klasse
„CalendarClock” von den Klassen „Clock”
und „Calendar” erben.
Die Klasse Clock simuliert das sekunden- Bild 24.2 CalendarClock
mäßige Ticken einer Uhr. In Kapitel 9 (Verzweigungen) hatten wir in einer Übungsaufgabe das Ticken, also das Vorwärtsschreiten
der Uhr um eine Sekunde, bereits simuliert (siehe Seite 74). Der Code der Lösung dieser
Übungsaufgabe (siehe Seite 482) befindet sich nun in unserer Methode tick().
Nachdem wir das „Ticken” recyceln können, gestaltet sich die Implementierung des Rests
der Klasse „Clock” relativ einfach: Eine Instanz dieser Klasse enthält eine Uhrzeit, die wir
in den Attributen self.hours , self.minutes und seconds speichern. Im Prinzip hätten
wir die __init__-Methode und die set-Methode zum Neusetzen einer Uhrzeit wie folgt
schreiben können:
def __init__(self,hours=0, minutes=0, seconds=0):
self._hours = hours
self.__minutes = minutes
self.__seconds = seconds
def set(self,hours, minutes, seconds=0):
self._hours = hours
self.__minutes = minutes
self.__seconds = seconds
Wir wollten aber gerne noch Plausibilitätsprüfungen für die Eingaben durchführen. Damit wir den dazu nötigen Code nicht gleichzeitig in der __init__-Methode und der setMethode vorhalten müssen, rufen wir in der __init__-Methode nur die set-Methode auf,
die dann die eigentliche Initialisierung inklusive Überprüfungen vornimmt.
Die komplette Klasse „Clock”:
"""
Die Klasse Clock dient der logischen Simulation einer Uhr.
Die Uhrzeit kann mit einer Methode sekundenweise weiterbewegt
werden.
"""
class Clock(object):
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
24.2 Beispiel: CalendarClock
def __init__(self, hours, minutes, seconds):
"""
Die Parameter hours, minutes, seconds müssen Ganzzahlen
sein, und es muss gelten:
0 <= h < 24
0 <= m < 60
0 <= s < 60
"""
self.set_Clock(hours, minutes, seconds)
def set_Clock(self, hours, minutes, seconds):
"""
Die Parameter hours, minutes, seconds müssen Ganzzahlen
sein, und es muss gelten:
0 <= h < 24
0 <= min < 60
0 <= sec < 60
"""
if type(hours) == int and 0 <= hours and hours < 24:
self._hours = hours
else:
raise TypeError("Stunden müssen Ganzzahlen zwischen 0 und
23 sein!")
if type(minutes) == int and 0 <= minutes and minutes < 60:
self.__minutes = minutes
else:
raise TypeError("Minuten müssen Ganzzahlen zwischen 0 und
59 sein!")
if type(seconds) == int and 0 <= seconds and seconds < 60:
self.__seconds = seconds
else:
raise TypeError("Sekunden müssen Ganzzahlen zwischen 0 und
59 sein!")
def __str__(self):
"""
Diese Methode überlädt die eingebaute Funktion str(),
d.h. es wird eine Methode zur Verfügung gestellt,
um ein Objekt der Klasse Clock in einen String zu wandeln.
Die Methode __str__ wird auch von der Print-Funktion
genutzt, um ein Objekt der Klasse Clock auszugeben.
"""
return "{0:02d}:{1:02d}:{2:02d}".format(self._hours,
self.__minutes,
self.__seconds)
265
266
24 Mehrfachvererbung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def tick(self):
"""
Diese Methode lässt die Uhr 'ticken', d.h. die interne
Uhrzeit eines Objektes, d.h. die Stunden-,
Minuten- und Sekunden-Attribute werden um eine Sekunde
weitergerechnet.
Beispiele:
>>> x = Clock(12,59,59)
>>> print(x)
12:59:59
>>> x.tick()
>>> print(x)
13:00:00
>>> x.tick()
>>> print(x)
13:00:01
"""
if self.__seconds == 59:
self.__seconds = 0
if self.__minutes == 59:
self.__minutes = 0
if self._hours == 23:
self._hours = 0
else:
self._hours += 1
else:
self.__minutes += 1
else:
self.__seconds += 1
if __name__ == "__main__":
x = Clock(23,59,59)
print(x)
x.tick()
print(x)
y = str(x)
print(type(y))
Ruft man das Modul clock standalone auf, erhält man folgende Ausgabe:
$ python3 clock.py
23:59:59
00:00:00
<class 'str'>
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
24.2 Beispiel: CalendarClock
Für unsere Klasse „CalendarClock”, die eine Uhrzeitfunktion mit Kalenderfunktion kombiniert, benötigen wir noch eine Klasse „Calendar”. Während eine Instanz einer Clock-Klasse
mittels der Methode „tick” sekundenweise ihren Zustand ändert, geschieht dies bei der
„Calendar”-Klasse tageweise. Die Methode zum Wechseln oder „Weiterblättern” der Tage
nennen wir nicht „tick”, sondern „advance”. Sie zählt zu einem gegebenen Kalenderdatum
einen Tag hinzu. Dabei müssen wir die Anzahl der Tage für die Monate berücksichtigen,
also z.B. nach dem 30. April kommt der 1. Mai, und nach dem 31. Januar kommt der 1. Februar. Apropos Februar, hier haben wir ein weiteres Problem, denn wir müssen nun wissen,
ob es sich um ein Datum in einem Schaltjahr handelt oder nicht. Auch bei der Schaltjahrberechnung können wir von bereits getaner Arbeit profitieren: Die Aufgabenstellung und
die Beschreibung, wie man Schaltjahre ermittelt, befindet sich auf der Seite 74. Die dazugehörige Lösung befindet sich auf Seite 480. In unserer Klasse implementieren wir diese
Funktionalität unter dem Namen „leapyear” als Klassenmethode, die True zurückliefert,
wenn es sich um ein Schaltjahr handelt, und ansonsten False. Für die Anzahl der Monate
benutzen wir ein Tupel2 als Klassenattribut.
"""
Die Klasse Calendar implementiert einen Kalender.
Ein Kalenderdatum kann auf ein bestimmtes Datum gesetzt werden
oder kann um einen Tag weitergeschaltet werden.
"""
class Calendar(object):
months = (31,28,31,30,31,30,31,31,30,31,30,31)
@staticmethod
def leapyear(jahr):
"""
Die Methode leapyear liefert True zurück, wenn jahr
ein Schaltjahr ist, und False, wenn nicht.
"""
if jahr % 4 == 0:
if jahr % 100 == 0:
if jahr % 400 == 0:
schaltjahr = True
else:
schaltjahr = False
else:
schaltjahr = True
else:
schaltjahr = False
return schaltjahr
2
Wir haben uns für den Typ tuple statt list entschieden, da sich ja die Anzahl der Tage in einem Monat
nicht ändert, wenn man vom Februar absieht.
267
268
24 Mehrfachvererbung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __init__(self, d, m, y):
"""
d, m, y have to be integer values and year has to be
a four digit year number
"""
self.set_Calendar(d,m,y)
def set_Calendar(self, d, m, y):
"""
d, m, y have to be integer values and year has to be
a four digit year number
"""
if type(d) == int and type(m) == int and type(y) == int:
self.__days = d
self.__months = m
self.__years = y
else:
raise TypeError("d, m, y müssen ganze Zahlen sein!")
def __str__(self):
"""
Diese Methode überlädt die eingebaute Funktion str(),
d.h. es wird eine Methode zur Verfügung gestellt,
um ein Objekt der Klasse Calendar in einen String zu
wandeln.
Die Methode __str__ wird auch von der Print-Funktion
genutzt, um ein Objekt der Klasse Calendar auszugeben.
"""
return "{0:02d}.{1:02d}.{2:4d}".format(self.__days,
self.__months,
self.__years)
def advance(self):
"""
setzt den Kalender auf den nächsten Tag
unter Berücksichtigung von Schaltjahren
"""
max_days = Calendar.months[self.__months-1]
if self.__months == 2 and Calendar.leapyear(self.__years):
max_days += 1
if self.__days == max_days:
self.__days= 1
if self.__months == 12:
self.__months = 1
self.__years += 1
24.2 Beispiel: CalendarClock
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
else:
self.__months += 1
else:
self.__days += 1
if __name__ == "__main__":
x = Calendar(31,12,2012)
print(x, end=" ")
x.advance()
print("nach advance: ", x)
print("2012 war ein Schaltjahr:")
x = Calendar(28,2,2012)
print(x, end=" ")
x.advance()
print("nach advance: ", x)
x = Calendar(28,2,2013)
print(x, end=" ")
x.advance()
print("nach advance: ", x)
print("1900 war kein Schaltjahr: Zahl durch 100, aber nicht durch
400 teilbar:")
x = Calendar(28,2,1900)
print(x, end=" ")
x.advance()
print("nach advance: ", x)
print("2000 war ein Schaltjahr, weil die Zahl durch 400 teilbar
ist:")
x = Calendar(28,2,2000)
print(x, end=" ")
x.advance()
print("nach advance: ", x)
Obiges Skript liefert folgende Ausgabe, wenn man es selbständig startet:
$ python3 calendar.py
31.12.2012 nach advance: 01.01.2013
2012 war ein Schaltjahr:
28.02.2012 nach advance: 29.02.2012
28.02.2013 nach advance: 01.03.2013
1900 war kein Schaltjahr: Zahl durch 100, aber nicht durch 400 teilbar
:
28.02.1900 nach advance: 01.03.1900
2000 war ein Schaltjahr, weil die Zahl durch 400 teilbar ist:
28.02.2000 nach advance: 29.02.2000
In unserem Test haben wir keine Fehlertests eingebaut. Wir sehen aber im Code der Methode set_Calendar, dass wir einen Fehler erheben, wenn einer der Werte für das Datum
keine Ganzzahl ist. Wir testen die Fehlerfälle in der interaktiven Python-Shell:
269
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
270
24 Mehrfachvererbung
>>> from calendar import Calendar
>>> x = Calendar(31,12,2012)
>>> x = Calendar("31",12,2012)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "calendar.py", line 34, in __init__
self.set_Calendar(d,m,y)
File "calendar.py", line 47, in set_Calendar
raise TypeError("d, m, y müssen ganze Zahlen sein!")
TypeError: d, m, y müssen ganze Zahlen sein!
>>> x = Calendar(12,2012)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 4 arguments (3 given)
Nun geht es darum, eine Klasse CalendarClock zu implementieren, die eine Uhr mit
integriertem Kalender repräsentiert. Die Klasse CalendarClock erbt von den Klassen
Clock und Calendar. Die Methode tick von Clock wird von CalendarClock überschrieben. Die neue tick-Methode muss jedoch auf die tick-Methode von Clock zugreifen.
Man kann diese jedoch nicht mit self.tick() aufrufen, da dies die tick-Methode von
CalendarClock repräsentiert. Der Aufruf erfolgt deshalb mit Clock.tick(self). Die
advance-Methode von Calendar kann jedoch direkt mit self.advance() aufgerufen
werden. Natürlich wäre der Aufruf auch mit Calendar.advance(self) möglich.
"""
Modul, das die Klasse CalendarClock implementiert.
"""
from clock import Clock
from calendar import Calendar
class CalendarClock(Clock, Calendar):
"""
Die Klasse CalendarClock implementiert eine Uhr mit
integrierter Kalenderfunktion. Die Klasse erbt sowohl
von der Klasse Clock als auch von der Klasse Calendar.
"""
def __init__(self,day, month, year, hour, minute, second):
"""
Zur Initialisierung der Uhrzeit wird der Konstruktor der
Clock-Klasse aufgerufen. Zur Initialisierung des Kalenders
wird der Konstruktor der Calendar-Klasse aufgerufen.
CalendarClock enthält dann die vereinigten Attribute
der Clock- und Calendar-Klasse:
self.day, self.month, self.year, self.hour, self.minute, self.
second
24.2 Beispiel: CalendarClock
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
"""
Clock.__init__(self,hour, minute, second)
Calendar.__init__(self,day, month, year)
def tick(self):
"""
Die Position der Uhr wird um eine Sekunde weiterbewegt,
der Kalender wird, falls Mitternacht überschritten wird,
um einen Tag weiterbewegt.
"""
previous_hour = self._hours
Clock.tick(self)
if (self._hours < previous_hour):
self.advance()
def __str__(self):
"""
Erzeugt die Stringdarstellung eines CalendarClock-Objekts
"""
return Calendar.__str__(self) + ", " + Clock.__str__(self)
if __name__ == "__main__":
x = CalendarClock(31,12,2013,23,59,59)
print("One tick from ",x, end=" ")
x.tick()
print("to ", x)
x = CalendarClock(28,2,1900,23,59,59)
print("One tick from ",x, end=" ")
x.tick()
print("to ", x)
x = CalendarClock(28,2,2000,23,59,59)
print("One tick from ",x, end=" ")
x.tick()
print("to ", x)
x = CalendarClock(7,2,2013,13,55,40)
print("One tick from ",x, end=" ")
x.tick()
print("to ", x)
Ruft man obiges Modul standalone auf, erhält man folgende Ausgabe, die gleichzeitig auch
die Arbeitsweise nochmals ein wenig erklärt:
$ python3 calendar_clock.py
One tick from 31.12.2013 23:59:59
One tick from 28.02.1900 23:59:59
One tick from 28.02.2000 23:59:59
One tick from 07.02.2013 13:55:40
to
to
to
to
01.01.2014
01.03.1900
29.02.2000
07.02.2013
00:00:00
00:00:00
00:00:00
13:55:41
271
272
24 Mehrfachvererbung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
24.3
Diamond-Problem
Bei dem Diamond-Problem3 handelt es sich um
ein Mehrdeutigkeitsproblem, was bei Mehrfachvererbung in der objektorientierten Programmierung entstehen kann. Es kann auftreten, wenn eine
Klasse D auf zwei verschiedenen Vererbungspfaden
über zwei Klassen B und C von der gleichen Basisklasse A abstammt. Das Problem tritt auf, falls in D
eine Methode, sagen wir „m”, aufgerufen wird, für
die gilt:
■
■
■
m wird in A definiert.
Die Methode m wird entweder in A oder in B oder
auch in beiden überschrieben.
Bild 24.3 Diamond-Problem
m wird nicht in D überschrieben.
Die Frage, die sich dann stellt: Von welcher Klasse wird m vererbt? In Python hängt es zunächst einmal von der Reihenfolge der Klassen bei der Definition von D ab: Wir können die
verschiedenen Möglichkeiten mit einfachen Klassendefinitionen testen. Zuerst wollen wir
uns den Fall anschauen, dass sowohl B als auch C die Methode m überschreiben:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
class C(A):
def m(self):
print("m of C called")
class D(B,C):
pass
Ruft man x.m() für eine Instanz x der Klasse D auf, erhalten wir die Ausgabe „m of B
called”. Vertauschen wir nun die Reihenfolge der Klassen B und C im Klassenheader von
D, also „class D(C,B):”, dann erhalten wir die Ausgabe „m of C called”.
Von besonderem Interesse ist jedoch der Fall, wenn m nur entweder in B oder in C überschrieben wird:
class A:
def m(self):
print("m of A called")
3
englisch: „diamond problem” oder „deadly diamond of death”
24.4 super und MRO
class B(A):
pass
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class C(A):
def m(self):
print("m of C called")
class D(B,C):
pass
x = D()
x.m()
Im obigen Fall sind prinzipiell beide Möglichkeiten denkbar, d.h. „m von C” oder „m von
A” wird aufgerufen. Ruft man dieses Programm mit einer 2er-Version von Python auf, wird
m von A aufgerufen:
$ python diamond_problem.py
m of A called
Vor der Einführung der sogenannten „New Style”-Klassen4 in Python 2 erfolgte eine Tiefensuche5 im Vererbungsbaum, d.h. man geht solange in der Suche nach der Methode m
nach oben, bis es nicht mehr weiter nach oben geht. Dann sucht man von links nach rechts
weiter. Rufen wir das obige Skript nun unter Python 3 auf, wird – anders als bei Python 2 –
die Methode m der Klasse C aufgerufen:
$ python3 diamond_problem.py
m of C called
Ändert man die Vererbungsreihenfolge bei der Definition von D, also class D(C,B): statt
class D(B,C):, dann wird sowohl in Python 3 als auch in Python 2 die Methode m von C
für eine Instanz aus D benutzt.
Sicherlich fragen Sie sich noch, woher der Name „Diamond-Problem” kommt: Zeichnet
man die Vererbungsbeziehungen zwischen den Klassen in einem Diagramm, so sieht das
Ergebnis wie eine Raute aus, was im Englischen auch als „diamond” bezeichnet wird.
24.4
super und MRO
Wir haben eben gesehen, wie Python das Diamond-Problem löst, d.h. in welcher Reihenfolge die Basisklassen durchsucht werden. Diese Reihenfolge wird durch die sogenannte
„Method Resolution Order”, kurz MRO, festgelegt.6
4
5
6
In Python 2 wird die obige Definition der Klasse A als „Old Style”-Klasse betrachtet, da die New-Style-Klassen
immer von der Klasse „object” erben müssen, wenn sie nicht von einer anderen Klasse erben. Die Definition
in Python 2 für New Style würde also so aussehen: class A(object): pass
englisch: depth-first search, DFS
Python benutzt ab Version 2.3 den „C3 superclass linearization”-Algorithmus, um die MRO festzulegen.
273
274
24 Mehrfachvererbung
Im Folgenden wollen wir zeigen, wie man MRO in Verbindung mit super beim DiamondProblem sinnvoll einsetzen kann. Dazu erweitern wir alle Klassen unseres vorigen Beispiels
um eine Methode m. Die Methode m von D ruft die Methoden m von B und C auf. Die Methoden m von B und C rufen jeweils m von A auf:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
A.m(self)
class C(A):
def m(self):
print("m of C called")
A.m(self)
class D(B,C):
def m(self):
print("m of D called")
B.m(self)
C.m(self)
Wir haben aber nun das Problem, dass unsere Methode m von A zweimal aufgerufen wird,
d.h. einmal über B und einmal über C:
>>> from diamond_problem_super_m import D
>>> d = D()
>>> d.m()
m of D called
m of B called
m of A called
m of C called
m of A called
Eine Lösungsmöglichkeit – allerdings eine nicht-pythonische – besteht darin, die Methoden m in B und C in zwei Teile zu splitten, und zwar in dem speziellen Code, der für die
Klasse spezifisch ist (_m) und die eigentliche Methode m, die _m und m von A aufruft. In der
Methode m von D rufen wir dann die speziellen Methoden von B und C auf. Insgesamt haben wir das Problem – auf Kosten eines aufwendigen und schwer verständlichen Designs –
gelöst:
class A:
def m(self):
print("m of A called")
class B(A):
def _m(self):
print("m of B called")
24.4 super und MRO
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def m(self):
self._m()
A.m(self)
class C(A):
def _m(self):
print("m of C called")
def m(self):
self._m()
A.m(self)
class D(B,C):
def m(self):
print("m of D called")
B._m(self)
C._m(self)
A.m(self)
Wir können sehen, dass es so funktioniert, wie wir es uns gewünscht hatten:
>>> from super4 import D
>>> x = D()
>>> x.m()
m of D called
m of B called
m of C called
m of A called
Der optimale oder der „super” pythonische Weg besteht in der Benutzung der superFunktion von Python:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
super().m()
class C(A):
def m(self):
print("m of C called")
super().m()
class D(B,C):
def m(self):
print("m of D called")
super().m()
275
276
24 Mehrfachvererbung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir erhalten wieder die gewünschte Ausgabe, aber diesmal mit einem perfekten Design:
>>> from super5 import D
>>> x = D()
>>> x.m()
m of D called
m of B called
m of C called
m of A called
Die super-Funktion wird sehr häufig bei der Initialisierung von Instanzen, also bei der
__init__-Methode verwendet. Im Folgenden sehen wir ein Beispiel:
class A:
def __init__(self):
print("A.__init__")
class B(A):
def __init__(self):
print("B.__init__")
super().__init__()
class C(A):
def __init__(self):
print("C.__init__")
super().__init__()
class D(B,C):
def __init__(self):
print("D.__init__")
super().__init__()
Die Arbeitsweise verdeutlichen wir in der folgenden Benutzung der Klassen:
>>> from super_init import A,B,C,D
>>> d = D()
D.__init__
B.__init__
C.__init__
A.__init__
>>> c = C()
C.__init__
A.__init__
>>> b = B()
B.__init__
A.__init__
>>> a = A()
A.__init__
24.4 super und MRO
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Es stellt sich nun folgende Frage: Wie entscheidet die Funktion super, welche Klasse sie
benutzt? Dazu benutzt sie die eingangs erwähnte Method Resolution Order (MRO), die auf
dem „C3 superclass linearization”-Algorithmus aufbaut. Man spricht auch von einer Linearisierung, weil aus der Baumstruktur eine lineare Reihenfolge wird, also eine (geordnete)
Liste. Um diese Liste zu generieren, bietet Python die mro-Methode:
>>> from super_init import A,B,C,D
>>> D.mro()
[<class 'super_init.D'>, <class 'super_init.B'>, <class 'super_init.C
'>, <class 'super_init.A'>, <class 'object'>]
>>> B.mro()
[<class 'super_init.B'>, <class 'super_init.A'>, <class 'object'>]
>>> A.mro()
[<class 'super_init.A'>, <class 'object'>]
277
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
25
25.1
Slots
Erzeugung von dynamischen
Attributen verhindern
Die Attribute eines Objekts werden in einem Dictionary __dict__ gespeichert. Wie bei
jedem anderen Dictionary auch ist die Anzahl der Elemente nicht begrenzt. Mit anderen
Worten: Man kann einem Dictionary weitere Elemente hinzufügen, nachdem es definiert
worden ist. Aus diesem Grund kann man auch dem Objekt einer Klasse, so wie wir sie bisher definiert hatten, beliebige weitere Attribute zufügen.
>>>
...
...
>>>
>>>
>>>
class A:
pass
a = A()
a.x = 66
a.y = "dynamically created attribute"
Das Dictionary, welches die Attribute von „a” enthält, kann folgendermaßen angesprochen
werden:
>>> a.__dict__
{'x': 66, 'y': 'dynamically created attribute'}
Einige werden sich sicherlich gewundert haben, dass man zu den bisher von uns definierten Klassen dynamisch Attribute hinzufügen kann, während dies bei den meisten built-inKlassen wie zum Beispiel „int” oder „list” nicht möglich ist:
>>> x = 42
>>> x.a = "not possible to do it"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'a'
>>>
>>> lst = [34, 999, 1001]
>>> lst.a = "forget it"
280
25 Slots
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'a'
Ein Dictionary zu benutzen, um Attribute zu speichern, ist eine geeignete Möglichkeit. Daraus folgt aber auch, dass für Objekte mit wenigen Instanzattributen trotzdem viel Speicher benötigt wird. Der Speicherbedarf kann kritisch werden, wenn viele Instanzen erstellt
werden. Hier kommen die sogenannten Slots ins Spiel. Mit Slots haben wir die Möglichkeit, dem Speicherkonsum entgegenzuwirken. Statt eines dynamischen Dictionarys stellen Slots eine statische Struktur zur Verfügung, die ein weiteres Hinzufügen von Attributen
verhindert, sobald eine Instanz erstellt worden ist.
Um Slots in einer Klasse zu benutzen, muss man eine Liste mit dem Namen __slots__
definieren. Diese Liste muss alle Attribute beinhalten, die man benutzen will. Im folgenden
Beispiel demonstrieren wir eine Klasse, deren Slot-Liste nur den Namen des Attributs val
enthält.
class S(object):
__slots__ = ['val']
def __init__(self, v):
self.val = v
x = S(42)
print(x.val)
x.new = "not possible"
Wenn wir das Programm starten, werden wir bemerken, dass es nicht möglich ist, ein weiteres Attribut dynamisch zu erzeugen. Beim Versuch, das Attribut new zu erzeugen, stoßen
wir auf einen Fehler:
42
Traceback (most recent call last):
File "slots_ex.py", line 12, in <module>
x.new = "not possible"
AttributeError: 'S' object has no attribute 'new'
Wie bereits erwähnt, helfen Slots dabei, den Speicher nicht mit der Bildung unnötiger Objekte zu verschwenden. Seit Python 3.3 ist dieser Vorteil aber nicht mehr so besonders beeindruckend. Mit der Einführung von Python 3.3 werden Key-Sharing-Dictionaries für die
Speicherung von Objekten benutzt. Die Attribute der Instanzen sind in der Lage, einen Teil
ihres genutzten Speichers mit anderen zu teilen. So kann der Speicherbedarf von Programmen gesenkt werden, die viele Instanzen von Klassen erstellen, die nicht „built-in” sind.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
26
26.1
Dynamische Erzeugung
von Klassen
Beziehung zwischen „class” und
„type”
In diesem Kapitel möchten wir Sie etwas tiefer mit dem „magischen” Verhalten von Python
vertraut machen, wenn wir eine Klasse definieren oder eine Instanz einer Klasse erstellen.
Möglicherweise fragen Sie sich: „Muss ich die Details wirklich wissen?” Vermutlich nicht
. . . oder Sie gehören zu denen, die Klassen auf einem sehr fortgeschrittenem Niveau erstellen.
Zunächst konzentrieren wir uns auf die Verbindung zwischen „type” und „class”. Als Sie
eine Klasse definiert haben, haben Sie sich vielleicht gefragt, was hinter den Kulissen passiert. Wir haben bereits gesehen, dass type(object) die Klasse einer Instanz zurückliefert:
>>> x = 42
>>> y = ["hi", 42, [8, 99]]
>>> print(type(x), type(y))
<class 'int'> <class 'list'>
Wenden wir type nochmals auf das Ergebnis an, so erhalten wir „type” als Ergebnis.
>>> print(type(type(x)), type(type(list)))
<class 'type'> <class 'type'>
>>>
>>> print(type(int), type(list))
<class 'type'> <class 'type'>
Dies zeigt uns, dass Klassen wie list und int Instanzen der Klasse type sind. Allgemein
kann man sagen, dass alle bisher von uns selbst erstellten Klassen Instanzen der Klasse
type sind.
Python unterscheidet nicht wie andere Sprachen zwischen Klassen und Typen. In den
meisten Fällen werden die Begriffe synonym verwendet.
Die Tatsache, dass Klassen Instanzen der Klasse „type” sind, erlaubt es uns, Meta-Klassen
zu programmieren. Wir können Klassen kreieren, die von der Klasse „type” erben. Somit
ist eine Meta-Klasse eine Subklasse der Klasse „type”.
282
26 Dynamische Erzeugung von Klassen
Zunächst wollen wir jedoch zeigen, wie man mit type eine Klasse erzeugen kann. Statt mit
nur einem Argument kann type auch mit drei Argumenten aufgerufen werden:
type(classname, superclasses, attributes_dict)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wenn type mit drei Argumenten aufgerufen wird, liefert es ein neues type-Objekt zurück,
was einer Klasse entspricht. Das bietet uns eine dynamische Form des Klassen-Statements,
d.h. wir können so Klassen dynamisch erzeugen.
■
classname ist der String, der den Klassennamen angibt und zum name-Attribut wird.
■
superclasses ist eine Liste oder ein Tupel mit den Oberklassen unserer Klassen. Die
Liste oder das Tupel wird zum bases-Attribut.
■
attributes_dict ist ein Dictionary, welches als Namensraum unserer Klasse fungiert.
Es beinhaltet die Definitionen des Klassenkörpers und wird zum dict-Attribut.
Schauen wir uns eine einfache Klassendefinition an:
class A:
pass
x = A()
print(type(x))
Führt man diesen Programmcode aus, erhält man folgende Ausgabe:
<class '__main__.A'>
Mit type können wir ebenfalls die obige Klasse definieren:
>>> A = type("A", (), {})
>>> x = A()
>>> print(type(x))
<class '__main__.A'>
>>>
Generell bedeutet dies, dass wir Klassen mit
type(classname, superclasses, attributedict)
zur Laufzeit definieren können.
Wenn wir type() aufrufen, wird die __call__-Methode von type aufgerufen. Die __call__Methode ruft zwei weitere Methoden auf: __new__ und __init__.
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(cls, classname, superclasses, attributedict)
Die __new__-Methode erstellt und liefert ein neu geschaffenes Klassenobjekt zurück. Anschließend initialisiert die __init__-Methode das erstellte Objekt.
class Robot:
counter = 0
def __init__(self, name):
self.name = name
26.1 Beziehung zwischen „class” und „type”
def sayHello(self):
return "Hi, I am " + self.name
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def Rob_init(self, name):
self.name = name
Robot2 = type("Robot2",
(),
{"counter":0,
"__init__": Rob_init,
"sayHello": lambda self: "Hi, I am " + self.name})
x = Robot2("Marvin")
print(x.name)
print(x.sayHello())
y = Robot("Marvin")
print(y.name)
print(y.sayHello())
print(x.__dict__)
print(y.__dict__)
Nach Ausführung des Codes erhalten wir folgende Ausgabe:
Marvin
Hi, I am
Marvin
Hi, I am
{'name':
{'name':
Marvin
Marvin
'Marvin'}
'Marvin'}
Die Klassendefinitionen von Robot und Robot2 sind syntaktisch völlig verschieden, aber
logisch gesehen implementieren beide genau das Gleiche. Python sorgt im ersten Beispiel
dafür, dass alle Methoden und Attribute der Klasse Robot gesammelt werden, um sie dann
als Parameter dem Argument attributes_dict beim type-Aufruf zu übergeben. Python
macht also das Gleiche, wie wir es mit der Klasse Robot2 gemacht haben.
283
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
27
27.1
Metaklassen
Motivation
In diesem Kapitel möchten wir einige Ideen und Anreize zur Nutzung von Metaklassen präsentieren. Um ein paar Designprobleme zu demonstrieren, die mit Metaklassen
gelöst werden können, erstellen wir diverse
Philosopher-Klassen. Jede Philosopher-Klasse
(Philosopher1, Philosopher2 und so weiter)
benötigt die gleiche „Menge” an Methoden. In Bild 27.1 Plato
unserem Beispiel nur eine - the_answer - als Basis ihres Grübelns und Nachdenkens.
Ein schlechte Implementierung erreichen wir, indem wir in jede Philosopher-Klasse den
identischen Code schreiben:
class Philosopher1:
def the_answer(self, *args):
return 42
class Philosopher2:
def the_answer(self, *args):
return 42
class Philosopher3:
def the_answer(self, *args):
return 42
plato = Philosopher1()
print(plato.the_answer())
kant = Philosopher2()
# let's see what Kant has to say :-)
print(kant.the_answer())
Das Programm liefert die folgende Ausgabe, was wohl nicht schwer nachzuvollziehen ist:
286
27 Metaklassen
42
42
Wir stellen fest, dass wir mehrere Kopien der Methode the_answer implementiert haben.
Das ist ziemlich fehleranfällig und bedeutet mehr Aufwand zum Warten des Programms.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Bisher wissen wir, dass man redundanten Code am einfachsten dadurch vermeidet, dass
man eine Basis schafft, welche die Methode the_answer enthält. Jede Philosopher-Klasse
erbt dann von dieser Basisklasse:
class Answers:
def the_answer(self, *args):
return 42
class Philosopher1(Answers):
pass
class Philosopher2(Answers):
pass
class Philosopher3(Answers):
pass
plato = Philosopher1()
print(plato.the_answer())
kant = Philosopher2()
# let's see what Kant has to say :-)
print(kant.the_answer())
Auch hier erhalten wir wieder die gleiche Ausgabe wie im oberen Beispiel, also zweimal die
Zahl 42.
Auf diese Art hat jede Philosopher-Klasse immer die Methode the_answer. Nehmen wir
an, dass wir noch nicht wissen, ob die Methode gebraucht wird. Nehmen wir weiter an,
dass die Entscheidung darüber, ob die Methode gebraucht wird, zur Laufzeit getroffen
wird. Diese Entscheidung kann abhängig sein von Konfigurationsdateien, Benutzereingaben oder Berechnungen.
# the following variable would be set as the result of a runtime
calculation:
x = input("Do you need the answer? (y/n): ")
if x == "y":
required = True
else:
required = False
def the_answer(self, *args):
return 42
27.1 Motivation
class Philosopher1:
pass
if required:
Philosopher1.the_answer = the_answer
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class Philosopher2:
pass
if required:
Philosopher2.the_answer = the_answer
class Philosopher3:
pass
if required:
Philosopher3.the_answer = the_answer
plato = Philosopher1()
kant = Philosopher2()
# let's see what Plato and Kant have to say :-)
if required:
print(kant.the_answer())
print(plato.the_answer())
else:
print("The silence of the philosophers")
Das Programm liefert folgende Ausgabe:
Do you need the answer? (y/n): y
42
42
Falls wir die Frage nicht mit „y” beantworten, sieht das Ergebnis so aus:
Do you need the answer? (y/n): n
The silence of the philosophers
Auch bei dieser Lösung gibt es noch Nachteile. Sie ist ebenfalls fehleranfällig, weil wir wieder den gleichen Code zu jeder Klasse schreiben müssen. Wenn wir viele Methoden hinzufügen wollen, kann das ziemlich unübersichtlich werden.
Wir können unseren Ansatz verbessern, indem wir eine Manager-Funktion definieren,
um redundanten Code zu vermeiden. Die Manager-Funktion übernimmt die Aufgabe, die
Klassen entsprechend zu erweitern.
# the following variable would be set as the result of a runtime
calculation:
x = input("Do you need the answer? (y/n): ")
287
288
27 Metaklassen
if x == "y":
required = True
else:
required = False
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def the_answer(self, *args):
return 42
# manager function
def augment_answer(cls):
if required:
cls.the_answer = the_answer
class Philosopher1:
pass
augment_answer(Philosopher1)
class Philosopher2:
pass
augment_answer(Philosopher2)
class Philosopher3:
pass
augment_answer(Philosopher3)
plato = Philosopher1()
kant = Philosopher2()
# let's see what Plato and Kant have to say :-)
if required:
print(kant.the_answer())
print(plato.the_answer())
else:
print("The silence of the philosophers")
Dies ist eine brauchbare Lösung für unser Problem. Jedoch müssen wir darauf achten, dass
wir den Aufruf der Manager-Funktion augment_answer nicht vergessen. Der Code sollte
automatisch aufgerufen werden. Wir brauchen eine Möglichkeit, um sicherzustellen, dass
„bestimmter” Code automatisch im Anschluss einer Klassendefinition ausgeführt wird.
Im
Folgenden
benutzen
wir
einen
Klassendekorator,
d.h.
wir
schreiben
die
augment_answer-Funktion als Dekoratorfunktion um. Die Philosopher-Klassen können
wir nun entsprechend dekorieren:
27.1 Motivation
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
# the following variable would be set as the result of a runtime
calculation:
x = input("Do you need the answer? (y/n): ")
if x == "y":
required = True
else:
required = False
def the_answer(self, *args):
return 42
def augment_answer(cls):
if required:
cls.the_answer = the_answer
# we have to return the class now:
return cls
@augment_answer
class Philosopher1:
pass
@augment_answer
class Philosopher2:
pass
@augment_answer
class Philosopher3:
pass
plato = Philosopher1()
kant = Philosopher2()
# let's see what Plato and Kant have to say :-)
if required:
print(kant.the_answer())
print(plato.the_answer())
else:
print("The silence of the philosophers")
Das Programm liefert folgende Ausgabe:
Do you need the answer? (y/n): y
42
42
289
290
27 Metaklassen
27.2
Definition
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Eine Metaklasse ist eine Klasse, deren Instanzen Klassen sind. Wie eine „gewöhnliche”
Klasse das Verhalten ihrer Instanzen definiert, so definiert auch eine Metaklasse das Verhalten ihrer Instanzen, aber die Instanzen sind in diesem Fall Klassen.
Metaklassen werden nicht von allen objektorientierten Programmiersprachen unterstützt.
Die Programmiersprachen, die sie implementiert haben, unterscheiden sich jedoch erheblich in der Art, wie die Metaklassen implementiert sind.
Manche Programmierer sehen Metaklassen in Python als „Lösungen, die auf ein Problem
warten”.
Dennoch gibt es zahlreiche mögliche Anwendungsgebiete für Metaklassen. Um nur einige
zu nennen:
■
Messwerterfassung
■
Profiling
■
Konsistenzprüfungen von Schnittstellen
■
Registrierung von Klassen nach ihrer Entstehungszeit
■
Automatisches Hinzufügen von Methoden
■
Automatische Erzeugung von Properties
■
Proxies
■
Automatisches Locking und Synchronisation
27.3
Definition von Metaklassen in Python
Prinzipiell werden Metaklassen genau so definiert wie jede andere Klasse in Python. Jedoch erben Metaklassen von type. Ein weiterer Unterschied ist, dass eine Metaklasse
automatisch aufgerufen wird, wenn das Klassen-Statement eine Metaklassen-Endung bekommt. Mit anderen Worten: Wenn kein metaclass-Schlüsselwort im Klassen-Header
benutzt wird, so wird type() aufgerufen. Wenn metaclass im Klassen-Header steht, wird
die zugewiesene Klasse aufgerufen statt type().
Wir erstellen eine einfache Metaklasse. Sie ist nutzlos, außer dass sie den Inhalt der Argumente in der __new__ Methode ausgibt und die Ergebnisse von type.__new__ zurückgibt:
class LittleMeta(type):
def __new__(cls, clsname, superclasses, attributedict):
print("clsname: ", clsname)
print("superclasses: ", superclasses)
print("attributedict: ", attributedict)
return type.__new__(cls, clsname, superclasses, attributedict)
27.3 Definition von Metaklassen in Python
Im folgenden Beispiel benutzen wir die Metaklasse „LittleMeta”:
class S:
pass
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class A(S, metaclass=LittleMeta):
pass
a = A()
Die Ausgabe bestätigt das bisher Gesagte:
clsname: A
superclasses: (<class '__main__.S'>,)
attributedict: {'__qualname__': 'A', '__module__': '__main__'}
Wir sehen, dass LittleMeta.__new__ aufgerufen wird und nicht type.__new__. Kommen
wir auf das letzte Kapitel zurück: Wir definieren eine Metaklasse „EssentialAnswers”, die
fähig ist, die Methode augment_answer automatisch anzufügen:
x = input("Do you need the answer? (y/n): ")
if x == "y":
required = True
else:
required = False
def the_answer(self, *args):
return 42
class EssentialAnswers(type):
def __init__(cls, clsname, superclasses, attributedict):
if required:
cls.the_answer = the_answer
class Philosopher1(metaclass=EssentialAnswers):
pass
class Philosopher2(metaclass=EssentialAnswers):
pass
class Philosopher3(metaclass=EssentialAnswers):
pass
plato = Philosopher1()
print(plato.the_answer())
kant = Philosopher2()
# let's see what Kant has to say :-)
print(kant.the_answer())
291
292
27 Metaklassen
Die folgende Ausgabe stellt keine Überraschung dar:
Do you need the answer? (y/n): y
42
42
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir haben im Kapitel „Beziehungen zwischen Klasse und Typ” gelernt, dass Python nach
der Klassendefinition folgenden Aufruf macht:
type(classname, superclasses, attributes_dict)
Das passiert nicht, wenn eine Metaklasse im Header definiert wurde. Das haben wir im
vorigen Beispiel gemacht. Unsere Klassen Philosopher1, Philosopher2 und Philosopher3
wurden mit der Metaklasse EssentialAnswers gekoppelt. Darum wird EssentialAnswers
ausgeführt statt type.
EssentialAnswers(classname, superclasses, attributes_dict)
Genauer gesagt werden die Argumente des Aufrufs wie folgt gesetzt:
EssentialAnswers('Philopsopher1',
(),
{'__module__': '__main__', '__qualname__': '
Philosopher1'})
Analog dazu natürlich auch die anderen Philosopher-Klassen.
27.4
Singletons mit Metaklassen erstellen
Als weiteres Beispiel der Anwendung einer Metaklasse implementieren wir nun eine
Singleton-Klasse. Das Singleton-Muster ist ein Entwurfsmuster, welches die Erstellung
von Instanzen auf eine beschränkt. Es wird dann genutzt, wenn nur ein Objekt gebraucht
wird. Das Konzept kann, etwas verallgemeinert, auf die Instanziierung einer bestimmten
Anzahl von Objekten begrenzt werden. In der Mathematik werden Singletons für Mengen
mit exakt einem Element benutzt.
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args
, **kwargs)
return cls._instances[cls]
class SingletonClass(metaclass=Singleton):
pass
class RegularClass():
pass
27.5 Beispiel – Methodenaufrufe zählen
x = SingletonClass()
y = SingletonClass()
print(x == y, id(x), id(y))
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
x = RegularClass()
y = RegularClass()
print(x == y, id(x), id(y))
Die Prüfung auf Gleichheit ist im vorigen Beispiel äquivalent mit der Prüfung, ob die Identitäten gleich sind. Es wird also geprüft, ob es sich um dasselbe Objekt handelt, wie wir in
der Ausgabe des Programms sehen können:
True 140311429714720 140311429714720
False 140311429714776 140311429714832
27.5
27.5.1
Beispiel – Methodenaufrufe zählen
Einführung
Nach der Durcharbeit des Kapitels „Metaklassen” fragen Sie sich vielleicht, was mögliche Anwendungsfälle für Metaklassen sein könnten. Es gibt ein paar interessante Anwendungsfälle, und es ist nicht, wie wir anfangs zitiert hatten, „eine Lösung, die auf ein
Problem wartet”. Ein paar Beispiele hatten wir bereits genannt.
In diesem Kapitel möchten wir ein Metaklassenbeispiel ausarbeiten, das die Methoden
der Subklasse dekoriert. Die dekorierte Funktion, die vom Dekorateur zurückgeliefert wird,
macht es möglich, die Anzahl der Aufrufe der Subklasse zu zählen.
Das ist normalerweise eine der Aufgaben, die wir von einem Profiler erwarten. Wir können
also die Metaklasse für einfaches Profiling benutzen. Die Metaklasse ist einfach zu erweitern für weitere Profiling-Aufgaben.
27.5.2
Vorbereitungen
Bevor wir uns des Problems annehmen, möchten wir noch daran erinnern, wie auf Attribute einer Klasse zugegriffen wird. Wir demonstrieren dies nochmal anhand einer list-Klasse.
Wir erhalten die Liste aller nicht privaten Attribute einer Klasse – hier die random-Klasse –
mit folgendem Code:
import random
cls = "random" # Name der Klasse als String
all_attributes = [x for x in dir(eval(cls)) if not x.startswith("__")]
print(all_attributes)
293
294
27 Metaklassen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir erhalten folgende Ausgabe:
['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'Random', 'SG_MAGICCONST
', 'SystemRandom', 'TWOPI', '_BuiltinMethodType', '_MethodType', '
_Sequence', '_Set', '_acos', '_ceil', '_cos', '_e', '_exp', '_inst
', '_log', '_pi', '_random', '_sha512', '_sin', '_sqrt', '_test',
'_test_generator', '_urandom', '_warn', 'betavariate', 'choice', '
expovariate', 'gammavariate', 'gauss', 'getrandbits', 'getstate',
'lognormvariate', 'normalvariate', 'paretovariate', 'randint', '
random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', '
triangular', 'uniform', 'vonmisesvariate', 'weibullvariate']
Anschließend filtern wir noch die aufrufbaren Attribute heraus bzw. die public-Methoden
der Klasse:
methods = [x for x in dir(eval(cls)) if not x.startswith("__")
and callable(eval(cls + "." + x))]
print(methods)
Nun erhalten wir folgende Ausgabe:
['Random', 'SystemRandom', '_BuiltinMethodType', '_MethodType', '
_Sequence', '_Set', '_acos', '_ceil', '_cos', '_exp', '_log', '
_sha512', '_sin', '_sqrt', '_test', '_test_generator', '_urandom',
'_warn', 'betavariate', 'choice', 'expovariate', 'gammavariate',
'gauss', 'getrandbits', 'getstate', 'lognormvariate', '
normalvariate', 'paretovariate', 'randint', 'random', 'randrange',
'sample', 'seed', 'setstate', 'shuffle', 'triangular', 'uniform',
'vonmisesvariate', 'weibullvariate']
Um die nicht aufrufbaren Attribute zu filtern, können wir das Schlüsselwort „not” bei callable einfügen.
non_callable_attributes = [x for x in dir(eval(cls)) if not x.
startswith("__")
and not callable(eval(cls + "." + x))]
print(non_callable_attributes)
Ausgabe:
['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'SG_MAGICCONST', 'TWOPI
', '_e', '_inst', '_pi', '_random']
Normalerweise ist es weder empfohlen noch notwendig, Methoden wie folgt zu verwenden, aber es ist möglich:
lst = [3,4]
list.__dict__["append"](lst, 42)
lst
[3, 4, 42]
27.5 Beispiel – Methodenaufrufe zählen
27.5.3
Ein Dekorateur, um Funktionsaufrufe zu zählen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Nun wollen wir beginnen, die Metaklasse zu erstellen, die wir bereits am Anfang dieses
Kapitels angesprochen haben. Sie wird alle Methoden der Subklasse dekorieren mit dem
Dekorateur, der die Aufrufe zählt. Wir haben diesen Dekorateur bereits im Kapitel „Memoisation und Dekorateure” definiert:
def call_counter(func):
def helper(*args, **kwargs):
helper.calls += 1
return func(*args, **kwargs)
helper.calls = 0
helper.__name__= func.__name__
return helper
Wir können ihn wie gewohnt verwenden:
@call_counter
def f():
pass
print(f.calls)
for _ in range(10):
f()
print(f.calls)
Ausgabe:
0
10
27.5.4
Die Metaklasse „Aufrufzähler”
Jetzt haben wir alle „Zutaten” beisammen, um unsere Metaklasse zu schreiben. Wir fügen
unseren call_counter-Dekorateur als static-Methode ein:
class FuncCallCounter(type):
""" A Metaclass which decorates all the methods of the
subclass using call_counter as the decorator
"""
@staticmethod
def call_counter(func):
""" Decorator for counting the number of function
or method calls to the function or method func
"""
def helper(*args, **kwargs):
helper.calls += 1
295
296
27 Metaklassen
return func(*args, **kwargs)
helper.calls = 0
helper.__name__= func.__name__
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
return helper
def __new__(cls, clsname, superclasses, attributedict):
""" Every method gets decorated with the decorator
call_counter, which will do the actual call
counting
"""
for attr in attributedict:
if callable(attributedict[attr]) and not attr.startswith("
__"):
attributedict[attr] = cls.call_counter(attributedict[
attr])
return type.__new__(cls, clsname, superclasses, attributedict)
class A(metaclass=FuncCallCounter):
def foo(self):
pass
def bar(self):
pass
if __name__ == "__main__":
x = A()
print(x.foo.calls, x.bar.calls)
for i in range(10):
x.foo()
for i in range(5):
x.bar()
print(x.foo.calls, x.bar.calls)
Wir erhalten folgende Ausgabe:
0 0
10 5
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
28
Abstrakte Klassen
Ein Klasse gilt in der objektorientierten Programmierung als abstrakt, wenn sie sich nicht instanziieren lässt. Es lassen sich also keine Objekte von
dieser Klasse erzeugen. Abstrakte Klassen enthalten eine oder mehrere abstrakte Methoden. Unter einer abstrakten Methode versteht man eine Bild 28.1 Abstrakte Basis Klassen
Methode, die deklariert, aber nicht implementiert
wird. Leitet eine Klasse von einer abstrakten Klasse ab, müssen alle vererbten abstrakten
Methoden überschrieben und implementiert werden, ansonsten würde die erbende Klasse selbst zu einer abstrakten Klasse.
Da man in Python unter anderem keine Deklarationen kennt, kann man in Python „normalerweise” keine abstrakten Klassen definieren. Wir demonstrieren dies im folgenden Beispiel:
class AbstractClass:
def do_something(self):
pass
class B(AbstractClass):
pass
a = AbstractClass()
b = B()
Wenn wir dieses Programm starten, erkennen wir, dass es sich nicht um eine abstrakte
Klasse handelt, da
■
■
wir sie instanziieren können, d.h. a = AbstractClass() erhebt keine Ausnahme.
wir nicht gezwungen werden, do_something in der Klassendefinition von B zu überlagern.
Dennoch gibt es eine Möglichkeit, abstrakte Klassen in Python zu definieren. Zu diesem
Zweck wurde in Python das Modul abc eingeführt. abc ist ein Akronym für „Abstract Base
Classes” (ABCs).
298
28 Abstrakte Klassen
Im folgenden Beispiel zeigen wir, wie wir mit dem Modul abc und der dort definierten Klasse ABC eine abstrakte Klasse erzeugen können:
from abc import ABC, abstractmethod
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class AbstractClassExample(ABC):
def __init__(self, value):
self.value = value
super().__init__()
@abstractmethod
def do_something(self):
pass
Die Methode do_something haben wir mithilfe des Dekorators abstractmethod zu einer
abstrakten Methode gemacht. Dekoriert man eine Methode in einer abstrakten Klasse mit
abstractmethod, wird diese Methode zu einer abstrakten Methode. Außerdem wird sichergestellt, dass eine erbende Klasse diese Methode implementieren muss, d.h. sie muss sie
überlagern.
Wir zeigen im folgenden Beispiel, dass es nicht möglich ist, von AbstractClassExample
zu erben und in der erbenden Klasse die Methode do_something nicht zu überlagern:
class DoAdd42(AbstractClassExample):
pass
x = DoAdd42(4)
Man erhält folgende Ausgabe, wenn man obiges Programm startet:
---------------------------------------------------------------------TypeError
Traceback (most recent call last)
<ipython-input-9-83fb8cead43d> in <module>()
2
pass
3
----> 4 x = DoAdd42(4)
TypeError: Can't instantiate abstract class DoAdd42 with abstract
methods do_something
Im folgenden Code-Beispiel definieren wir nun zwei korrekte Unterklassen:
class DoAdd42(AbstractClassExample):
def do_something(self):
return self.value + 42
class DoMul42(AbstractClassExample):
def do_something(self):
return self.value * 42
28 Abstrakte Klassen
x = DoAdd42(10)
y = DoMul42(10)
print(x.do_something())
print(y.do_something())
Die Ausgabe sieht wie folgt aus:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
52
420
Falls der Eindruck entstanden sein sollte, dass wir die Methoden in der abstrakten Klasse
nur mittels pass definieren könnten, so trügt dies. Eine Methode kann ganz „normal” Code
im Body enthalten, aber dennoch muss diese Methode in der Unterklasse überlagert werden. Der Vorteil liegt jedoch darin, dass wir die Methode aus der abstrakten Klasse mittels
super() aufrufen können. Dies ermöglicht es uns, eine grundlegende Funktionalität in der
abstrakten Methode unterzubringen, welche dann durch die überlagernden Implementierungen der erbenden Klassen bereichert werden:
from abc import ABC, abstractmethod
class AbstractClassExample(ABC):
@abstractmethod
def do_something(self):
print("Grundlegende Funktionalitäten!")
class AnotherSubclass(AbstractClassExample):
def do_something(self):
super().do_something()
print("Bereicherungen durch AnotherSubclass!")
x = AnotherSubclass()
x.do_something()
Grundlegende Funktionalitäten!
Bereicherungen durch AnotherSubclass!
299
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
29
Aufgaben
1. Aufgabe:
Wir haben folgende einfache Roboterklasse geschrieben:
class Robot(object):
def __init__(self, name):
self.name = name
x = Robot("Marvin")
y = Robot("Hugo")
print(x.name, y.name)
Diese Klasse erfreut sich nun weltweit großer Beliebtheit. Wir haben allerdings ein
Problem: Die internationale Robotergewerkschaft konnte ein weltweites Verbot durchsetzen, dass Roboter nicht mehr „Hugo” genannt werden dürfen. Schreiben Sie nun
die Klasse so um, dass Roboter automatisch „Marvin” genannt werden, wenn jemand
versucht, sie „Hugo” zu taufen. Für die Benutzer der Klasse darf sich natürlich nichts
ändern, d.h. die letzten drei Zeilen müssen unverändert bleiben können.
Lösung: 1. Aufgabe, Seite 502
2. Aufgabe:
In dieser Aufgabe geht wieder um eine Roboterklasse. Uns interessiert nicht das Aussehen und die Beschaffenheit eines Roboters, sondern nur seine Position in einer imaginären „Landschaft”, die zweidimensional sein soll und durch ein Koordinatensystem beschrieben werden kann.
Bild 29.1 Roboter
Ein Roboter hat also zwei Attribute für die x- und die yKoordinate. Es empfiehlt sich, diese Informationen in einer 2erListe zusammenzufassen, also beispielsweise position = [3,4],
wobei dann 3 der x-Position und 4 der y-Position entspricht. Der
Roboter ist in eine der vier Richtungen „west”, „south”, „east”
oder „north” orientiert, was wir in einem Attribut speichern wollen. Außerdem sollten unsere Roboter auch Namen haben. Allerdings dürfen die Namen nicht länger als 10 Zeichen sein. Sollte
302
29 Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
jemand versuchen, dem Roboter einen längeren Namen zuzuweisen, soll der Name
auf 10 Zeichen abgeschnitten werden.
Um die Roboter im Koordinatensystem bewegen zu können, benötigen wir eine moveMethode. Die Methode erwartet einen Parameter „distance”, der angibt, um welchem
Betrag sich der Roboter in Richtung der eingestellten Orientierung bewegen soll. Wird
ein Roboter x beispielsweise mit x.move(10) aufgerufen und ist dieser Roboter östlich orientiert, also x.orientation == "east", und ist [3,7] die aktuelle Position des
Roboters, dann bewegt er sich 10 Felder östlich und befindet sich anschließend in
Position [3,17].
Lösung: 2. Aufgabe, Seite 502
3. Aufgabe:
Erweitern Sie die folgende Roboterklasse
class Roboter:
def __init__(self, name, baujahr):
self.name = name
self.baujahr = baujahr
um ein Attribut „energie” und eine Property „befinden”, die vier Stringwerte liefert:
■
„In Anbetracht meines Alters nicht so schlecht!”
falls das Alter das Roboters größer als 20 und der Energiewert kleiner als 50 ist.
■
„Super. Vor allem in Anbetracht meines Alters!”
falls das Alter das Roboters größer als 20 und der Energiewert nicht kleiner 50 ist.
■
„Ausgelaugt”
falls das Alter das Roboters kleiner oder gleich 20 ist und der Energiewert kleiner
als 50 ist.
■
„Super!”
falls das Alter das Roboters größer 20 und der Energiewert nicht kleiner 50 ist.
Lösung: 3. Aufgabe, Seite 505
4. Aufgabe:
Neandertal- oder Bierdeckelarithmetik: Üblicherweise zählen und rechnen wir im Dezimalsystem. Informatiker und Programmierer haben manchmal auch Kontakt mit dem
Binärsystem. Aber es gibt auch eine Berufsgruppe, die von Berufs wegen mit einem
unären Zahlsystem rechnet. Ja richtig, in der Gastronomie. In der folgenden Aufgabe
geht es um die Bierdeckelnotation oder auch Bierdeckelarithmetik. Es könnte auch die
Art gewesen sein, wie unsere in Höhlen lebenden Vorfahren mit Zahlen umgegangen
sind.
Das Unärsystem ist ein Zahlensystem, in dem lediglich eine Ziffer vorhanden ist, die
man üblicherweise mit einem senkrechten Strich bezeichnet. Also der Strich auf dem
Bierdeckel. Man könnte aber ebenso gut eine Null, eine Eins oder irgendein anderes
Zeichen verwenden.
29 Aufgaben
Es gilt:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
7d ezi mal = |||||||unär
Bild 29.2 Neandertal-Arithmetik
Schreiben Sie eine Klasse CaveInt,
die die vier Grundrechenarten für die
Neandertal-Arithmetik definiert. Auch
wenn die Höhlenmenschen keine
Null kannten, definieren wir einen
leeren String als die Null. Da es keine negativen Zahlen gibt, definieren
wir die Subtraktion wie folgt: x - y
ist gleich dem leeren String, wenn x
kleiner als y ist. Außerdem soll eine
Cast-Methode für die Wandlung von
unär nach int bereitgestellt werden.
Lösung: 4. Aufgabe, Seite 506
5. Aufgabe:
Wir haben in diesem Kapitel die Standardklasse list um die Methode push erweitert.
In Perl gibt es noch die Methode „splice”, mit der eine Folge von Elementen aus einer
Liste gelöscht werden kann und durch eine Liste von anderen Elementen ersetzt wird.
splice(offset, length, replacement)
„offset” entspricht dem ersten Index, ab dem die Elemente gelöscht werden sollen,
und der Parameter „length” gibt an, wie viele Elemente gelöscht werden sollen. An die
Stelle der gelöschten Elemente sollen die Elemente der Liste replacement eingefügt
werden.
Erweitern Sie nun die Klasse plist um eine Methode splice.
Lösung: 4. Aufgabe, Seite 506
6. Aufgabe:
Erweitern Sie die Klasse Length, die wir im Kapitel vorgestellt haben, um eine Subtraktion, Multiplikation und Division. Zusätzlich können Sie sie noch um die erweiterten
Zuweisungen erweitern, d.h. „+=”, „-=” usw.
Lösung: 6. Aufgabe, Seite 508
303
304
29 Aufgaben
7. Aufgabe:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Schreiben Sie eine abstrakte Klasse BasisFigure, die zwei Attribute size1 und
size2 hat. Außerdem soll diese Klasse zwei abstrakte Methoden area (Fläche)
und circumference (Umfang) definieren. Schreiben Sie dann zwei Unterklassen
Rectangle (Rechteck) und Circle (Kreis) als Konkretisierungen dieser abstrakten
Klasse.
x = Rectangle(2, 3)
print(x.circumference()) # gibt den Umfang des Rechtecks zurück
y = Circle(3)
print(y.area()) # gibt die Fläche des Kreises mit dem Radius 3
zurück
Lösung: 7. Aufgabe, Seite 511
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Teil III
Fortgeschrittenes Python
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
30
lambda, map, filter
und reduce
Wenn es nach Guido van Rossum, dem Autor von Python, gegangen wäre, würde dieses Kapitel in unserem Buch wahrscheinlich fehlen. Guido van Rossum hatte lambda, reduce(),
filter() und map() noch nie gemocht und sie bereits 1993 widerwillig in Python aufgenommen, nachdem er eine Codeerweiterung mit diesen Funktionalitäten von einem, wie er
glaubte, Lisp-Hacker erhalten hatte. In Python 3 sollten sie nach seinem Willen verschwinden.1
Dazu kann man die folgenden Gründe angeben:
■
■
■
Es gibt eine gleich mächtige Alternative zu lambda, filter, map und reduce, nämlich die
Listen-Abstraktion (englisch: list comprehension).
Die Listen-Abstraktion ist klarer und leichter verständlich.
Hat man sowohl die Listen-Abstraktion als auch „Lambda und Co.”, dann verletzt dies
das Python-Motto „Es soll (genau) einen offensichtlichen Weg geben, um ein Problem
zu lösen” („There should be one obvious way to solve a problem”).
30.1
lambda
Eine anonyme Funktion oder Lambda-Funktion ist eine Funktion,
die nicht über einen Namen verfügt. Eine solche Funktion kann deshalb nur über Verweise angesprochen werden.
Der lambda-Operator bietet eine Möglichkeit, anonyme Funktionen, also Funktionen ohne Namen, zu schreiben und zu benutzen. Lambda-Funktionen kommen aus der funktionalen Programmierung und wurden insbesondere durch die Programmiersprache
Lisp besonders bekannt. Sie können eine beliebige Anzahl von Parametern haben, führen einen Ausdruck aus und liefern den Wert
dieses Ausdrucks als Rückgabewert zurück.
1
Bild 30.1 Dateien
„About 12 years ago, Python aquired lambda, reduce(), filter() and map(), courtesy of (I believe) a Lisp hacker who missed them and submitted working patches. But, despite of the PR value, I think these features
should be cut from Python 3000.”, Guido van Rossum in „The fate of reduce() in Python 3000”, 10. März 2005
308
30 lambda, map, filter und reduce
Anonyme Funktionen sind insbesondere bei der Anwendung der map-, filter- und reduceFunktionen besonders vorteilhaft.
Schauen wir uns ein einfaches Beispiel einer lambda-Funktion an.
lambda x: x + 42
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beim obigen Beispiel handelt es sich um eine Funktion mit einem Argument „x”, die die
Summe von x und 42 zurückgibt.
Die allgemeine Syntax einer Lambda-Funktion sieht wie folgt aus:
lambda Argumentenliste: Ausdruck
Die Argumentenliste besteht aus einer durch Kommata getrennten Liste von Argumenten,
und der nach dem Doppelpunkt stehende Ausdruck ist ein beliebiger Ausdruck, der diese
Argumente benutzt.
Doch kommen wir zurück zu unserem einführenden Beispiel. Wie können wir unsere
Funktion
lambda x: x + 42
benutzen?
>>>
45
>>>
>>>
-55
>>>
...
...
42
43
44
45
46
47
48
49
50
51
(lambda x: x + 42)(3)
y = (lambda x: x + 42)(3) - 100
print(y)
for i in range(10):
(lambda x: x + 42)(i)
Natürlich kann man mit obigem Code viele beeindrucken, aber man hätte es natürlich
auch viel einfacher hinschreiben können:
>>> for i in range(10):
...
print(42 + i)
...
42
43
44
30.1 lambda
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
45
46
47
48
49
50
51
Eine andere Möglichkeit besteht darin, den lambda-Ausdruck einer Variablen zuzuweisen.
Mit diesem Namen können wir unsere Funktion jetzt wie eine „gewöhnliche” Funktion f
benutzen:
>>> f42 = lambda x: x + 42
>>> f42(4)
46
Aber dazu brauchten wir nicht die lambda-Notation. Wir hätten dies auch mit einer normalen Funktionsdefinition bewerkstelligen können:
>>> def f42(x):
...
return x + 42
...
>>> f42(4)
46
Nun kommen wir endlich zu einer sinnvollen Anwendung der lambda-Notation. Wir
schreiben eine Funktion mit dem Namen „anwenden”, die eine Funktion als erstes Argument und eine Liste als zweites Argument erwartet. Die Funktion „anwenden” wendet
auf jedes Element der übergebenen Liste die als erstes Argument übergebene Funktion an:
>>> def anwenden(f,liste):
...
ergebnis = []
...
for element in liste:
...
ergebnis.append(f(element))
...
return ergebnis
Wir können nun die Funktion „anwenden” mit unserer f42-Funktion und der Liste der Zahlen von 0 bis 9 aufrufen:
>>> anwenden(f42,range(10))
[42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
Wir haben oben den Funktionsnamen als Referenz auf unsere Funktion übergeben. Um
diese Funktion zu benutzen, hatten wir zuerst eine Funktion mit dem (hässlichen) Namen
f42 einführen müssen. Die Funktion f42 ist eine „Wegwerffunktion”, die wir nur einmal bei
dem Funktionsaufruf von „anwenden” benötigen. Sie können sich leicht vorstellen, dass
wir gegebenenfalls auch ähnliche Funktionen wie f43, f44 usw. benötigen könnten.
Aus diesem Grund wäre es natürlich bedeutend eleganter, wenn wir diese Funktionen direkt an unsere Funktion „anwenden” übergeben könnten, also ohne den Umweg mit der
Namensgebung. Dies ist mit der lambda-Notation möglich:
309
310
30 lambda, map, filter und reduce
>>> anwenden(lambda x: x + 42,range(10))
[42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Auch solche Anwendungen sind jetzt möglich:
>>> for i in [17, 22,42]:
...
anwenden(lambda x: x + i, range(10))
...
[17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
[22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
[42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
In obigem Beispiel haben wir mithilfe des Schleifenparameters i drei verschiedene Funktionen kreiert.
30.2
map
Nachdem wir uns im vorigen Kapitel intensiv mit der Funktion „anwenden” beschäftigt
hatten, stellt die von Python zur Verfügung gestellte Funktion „map” kein Problem dar. Im
Prinzip entspricht „map” unserer Funktion „anwenden”.
map ist eine Funktion mit zwei Argumenten:
r = map(func, seq)
Das erste Argument func ist eine Funktion und das zweite eine Sequenz (z.B. eine Liste oder
ein Tupel) seq. map wendet die Funktion func auf alle Elemente von seq an und schreibt
die Ergebnisse in ein map object, also ein Iterator. Rufen wir
map(lambda x: x + 42, range(10))
auf, entspricht dies fast unserem Aufruf
anwenden(lambda x: x + 42, range(10))
Damit es ist völlig gleich ist, müssen wir lediglich das map-Objekt noch in eine Liste wandeln:
>>> list(map(lambda x: x
[42, 43, 44, 45, 46, 47,
>>> anwenden(lambda x: x
[42, 43, 44, 45, 46, 47,
+ 42, range(10)))
48, 49, 50, 51]
+ 42, range(10))
48, 49, 50, 51]
In einem weiteren Beispiel wollen wir nun zeigen, welchen großen Vorteil diese Kombination aus lambda und map-Funktion mit sich bringt. Nehmen wir an, dass wir Listen mit
Temperaturwerten in Grad Celsius und Grad Fahrenheit haben. Wir möchten diese wechselseitig in die jeweils andere Temperaturskala wandeln.
Eine Temperatur C in Grad Celsius lässt sich mittels der Formel
9
∗ C + 32
5
30.2 map
in Grad Fahrenheit wandeln, und eine Temperatur F in Grad Fahrenheit lässt sich mittels
der Formel
5
∗ (F − 32)
9
in Grad Celsius wandeln.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Für das folgende Beispiel „vergessen” wir nun „lambda” und „map” und beschränken uns
auf eine „konventionelle” Programmierung:
def fahrenheit(T):
return ((9.0 / 5) * T + 32)
def celsius(T):
return (5.0 / 9) * ( T - 32 )
temp = (36.5, 37, 37.5,39)
def Fahrenheit2Celsius(F_liste):
erg = []
for F in F_liste:
erg.append(celsius(F))
return erg
def Celsius2Fahrenheit(C_liste):
erg = []
for C in C_liste:
erg.append(fahrenheit(C))
return erg
F_liste = Celsius2Fahrenheit(temp)
print(F_liste)
C_liste = Fahrenheit2Celsius(F_liste)
print(C_liste)
Unter Benutzung von lambda und map schrumpft unser obiges Codebeispiel in beachtlicher Weise:
temp = (36.5, 37, 37.5,39)
F_liste = list(map(lambda C: (5.0 / 9) * ( C - 32 ), temp))
print(F_liste)
C_liste = map(lambda F: (9.0 / 5) * F + 32, F_liste)
print(list(C_liste))
map kann auch gleichzeitig auf mehrere Listen angewendet werden. Dann werden die Argumente entsprechend ihrer Position und der Reihenfolge der Listenargumente mit den
Werten aus den Listen versorgt.
311
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
312
30 lambda, map, filter und reduce
>>> a = [1,2,3,4]
>>> b = [17,12,11,10]
>>> c = [-1,-4,5,9]
>>> list(map(lambda x,y:x+y, a,b))
[18, 14, 14, 14]
>>> list(map(lambda x,y,z:x+y+z, a,b,c))
[17, 10, 19, 23]
>>> list(map(lambda x,y,z : 2.5*x + 2*y - z, a,b,c))
[37.5, 33.0, 24.5, 21.0]
>>>
Wir sehen in dem obigen Beispiel, dass der Parameter x seine Werte aus der Liste a bezieht,
der Parameter y seine Werte aus der Liste b und der Parameter z schließlich seine Werte
aus der Liste c.
30.3
Filtern mit „filter”
Die Funktion
filter(funktion, liste)
bietet eine elegante Möglichkeit, diejenigen Elemente aus der Liste liste herauszufiltern,
für die die Funktion „funktion” True liefert.
Die Funktion
filter(f,iter)
benötigt als erstes Argument eine Funktion f, die Wahrheitswerte liefert. Diese Funktion
wird dann auf jedes Argument des Objekts „iter” angewendet. „iter” ist entweder ein sequentieller Datentyp wie beispielsweise eine Liste oder ein Tupel oder es ist ein iterierbares
Objekt. Liefert f True für ein x, dann wird x in der Ergebnisliste übernommen, ansonsten
wird x nicht übernommen.
>>>
>>>
>>>
[1,
>>>
>>>
[0,
>>>
>>>
>>>
...
>>>
>>>
[0,
>>>
fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
odd_numbers = list(filter(lambda x: x % 2, fibonacci))
print(odd_numbers)
1, 3, 5, 13, 21, 55]
even_numbers = list(filter(lambda x: x % 2 == 0, fibonacci))
print(even_numbers)
2, 8, 34]
# or alternatively:
even_numbers = list(filter(lambda x: x % 2 -1, fibonacci))
print(even_numbers)
2, 8, 34]
30.4 reduce
30.4
reduce
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Eingangs des Kapitels erwähnten wir bereits, dass Guido van Rossum lambda, map, filter
und reduce in Python 3 nicht mehr wollte. reduce ist diejenige Funktion, die er am wenigsten mag. Er schrieb wörtlich: „Dies ist in der Tat diejenige, die ich immer am meisten
gehasst habe ...”.2 Mit reduce war er erfolgreich. reduce wurde in das Modul „functools”
verbannt und gehört damit nicht mehr in den Kern der Sprache.
Die Funktion
reduce(func, seq)
wendet die Funktion func() fortlaufend auf eine Sequenz seq an und liefert einen einzelnen
Wert zurück. Die Funktion „func” ist eine Funktion, die zwei Argumente erwartet.
Falls seq = [ s1, s2, s3, ... , sn ] ist, funktioniert der Aufruf reduce(func, seq) wie folgt:
■
Zuerst wird func auf die beiden ersten Argumente s1 und s2 angewendet. Das Ergebnis
ersetzt die beiden Elemente s1 und s2.
■
Die Liste sieht damit wie folgt aus: [ func(s1, s2), s3, ... , sn ].
■
Im nächsten Schritt wird func auf func(s1, s2) und s3 angewendet.
■
Die Liste sieht damit wie folgt aus: [ func(func(s1, s2),s3), ... , sn ].
■
Dies wird solange fortgesetzt, bis nur noch ein Element übrig bleibt, d.h. man hat die
Liste auf ein Element reduziert.
Für den Fall n = 4 können wir die vorige Erklärung auch wie folgt illustrieren:
Das folgende Beispiel zeigt die Arbeitsweise von reduce() an einem einfachen Beispiel. Um
mit reduce zu arbeiten, müssen wir in Python 3 das Modul functools importieren. Das ist
der wesentliche Unterschied zu früheren Python-Versionen wie zum Beispiel Python 2.7:
>>> import functools
>>> functools.reduce(lambda x,y: x+y, [47,11,42,13])
113
>>>
2
„So now reduce(). This is actually the one I’ve always hated most, because, apart from a few examples involving + or *, almost every time I see a reduce() call with a non-trivial function argument, I need to grab pen
and paper to diagram what’s actually being fed into that function before I understand what the reduce() is
supposed to do.”
313
314
30 lambda, map, filter und reduce
Im folgenden Beispiel veranschaulichen wir die Arbeitsweise von reduce an einem konkreten Beispiel:
>>> reduce(lambda x,y: x+y, [47,11,42,13])
113
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im folgenden Diagramm sind die Ergebniswerte dargestellt:
30.5
Aufgaben
1. Aufgabe:
In einer Buchhandlung findet sich in einem Abrechnungsprogramm in Python eine
Liste mit Unterlisten mit folgendem Aufbau:
Bestellnummer
Buchtitel und Autor
Anzahl
Einzelpreis
34587
Learning Python, Mark Lutz
4
40.95
98762
Programming Python, Mark Lutz
5
56.80
77226
Head First Python, Paul Barry
3
32.95
Schreiben Sie ein Programm unter Benutzung von lambda und map, das als Ergebnis
eine Liste mit Zweier-Tupeln liefert. Jedes Tupel besteht aus der Bestellnummer und
dem Produkt aus der Anzahl und dem Einzelpreis. Das Produkt soll jedoch um 10,- €
erhöht werden, wenn der Bestellwert unter 100,00 € liegt.
Lösung: Lösungen zu Kapitel 30 (lambda, map, filter und reduce), Seite 518
2. Aufgabe:
Situation wie in voriger Aufgabe, aber jetzt sehen die Unterlisten wie folgt aus:
[Bestellnummer, (Artikel-Nr, Anzahl, Einzelpreis), ... (Artikel-Nr, Anzahl, Einzelpreis) ]
Schreiben Sie wieder ein Programm, das eine Liste mit 2-Tupel (Bestellnummer, Gesamtpreis) liefert.
Lösung: Lösungen zu Kapitel 30 (lambda, map, filter und reduce), Seite 518
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
31
31.1
Listen-Abstraktion/
List Comprehension
Die Alternative zu Lambda und Co.
Im vorigen Kapitel über lambda, filter, reduce und map haben wir
erfahren, dass Guido van Rossum diese Funktionen nicht mag und
sie am liebsten mit der Einführung von Python 3 entfernt hätte.
Stattdessen bevorzugt er die Listen-Abstraktion, die meist auch im
Deutschen als „List Comprehension” bezeichnet wird. Die ListenAbstraktion wurde mit der Version 2.0 in Python eingeführt. Die
Listen-Abstraktion ist eine elegante Methode, Mengen in Python zu
Bild 31.1
definieren oder zu erzeugen. Die List Comprehension kommt der
Durchfahrt verboten
mathematischen Notation von Mengen sehr nahe. In der Mathefür Lambda und Co.
matik definiert man die Quadratzahlen der natürlichen Zahlen als
2
{x |x ∈ N} oder die Menge der komplexen ganzen Zahlen {(x, y)|x ∈ Z ∧ y ∈ Z}.
Die Listen-Abstraktion ist eine einfache Methode, um Listen zu erzeugen. Man kann sie
als vollständigen Ersatz für den Lambda-Operator sowie die Funktionen map, filter und
reduce ansehen. Sie werden verwendet, um neue Listen zu erzeugen, bei denen jedes
Element durch Anwendung verschiedener Operationen aus einem Element einer anderen Liste oder eines Iterators erzeugt wird. Ebenso kann man Unterlisten erzeugen, d.h.
es werden nur Elemente aus einer anderen Liste, einem Tupel oder allgemein eines sequentiellen Datentyps übernommen, die bestimmte Bedingungen erfüllen. Im Folgenden
erzeugen wir die Liste der Quadratzahlen für die Zahlen von 1 bis 9 ohne Benutzung der
Listen-Abstraktion:
>>>
>>>
...
...
>>>
[1,
squares = []
for i in range(1, 10):
squares.append(i**2)
squares
4, 9, 16, 25, 36, 49, 64, 81]
Viel einfacher – das heißt in nur einer Codezeile – lässt sich dies unter Benutzung der
Listen-Abstraktion bewerkstelligen:
squares = [i**2 for i in range(1, 10)]
316
31 Listen-Abstraktion/List Comprehension
Unter Benutzung von map und lambda sieht es wie folgt aus:
>>> squares = list(map(lambda x: x**2, range(1, 10)))
>>> squares
[1, 4, 9, 16, 25, 36, 49, 64, 81]
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
map liefert einen Iterator auf ein list-Objekt zurück, während die Listen-Abstraktion eine
Liste zurückgibt.
31.2
Syntax
Die Listen-Abstraktion ist von eckigen Klammern umrahmt. Nach der öffnenden Klammer
steht ein Ausdruck, der von einem oder mehreren for-Ausdrücken und gegebenenfalls von
einer if-Bedingung gefolgt wird.
Im folgenden Beispiel erzeugen wir alle 2-Tupel mit Zahlen von 1 bis 6, deren Summe gleich
7 ist. Dies könnte beispielsweise den Ergebnissen von zwei Würfeln entsprechen:
>>> [ (x,y) for x in range(1, 7) for y in range(1, 7) if x + y == 7]
[(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)]
Dies ist äquivalent mit dem folgenden „konventionellen” Code, der keine Listen-Abstraktion
nutzt:
>>> t = []
>>> for x in range(1,7):
...
for y in range(1,7):
...
if x + y == 7:
...
t.append( (x,y) )
...
>>> t
[(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)]
31.3
Weitere Beispiele
Im Abschnitt über die map-Funktion hatten wir die Wandlung einer Liste mit Werten in
Grad Celsius in Fahrenheit-Werte mit der map-Funktion gelöst. Mit der List Comprehension lässt sich dies sehr einfach lösen:
>>> Celsius = [39.2, 36.5, 37.3, 37.8]
>>> Fahrenheit = [ ((float(9)/5)*x + 32) for x in Celsius ]
>>> print(Fahrenheit)
[102.56, 97.7, 99.14, 100.03999999999999]
31.4 Die zugrunde liegende Idee
Ein pythagoreisches Tripel oder pythagoreisches Zahlentripel besteht aus drei positiven
ganzen Zahlen mit der Eigenschaft a 2 + b 2 = c 2 . Ein solches Tripel wird üblicherweise mit
(a, b, c) notiert. Ein Beispiel stellen die Zahlen 3, 4 und 5 dar, also das Tripel (3, 4, 5).
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die pythagoreischen Tripel lassen sich leicht mittels List Comprehension berechnen, wie
das folgende Beispiel zeigt:
>>> [(x,y,z) for x in range(1,30) for y in range(x,30) for z in range(
y,30) if x**2 + y**2 == z**2]
[(3, 4, 5), (5, 12, 13), (6, 8, 10), (7, 24, 25), (8, 15, 17), (9, 12,
15), (10, 24, 26), (12, 16, 20), (15, 20, 25), (20, 21, 29)]
Im folgenden Beispiel berechnen wir das Kreuzprodukt aus zwei Mengen:
>>> colours = [ "red", "green", "blue" ]
>>> things = [ "house", "tree" ]
>>> coloured_things = [ (x,y) for x in colours for y in things ]
>>> print(coloured_things)
[('red', 'house'), ('red', 'tree'), ('green', 'house'), ('green', '
tree'), ('blue', 'house'), ('blue', 'tree')]
31.4
Die zugrunde liegende Idee
Die Idee, eine Liste quasi algorithmisch zu beschreiben, ist angelehnt an die mathematische Notation von Mengen.
Beispiel:
Die Menge aller ganzen Zahlen, die durch 4 teilbar sind:
{x|x ∈ Z ∧ x teilbar durch 4}
oder ein anderes Beispiel:
{x|10 <= x <= 100 ∧ x teilbar durch 4}
Das letzte Beispiel lässt sich wie folgt in Python schreiben:
>>> [ x for x in range(10,101) if not x % 4]
[12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76,
80, 84, 88, 92, 96, 100]
Der senkrechte Strich, also „|” in der mathematischen Notation, wird übrigens im Englischen bei der Mengenschreibweise meist als „for” gelesen, also im obigen Beispiel „All x
for which 10 is . . . ”
317
318
31 Listen-Abstraktion/List Comprehension
31.5
Anspruchsvolleres Beispiel
Berechnung der Primzahlen bis 100 nach dem Sieb des Eratosthenes:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>>
>>>
>>>
[2,
noprimes = [j for i in range(2, 8) for j in range(i*2, 100, i)]
primes = [x for x in range(2, 100) if x not in noprimes]
print(primes)
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,
67, 71, 73, 79, 83, 89, 97]
Das vorige Beispiel wollen wir nun in einer etwas allgemeineren Form schreiben, damit es
die Primzahlen für eine beliebige Zahl n berechnet:
>>>
>>>
>>>
>>>
from math import sqrt
n = 100
sqrt_n = int(sqrt(n))
no_primes = [j for i in range(2,sqrt_n) for j in range(i*2, n, i)]
Wenn wir uns no_primes ausgeben lassen, erkennen wir, dass es bei der Konstruktion dieser Liste ein Problem gibt. Diese Liste beinhaltet Doppeleinträge, was die Berechnung der
Liste der Primzahlen ineffizient werden lässt, da auch alle Doppeleinträge jeweils betrachtet werden müssen:
>>> no_primes
[4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38,
40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70,
72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 6, 9, 12,
15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60,
63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 8, 12, 16, 20,
24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84,
88, 92, 96, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70,
75, 80, 85, 90, 95, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72,
78, 84, 90, 96, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91,
98, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 18, 27, 36, 45,
54, 63, 72, 81, 90, 99]
Am besten wäre es, diese Doppeleinträge bereits gar nicht zu erzeugen.
31.6
Mengen-Abstraktion
Die Mengen-Abstraktion (Set Comprehension) ist analog zur Listen-Abstraktion (List Comprehension), aber sie liefert – wie der Name andeutet – eine Menge und nicht eine Liste zurück. Zur syntaktischen Unterscheidung zur Listen-Abstraktion benutzen wir geschweifte
Klammern, also „richtige Mengenklammern”.
Mit der Mengen-Abstraktion sind wir nun in der Lage, das vorige Problem zu lösen, also die
Nicht-Primzahl ohne Doppelte zu kreieren:
31.7 Rekursive Primzahlberechnung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>>
>>>
>>>
>>>
>>>
{4,
from math import sqrt
n = 100
sqrt_n = int(sqrt(n))
no_primes = {j for i in range(2,sqrt_n) for j in range(i*2, n, i)}
no_primes
6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28,
30, 32, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45, 46, 48, 49, 50,
51, 52, 54, 55, 56, 57, 58, 60, 62, 63, 64, 65, 66, 68, 69, 70,
72, 74, 75, 76, 77, 78, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91,
92, 93, 94, 95, 96, 98, 99}
>>> primes = {i for i in range(n) if i not in no_primes}
>>> print(primes)
{0, 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
61, 67, 71, 73, 79, 83, 89, 97}
31.7
Rekursive Primzahlberechnung
Eine effizientere Berechnung der Primzahlen bis zu einer natürlichen Zahl n stellen wir in
der folgenden rekursiven Implementierung in Python vor. Wir berücksichtigen darin, dass
man sich nur die Vielfachen der Primzahlen von 1 bis zur Quadratwurzel von n anschauen
muss:
from math import sqrt
def primes(n):
if n == 0:
return []
elif n == 1:
return []
else:
p = primes(int(sqrt(n)))
no_p = {j for i in p for j in range(i*2, n+1, i)}
p = {x for x in range(2, n + 1) if x not in no_p}
return p
for i in range(1,50):
print(i, primes(i))
31.8
Generatoren-Abstraktion
Die Generatoren-Abstraktion (Generator Comprehension) wurde mit Python 2.6 eingeführt. Die Syntax ist nahezu identisch mit der Listen-Abstraktionen, außer dass sie in runde
Klammern statt in eckige Klammern eingebettet sind. Erzeugt wird ein Iterator statt einer
319
320
31 Listen-Abstraktion/List Comprehension
Liste. Man hat sie eingeführt, weil häufig keine Liste benötigt wird, sondern nur die einzelnen Elemente, über die iteriert wird.
Im folgenden Summationsausdruck wird zunächst mittels der Listen-Abstraktion die Liste
der Quadratzahlen von 1 bis 10 gebildet. Danach wird die Funktion sum darauf angewendet, um diese Quadratzahlen zu summieren:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> sum([x**2 for x in range(1,11)])
385
Für die Funktion sum hätte aber auch ein Generator-Objekt statt einer Liste genügt. Speicherplatzschonend kann man obige Summe also auch so berechnen:
>>> sum(x**2 for x in range(1,11))
385
Sicherlich haben Sie bemerkt, dass im vorigen Ausdruck eigentlich ein Klammernpaar fehlte. Im Prinzip hätten wir den Ausdruck so schreiben müssen:
>>> sum((x**2 for x in range(1,11)))
385
Bei Reduktionsfunktionen, die ein sequentielles Datenobjekt auf einen Wert reduzieren, so
wie sum, min und max, brauchen wir also kein separates Klammernpaar. Sonst ist es aber
notwendig:
>>> (x**2 for x in range(1,11))
<generator object <genexpr> at 0x960e2ac>
>>> x**2 for x in range(1,11)
File "<stdin>", line 1
x**2 for x in range(1,11)
^
SyntaxError: invalid syntax
31.9
Aufgaben
1. Aufgabe:
Mittels einer Listen-Abstraktion soll die Menge aller Paare von natürlichen Zahlen
kleiner als ein gegebenes n gebildet werden, sodass die erste der Zahlen ohne Rest
durch die zweite teilbar ist und die beiden Zahlen nicht identisch sind.
Lösung: Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension), Seite 519
31.9 Aufgaben
2. Aufgabe:
Wie groß ist die Wahrscheinlichkeit, dass beim Wurf zweier Würfel die Summe der
Augenzahlen 7 entspricht?
Anmerkung: Wir erwarten natürlich, dass Sie die Aufgabe unter Benutzung von ListenAbstraktionen lösen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Lösung: Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension), Seite 519
3. Aufgabe:
Schreiben Sie eine Funktion, die obige Wahrscheinlichkeit für eine beliebige Summe
als Argument berechnet.
Lösung: Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension), Seite 519
321
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
32
32.1
Generatoren
und Iteratoren
Einführung
In der Informatik versteht man unter einem Generator
Programmcode – meist eine Routine oder Funktion –, der
dazu benutzt wird, das Iterationsverhalten einer Schleife zu kontrollieren. Man kann sich das wie eine Funktion
mit Parametern vorstellen, die bei einem Aufruf eine Folge
von Werten zurückliefert. Allerdings mit dem Unterschied,
dass diese Folge von Werten nicht, wie zum Beispiel bei
einer Liste, auf einmal zurückgeliefert wird, sondern ein
Wert nach dem anderen. Dies hat zwei Vorteile: Zum einen Bild 32.1 Generatoren
spart man Speicherplatz, weil eine solche Liste nie mit all
ihren Werten erzeugt werden muss, und zum anderen, weil die Schleife schneller ihre ersten Werte erhält, denn man muss nicht warten, bis die gesamte Folge von Werten berechnet worden ist (falls Letzteres überhaupt möglich ist). Generatoren können auch potenziell
unendliche Folgen von Werten liefern. Ein Generator ist also im Prinzip eine Funktion, die
sich wie ein Iterator verhält. Deshalb werden Generatoren, wie wir schon eingangs erwähnt
haben, meistens in Schleifen verwendet.
Auch wenn man denken könnte, dass es sich bei Generatoren um neue und moderne
Software-Technik handelt, wurden sie bereits 1975 in CLU und 1977 in Icon verwendet.
32.2
Iteration in for-Schleifen
Iteratoren haben wir bereits in for-Schleifen kennengelernt und häufig benutzt, ohne im
Besonderen darauf einzugehen. Im folgenden Beispiel durchlaufen wir die Elemente der
Liste „cities”.
>>> cities = ["Hamburg", "Berlin", "München",
>>> for city in cities:
...
print("Stadt: " + city)
...
"Zürich"]
324
32 Generatoren und Iteratoren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Stadt:
Stadt:
Stadt:
Stadt:
Hamburg
Berlin
München
Zürich
Schauen wir uns einmal an, was Python genau macht, um die for-Schleife auszuführen: Bevor die Schleife gestartet wird, ruft Python die eingebaute1 Funktion iter mit der Liste „cities” als Argument auf. Die Funktion iter liefert nun ein Objekt zurück, mit dem es möglich
ist, die Elemente der Liste zu durchlaufen. Der Rückgabewert von iter() ist ein Element der
Klasse „list_iterator”, also ein Iterator. Die eigentliche „Arbeit” erledigt die Methode __iter__
der list-Klasse, die von der Funktion iter() aufgerufen wird. Um das weitere Vorgehen bei
der for-Schleife zu verstehen, nehmen wir an, dass das Resultat von iter() an eine Variable
iter_i gebunden wird. Also in unserem Fall iter_i = iter(cities). Nach jedem Schleifendurchlauf wird die next-Funktion mit iter_x als Argument aufgerufen und das Ergebnis
der Variablen iter_x zugewiesen. Die Schleife wird solange wiederholt, bis next(iter_x) die
Ausnahme StopIteration erzeugt.
Man kann diese Iteration auch „manuell” nachvollziehen:
>>> l = [23, 34, 56, 78]
>>> iter_x = iter(l)
>>> next(iter_x)
23
>>> next(iter_x)
34
>>> next(iter_x)
56
>>> next(iter_x)
78
>>> next(iter_x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Man kann die Iteration der for-Schleife durch folgenden Code simulieren:
cities = ["Hamburg", "Berlin", "München", "Stuttgart", "Zürich"]
iterator = iter(cities)
while True:
try:
city = next(iterator)
except StopIteration:
break
print("Stadt: " + city)
1
Häufig wird hierfür der englische Begriff „built-in” benutzt!
32.3 Generatoren
Die sequentiellen Basistypen sowie ein Großteil der Klassen der Standardbibliothek von
Python unterstützen Iterationen. Auch der Datentyp Dictionary (dict) unterstützt die Iteration. In diesem Fall läuft die Iteration über die Schlüssel des Dictionarys:2
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
hauptstaedte = { "Österreich":"Wien",
"Deutschland":"Berlin",
"Schweiz":"Bern" }
for land in hauptstaedte:
print(land + ": " + hauptstaedte[land])
Ausgegeben werden nur die Schlüsselwerte des Dictionarys:
Schweiz: Bern
Österreich: Wien
Deutschland: Berlin
32.3
Generatoren
Doch kommen wir zurück zu unserem ersten Beispiel über Städtenamen. Wir können daraus ein erstes Beispiel für einen Generator bilden:
def city_generator():
cities = ["Paris", "Berlin", "London", "Wien"]
for city in cities:
yield city
for city in city_generator():
print(city)
Trifft der Programmfluss auf eine yield-Anweisung, wird der Generator wie bei einer
return-Anweisung verlassen, aber Python „merkt” sich, wo der Generator verlassen wird,
und gleichzeitig werden auch alle Zustände, also die lokalen Variablen, für einen weiteren Aufruf erhalten. Zur Erinnerung: Bei Funktionen werden alle lokalen Variablen beim
Verlassen gelöscht und bei einem neuen Aufruf wieder neu angelegt.
Ruft man dieses Programm auf, werden die Städtenamen ausgegeben:
$ python3 city_generator.py
Paris
Berlin
London
Wien
2
Bern ist der Bundessitz der Schweiz, aber wird nicht Haupt-, sondern Bundesstadt genannt. Bern ist nur de
facto, aber nicht de jure Hauptstadt der Schweiz.
325
326
32 Generatoren und Iteratoren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die for-Schleife endet also, sobald alle Städte im Generator durchlaufen sind. Wir können
im Generator allerdings auch eine Endlosschleife verwenden.
def city_generator():
cities = ["Hamburg", "Berlin", "München",
while True:
city = cities.pop(0)
yield city
cities.append(city)
"Zürich"]
Speichern wir diese Funktionsdefinition in einer Datei cities_forever.py, können wir
sie interaktiv in der Python-Shell testen:
>>> from cities_forever import city_generator
>>> x = city_generator()
>>> next(x)
'Hamburg'
>>> next(x)
'Berlin'
>>> next(x)
'München'
>>> next(x)
'Zürich'
>>> next(x)
'Hamburg'
>>> next(x)
'Berlin'
Wir sehen also, dass wir einen Generator, der eine Endlosschleife beinhaltet, durchaus
sinnvoll benutzen können. Wir dürfen ihn lediglich nicht in einer Endlosschleife aufrufen, wie es im folgenden interaktiven Skript geschieht. Wir müssen dann die Schleife mit
„Ctrl-C”’ abbrechen:
>>> from cities_forever import city_generator
>>> for city in city_generator():
...
print(city, end=", ")
...
Hamburg, Berlin, München, Zürich, Hamburg, Berlin,
Zürich, Hamburg, Berlin, München, Zürich, Hamburg,
München, Zürich, Hamburg, Berlin, München, Zürich,
Berlin, München, Zürich, Hamburg, Berlin, München,
Hamburg, Berlin, ...
München,
Berlin,
Hamburg,
Zürich,
Um eine solche Endlosschleife zu vermeiden, können wir unsere for-Schleife mit einem
Zähler und einem break versehen:
def city_generator():
cities = ["Paris","Berlin","London","Wien"]
while True:
city = cities.pop(0)
32.4 Generatoren zähmen mit firstn und islice
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
yield city
cities.append(city)
if __name__ == "__main__":
count = 0
for city in city_generator():
if count == 8:
print()
break
print(city, end=", ")
count += 1
Ein Aufruf liefert dann, wie erwartet, folgende Ausgabe:
Paris, Berlin, London, Wien, Paris, Berlin, London, Wien,
32.4
Generatoren zähmen
Obiges Abbruchproblem können wir auch eleganter lösen, indem wir einen Generator
firstn(g, n) schreiben, der die ersten n Elemente eines Generators g ausgibt:
def firstn(generator, n):
iterator = generator()
for i in range(n):
yield next(iterator)
def city_generator():
cities = ["Paris", "Berlin", "London", "Wien"]
while True:
city = cities.pop(0)
yield city
cities.append(city)
if __name__ == "__main__":
for city in firstn(city_generator, 9):
print(city, end=", ")
print()
Wir erhalten hier folgendes Ergebnis:
Paris, Berlin, London, Wien, Paris, Berlin, London, Wien, Paris,
Aber Python wäre nicht Python, wenn es nicht eine noch elegantere Methode gäbe. Man
findet sie im Modul itertools. Mit der Methode islice kann man sich die Elemente von
einer Start- bis zu einer Endposition ausgeben lassen. Im nächsten Beispiel sind dies die
ersten fünf Elemente, die von city_generator generiert werden:
327
328
32 Generatoren und Iteratoren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> import itertools
>>> from cities_forever import city_generator
>>> itertools.islice(city_generator(),0,5)
<itertools.islice object at 0xb713f4b4>
>>> cities = itertools.islice(city_generator(),0,5)
>>> list(cities)
['Paris', 'Berlin', 'London', 'Wien', 'Paris']
32.5
32.5.1
Beispiele
Permutationen
Unter einer Permutation (von lateinisch permutare, vertauschen) versteht man in der Kombinatorik eine Anordnung von Objekten in einer
bestimmten Reihenfolge. Wenn Objekte mehrfach auftreten dürfen, spricht man von einer Permutation mit Wiederholung, ansonsten spricht
man von einer Permutation ohne Wiederholung.
Im Folgenden wollen wir uns nur mit Permutationen ohne Wiederholung beschäftigen und
einen Generator schreiben, der alle Permutationen eines sequentiellen Datenobjekts erzeugt, also z.B. von einem String, einer Liste oder einem
Tupel.
Bild 32.2 Permutationen von vier
Farben auf vier Plätzen
Die Anzahl der Permutationen von n Objekten berechnet sich durch die Fakultät:
n! = n · (n − 1) . . . 2 · 1
Auch wenn das hier sehr abstrakt klingt, kennt man Permutationen aus dem täglichen Leben:
■
■
Das Mischen der Karten eines Kartenspiels: Der Kartenstapel entspricht einer Permutation aller Karten.
Ein Anagramm ist eine Permutation der Buchstaben eines Wortes: Lager, Regal, erlag
def permutations(items):
n = len(items)
if n==0:
yield []
else:
for i, item in enumerate(items):
for cc in permutations(items[:i] + items[i+1:]):
yield [item] + cc
32.5 Beispiele
print("""permutations for (['r','e','d']""")
for p in permutations(['r','e','d']):
print(''.join(p))
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
print("""permutations of the letters of the string "bin" """)
for p in permutations(list("bin")):
print(''.join(p))
Obiges Programm liefert folgende Ausgabe:
permutations for (['r','e','d']
red
rde
erd
edr
dre
der
permutations of the letters of the string "bin"
bin
bni
ibn
inb
nbi
nib
32.5.2
Variationen und Kombinationen
Wählt man aus einer n-elementigen Menge von Objekten k Objekte unter Berücksichtigung der Reihenfolge ohne Zurücklegen aus, so bezeichnet man dies als eine Variation ohne Wiederholung. Die Permutationen ergeben sich als ein Sonderfall für n = k.
Die Anzahl der verschiedenen k-Variationen von n Elementen lässt sich wie folgt berechnen:
n!
= n · (n − 1) · (n − 2)...(n − k + 1)
(n − k)!
Der folgende Generator „variations” liefert alle k-Variationen ohne Wiederholungen:
def variations(items, k):
if k==0:
yield []
else:
for item in items:
for v in variations(items, k-1):
if item not in v:
yield [item] + v
329
330
32 Generatoren und Iteratoren
for variation in variations(["a", "b", "c"], 2):
print(variation)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Das Ergebnis lautet:
['a',
['a',
['b',
['b',
['c',
['c',
'b']
'c']
'a']
'c']
'a']
'b']
Spielt die Reihenfolge bei der Auswahl ohne Zurücklegen keine Rolle, so bezeichnet man
dies als eine Kombination ohne Wiederholung. Die k-Kombinationen von n Elementen
kann man auch als die k-elementigen Teilmengen der n Elemente ansehen. Bei der Lotterie 6 aus 49 handelt es sich beispielsweise um eine solche Kombination.
Die Anzahl der verschiedenen k-Kombinationen von n Elementen berechnet sich wie folgt:
à ! Ã
!
n · (n − 1) · (n − 2)...(n − k + 1)
n
n
n!
=
=
=
k! · (n − k)!
k!
k
n −k
Der folgende Generator „combinations” liefert alle k Kombinationen aus „elements” ohne
Zurücklegen:
def combinations(elements, k):
s = set(elements)
while s:
el = s.pop()
if k==1:
yield {el}
for combination in combinations(s, k-1):
combination.add(el)
yield combination
for c in combinations("abcd", 3):
print(c)
Das Ergebnis lautet:
{'c',
{'c',
{'c',
{'b',
'b',
'b',
'a',
'a',
'a'}
'd'}
'd'}
'd'}
Falls Sie allerdings nur an einem Lottogewinn interessiert sind, können Sie auch die Funktion sample aus dem Modul random benützen.
random.sample(population, k)
32.6 Generator-Ausdrücke
population ist eine Sequenz (z.B. Liste), aus der k Elemente ausgewählt und in einer neuen
Liste zurückgegeben werden. Dabei wird „population” aber nicht verändert.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> import random
>>> print(random.sample(range(1,50),6))
[19, 10, 30, 2, 14, 41]
Obige Zahlen können Sie gerne als Tipp für Ihren Lottoschein verwenden. Vergessen Sie
bitte nicht den Autor dieses Buches, sollten Sie einen Sechser haben ,.
32.6
Generator-Ausdrücke
Generator-Ausdrücke sind Listen-Abstraktionen (List Comprehensions) sehr ähnlich. Statt
eckiger Klammern werden runde Klammern verwendet. Anders als Listen-Abstraktionen
werden keine vollständigen Listen erzeugt, sondern Generatoren, die ihre Elemente wie
Generatoren eines nach dem anderen zurückliefern.
Den Zusammenhang zwischen Listen-Abstraktion und Generator-Ausdrücken zeigt das
nächste Beispiel:
>>> squares1 = [i**2 for i in
>>> squares1
[1, 4, 9, 16, 25, 36, 49, 64,
>>> squares2 = (i**2 for i in
>>> type(squares2)
<class 'generator'>
>>> list(squares2)
[1, 4, 9, 16, 25, 36, 49, 64,
range(1, 10)]
81]
range(1, 10))
81]
Man sieht, dass list(squares2) und squares1 identisch sind:
>>> squares = (i**2 for i in range(1, 4))
>>> next(squares)
1
>>> next(squares)
4
>>> next(squares)
9
>>> next(squares)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Der obigen Listenausdruck ist identisch mit dem folgenden Generator:
def squares():
for i in range(1, 4):
yield i ** 2
331
332
32 Generatoren und Iteratoren
32.7
return-Anweisungen in Generatoren
Seit Python 3.3 kann man innerhalb von Generatoren auch return-Anweisungen benutzen. Allerdings muss ein Generator dennoch mindestens eine yield-Anweisung haben, um
ein Generator zu sein. Eine return-Anweisung innerhalb eines Generators entspricht einer
raise StopIteration().
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Schauen wir uns im Folgenden einen Generator an, in dem wir eine StopIteration erheben:
>>> def gen():
...
yield 1
...
raise StopIteration(42)
...
>>>
>>> g = gen()
>>> next(g)
1
>>> next(g)
Traceback (most recent call last):
File "", line 1, in
File "", line 3, in gen
StopIteration: 42
>>>
Wenn wir raise StopIteration(42) durch return 42 austauschen, sehen wir, dass wir
bis auf eine Zeile Unterschied im Traceback das gleiche Ergebnis wie vorher erhalten:
>>> def gen():
...
yield 1
...
return 42
...
>>>
>>> g = gen()
>>> next(g)
1
>>> next(g)
Traceback (most recent call last):
File "", line 1, in
StopIteration: 42
>>>
32.8
send-Methode / Koroutinen
Generatoren können nicht nur mittels yield Objekte liefern, sondern man kann auch
„Nachrichten” (Objekte) an Generatoren senden. Dies erreicht man dadurch, dass man
statt next die send-Methode benutzt. Die send-Methode verhält sich im Prinzip wie die
32.8 send-Methode / Koroutinen
next-Methode, aber sendet gleichzeitig noch einen Wert an den Generator. Im Prinzip
sendet auch next einen Wert, aber dieser ist immer None.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir demonstrieren dieses Verhalten in folgendem Beispiel:
>>> def simple_coroutine():
...
print("Koroutine wurde gestartet!")
...
x = yield
...
print("Koroutine empfing: ", x)
...
>>> cr = simple_coroutine()
>>> cr
<generator object simple_coroutine at 0x7fc08820d990>
>>> next(cr)
Koroutine wurde gestartet!
>>> cr.send("hi")
Koroutine empfing: hi
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Wir mussten den Generator zuerst mit next aufrufen, weil der Generator zuerst gestartet
werden musste. Wendet man send auf einen Klassifikator an, der noch nicht gestartet worden ist, so führt dies zu einer Ausnahme:
>>> cr = simple_coroutine()
>>> cr.send("Hi")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
Damit wir die send-Methode benutzen können, muss der Generator in einer yieldAnweisung positioniert sein, d.h. er muss bereits vorher einen Wert mittels yield geliefert
haben und wartet auf einen Aufruf von next oder send.
Steht yield auf der rechten Seite einer Zuweisung, so wird der Wert des Arguments von
send an die Variable auf der linken Seite übergeben. Wird der Generator mit next aufgerufen, so wird None übergeben.
Den nachfolgenden Generator haben wir infinite_looper genannt, weil wir ihn mit einem sequentiellen Datentyp aufrufen können und der Generator dann in der Lage ist,
„ewig” über dieses Objekt zu iterieren, d.h. er beginnt automatisch wieder mit dem ersten Element, wenn das letzte abgearbeitet worden ist. Mittels der send-Methode können
wir diesen Generator auf eine beliebige Position des sequentiellen Datentyps umsetzen.
def infinite_looper(objects):
count = 0
while True:
if count >= len(objects):
count = 0
message = yield objects[count]
333
334
32 Generatoren und Iteratoren
if message != None:
count = 0 if message < 0 else message
else:
count += 1
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir zeigen in der folgenden interaktiven Sitzung, wie man diesen Generator nutzen kann.
>>>
>>>
'E'
>>>
'r'
>>>
' '
>>>
'S'
x = infinite_looper("Ein kleiner String")
next(x)
x.send(10)
next(x)
next(x)
32.9
Die throw-Methode
Mit Python 2.5 wurde die throw-Methode für Generator-Objekte hinzugefügt. Dadurch ist
es möglich, innerhalb des Generator-Objekts eine Ausnahme zu erheben, so als wäre sie
von der yield-Anweisung erhoben worden. Der Generator muss diese Ausnahme auffangen; ansonsten wird die Ausnahme wieder an den Aufrufer propagiert.
Der infinite_looper-Generator aus unserem vorigen Beispiel liefert die Elemente des
sequentiellen Datentyps zurück, aber wir erhalten keinerlei Information über die Variable
count. Wir können die throw-Methode nutzen, um an diese Information zu gelangen. Wir
fangen die exception auf und geben den Wert von count aus, wenn die throw-Methode
aufgerufen wird:
def infinite_looper(objects):
count = 0
while True:
if count >= len(objects):
count = 0
try:
message = yield objects[count]
except Exception:
print("index: " + str(count))
message = None
if message != None:
count = 0 if message < 0 else message
else:
count += 1
Dieser Generator lässt sich wie folgt benutzen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
32.9 Die throw-Methode
>>> from generator_throw import infinite_looper
>>> looper = infinite_looper("Python")
>>> next(looper)
'P'
>>> looper.throw(Exception)
index: 0
'y'
>>> next(looper)
't'
>>> looper.throw(Exception)
index: 2
'h'
Das vorige Beispiel können wir noch verbessern, indem wir unsere eigene AusnahmeKlasse StateOfGenerator definieren:
class StateOfGenerator(Exception):
def __init__(self, message=None):
self.message = message
def infinite_looper(objects):
count = 0
while True:
if count >= len(objects):
count = 0
try:
message = yield objects[count]
except StateOfGenerator:
print("index: " + str(count))
if message != None:
count = 0 if message < 0 else message
else:
count += 1
Beispiel für die Benutzung des gerade definierten Generators:
>>> from generator_throw import infinite_looper, StateOfGenerator
>>> looper = infinite_looper("Python")
>>> next(looper)
'P'
>>> next(looper)
'y'
>>>
>>> looper.throw(StateOfGenerator)
index: 1
't'
>>> next(looper)
'h'
>>>
335
336
32 Generatoren und Iteratoren
32.10
Dekoration von Generatoren
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Unser Ansatz hat aber einen Schönheitsfehler: Wir können ihn nicht direkt starten und
gleichzeitig einen Index übergeben, d.h. wir können nicht sofort ein send benutzen. Wir
müssen zuerst die next-Methode aufrufen, um damit den Generator zu starten und ihn
dadurch zur yield-Anweisung vorwärts zu bewegen.
Wir werden nun einen Dekorateur schreiben, der in der Lage ist, einen Generator bereit
zu machen, indem es diesen automatisch bei der Erzeugung zur yield-Anweisung fortbewegt. Dadurch wird es möglich, die send-Methode unmittelbar nach der Initialisierung zu
benutzen.
from functools import wraps
def get_ready(gen):
"""
gets a generator gen ready
by advancing it to first first yield statement
"""
@wraps(gen)
def generator(*args,**kwargs):
g = gen(*args,**kwargs)
next(g)
return g
return generator
@get_ready
def infinite_looper(objects):
count = -1
message = yield None
while True:
count += 1
if message != None:
count = 0 if message < 0 else message
if count >= len(objects):
count = 0
message = yield objects[count]
x = infinite_looper("abcdef")
print(next(x))
print(x.send(4))
print(next(x))
print(next(x))
print(x.send(5))
print(next(x))
Wir erhalten die folgenden Ergebnisse:
32.11 yield from
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
a
e
f
a
f
a
32.11
yield from
Dieses Sprachkonstrukt ist seit 3.3 verfügbar! yield from <expr> kann im Body eines Generators benutzt werden. Der Ausdruck <expr> wird in einen Iterator evaluiert, aus dem
ein Iterator extrahiert wird. Dieser Iterator läuft bis StopIteration, und er liefert und empfängt Werte vom aufrufenden Generator, also demjenigen, der die yield from-Anweisung
enthält.
Aus dem folgenden Beispiel können wir ersehen, dass der Generator gen2 das Gleiche
macht wie gen1 . Durch die Verwendung von yield from benötigen wir jedoch keine forSchleife mehr:
def gen1():
for char in "Python":
yield char
for i in range(5):
yield i
def gen2():
yield from "Python"
yield from range(5)
g1 = gen1()
g2 = gen2()
print("g1: ", end=", ")
for x in g1:
print(x, end=", ")
print("\ng2: ", end=", ")
for x in g2:
print(x, end=", ")
print()
Die Ausgabe zeigt, dass gen1 und gen2 die gleiche Ausgabe liefern:
g1: , P, y, t, h, o, n, 0, 1, 2, 3, 4,
g2: , P, y, t, h, o, n, 0, 1, 2, 3, 4,
Der Vorteil eines yield from-Ausdrucks besteht darin, dass man einen Generator in mehrere Generatoren aufsplitten kann. Im Prinzip haben wir das bereits im vorigen Beispiel
gemacht, wollen es aber nochmals direkter im folgenden Code demonstrieren:
337
338
32 Generatoren und Iteratoren
def cities():
for city in ["Berlin", "Hamburg", "Munich", "Freiburg"]:
yield city
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def squares():
for number in range(10):
yield number ** 2
def generator_all_in_one():
for city in cities():
yield city
for number in squares():
yield number
def generator_splitted():
yield from cities()
yield from squares()
lst1 = [el for el in generator_all_in_one()]
lst2 = [el for el in cities()] + [el for el in squares()]
print(lst1 == lst2)
Der vorherige Code liefert True zurück, weil die Generatoren generator_all_in_one
und generator_splitted die gleichen Elemente zurückliefern. Dies bedeutet, dass, falls
<expr> von der yield from-Anweisung ein anderer Generator ist, der Effekt der gleiche
ist, als wenn der Body des Subgenerators anstelle der yield from hereinkopiert würde.
Einem solchen Subgenerator ist es erlaubt, eine return-Anweisung zu haben. Der Wert
der return-Anweisung wird zum Rückgabewert der yield from-Anweisung.
Wir veranschaulichen dies mit folgendem Programmcode:
def subgenerator():
yield 1
yield 2
return 42
def delegating_generator():
x = yield from subgenerator()
print(x)
for x in delegating_generator():
print(x)
Wir erhalten das folgende Ergebnis:
1
2
42
32.12 Aufgaben
32.12
Aufgaben
1. Aufgabe:
Schreiben Sie einen Generator, der die Fibonacci-Zahlen generiert.
Zur Erinnerung:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die n -te Fibonacci-Zahl errechnet sich
F (n) = F (n − 1) + F (n − 2),
mit F (0) = 0 und F (1) = 1
2. Aufgabe:
Schreiben Sie einen Generator „round_robin” mit einem Generator g als Argument, der zunächst die Elemente von g auch generiert. Falls g endlich ist, beginnt
„round_robin” wieder von vorne mit g, nachdem das letzte Element von g produziert
worden ist.
Beispiel: Falls g die Elemente 1, 2 und 3 generiert, dann generiert round_robin die
Elemente 1,2,3,1,2,3,1,2,3 . . .
3. Aufgabe:
Schreiben Sie einen Generator „pendulum” mit einem Generator g als Argument, der
als Erstes die Elemente von g generiert. Falls g endlich ist, gibt „pendulum” die Elemente von g in umgekehrter Reihenfolge aus, nachdem das letzte Element von g produziert worden ist. Wird dann wieder das erste Element erreicht, beginnt „pendulum”
wieder von vorne.
Beispiel: Falls g die Elemente 1, 2 und 3 generiert, dann generiert „pendulum” die
Folge 1,2,3,3,2,1,1,2,3,3,2,1 . . .
4. Aufgabe:
Schreiben Sie einen Generator „pair_sum” mit einem Generator g als Argument.
Nehmen wir an, dass g die Werte g 1 , g 2 , . . . g n generiert. „pair_sum” generiert dann
die Werte g 1 + g 2 , g 2 + g 3 , . . . g n−1 + g n , g n + g 1 , g 1 + g 2 , . . .
5. Aufgabe:
Schreiben Sie einen Generator mit Namen random_ones_and_zeroes, der in jeder
Iteration eine 1 oder eine Null zurückliefert. Die Wahrscheinlichkeit p, mit der eine 1
zurück geliefert wird, wird durch die Variable p gesteuert. Beim Start des Generators
wird der Wert von p auf 0.5 gesetzt. Das bedeutet, dass Nullen und Einsen mit gleicher Wahrscheinlichkeit erzeugt werden.
339
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
33
33.1
Dekorateure
Einführung Dekorateure
Dekorateure gehören vermutlich zu den
leistungsstärksten Designmöglichkeiten von
Python. Gleichzeitig wird es von vielen als
schwierig betrachtet, einen Einstieg in die
Thematik zu finden. Um genau zu sein: Die
Nutzung von Dekorateuren ist sehr einfach,
aber das Schreiben eines Dekorateurs kann
sich vor allem für Ungeübte als kompliziert
erweisen. In Python gibt es zwei verschiedene Arten von Dekorateuren:
■
Funktions-Dekorateure
■
Klassen-Dekorateure
Ein Dekorateur in Python ist ein beliebiges
aufrufbares Python-Objekt, welches zur Modifikation einer Funktion oder einer Klasse
genutzt wird. Eine Referenz zu einer Funktion „func” oder einer Klasse „C” wird an den
Bild 33.1 Dekorateure mit Klammeraffen
Dekorateur übergeben, und der Dekorateur
liefert eine modifizierte Funktion oder Klasse zurück. Die modifizierten Funktionen oder
Klassen rufen üblicherweise intern die Originalfunktion „func” oder -Klasse „C” auf.
Aus Erfahrung können wir sagen, dass es für Anfänger beim Thema Dekorateure einige
Schwierigkeiten bei der Benutzung von Funktionen mit funktionalen Argumenten und
Rückgabewerten gibt. Deshalb möchten wir nochmals auf die prinzipiellen Möglichkeiten
von Funktionen bei Parameterübergabe und Rückgabe sowie bei lokalen Funktionsdefinitionen eingehen.
Es ist wichtig zu verstehen, dass Funktionsnamen Referenzen auf Funktionen darstellen,
oder genauer: Referenzen auf Funktionsobjekte. Wie bei allen Objekten ist es also möglich,
verschiedene Namen für das gleiche Objekt zu vergeben:
342
33 Dekorateure
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>>
...
...
>>>
>>>
42
>>>
42
def nachfolger(n):
return n + 1
successor = nachfolger
successor(41)
nachfolger(41)
Wir haben jetzt also zwei Namen, d.h. nachfolger und successor für dasselbe Funktionsobjekt.
Der nächste wichtige Punkt ist, dass wir einen der beiden Namen, also nachfolger oder
successor, löschen können. Dadurch wird das Funktionsobjekt nicht entfernt, da weiterhin noch eine Referenz auf das Objekt existiert.
>>> del nachfolger
>>> successor(41)
42
33.1.1
Verschachtelte Funktionen
Das Konzept von verschachtelten Funktionen, also Funktionsdefinitionen innerhalb einer
Funktion, ist in vielen anderen Programmiersprachen wie beispielsweise C, C++ und Java
nicht bekannt:
def f():
def g():
print("Hallo, ich bin es, 'g'")
print("Dies ist die Funktion 'f'")
print("'f' ruft nun 'g' auf!")
g()
f()
Wenn wir den obigen Code ausführen, erhalten wir die folgende Ausgabe:
Dies ist die Funktion 'f'
'f' ruft nun 'g' auf!
Hallo, ich bin es, 'g'
Noch ein Beispiel mit Funktionen, die return-Anweisungen verwenden:
def temperatur(t):
def celsius2fahrenheit(x):
return 9 * x / 5 + 32
33.1 Einführung Dekorateure
result = str(celsius2fahrenheit(t)) + " Grad Fahrenheit!"
return result
print(temperatur(20))
Wir erhalten 68.0 Grad Fahrenheit! als Ergebnis.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
celsius2fahrenheit ist nur innerhalb von temperatur bekannt, d.h. sie kann nur innerhalb des Funktionskörpers von temperatur aufgerufen werden.
33.1.2
Funktionen als Parameter
Jeder Parameter einer Funktion ist eine Referenz auf ein Objekt. Funktionen sind ebenfalls
Objekte. Somit können wir Funktionen, oder besser „Referenzen auf Funktionen”, ebenfalls
als Argumente an Funktionen übergeben. Wir demonstrieren dies am folgenden Beispiel:
def g():
print("Hallo, ich bin es, 'g'")
def f(func):
print("Dies ist die Funktion 'f'")
print("'f' ruft nun 'func' auf!")
func()
f(g)
Die Ausgabe sieht wie folgt aus:
Dies ist die Funktion 'f'
'f' ruft nun 'func' auf!
Hallo, ich bin es, 'g'
Möglicherweise sind Sie mit der Ausgabe nicht ganz zufrieden. f sollte ausgeben, dass g
aufgerufen wird, und nicht func. Dazu müssen wir den „wirklichen” Namen von func wissen. In diesem Fall können wir das Attribut __name__ des Funktionsobjekts benutzen, welches den Namen der Funktion beinhaltet:
def g():
print("Hallo, ich bin es, 'g'")
def f(func):
print("Dies ist die Funktion 'f'")
print("'f' ruft nun " + func.__name__ + " auf!")
func()
f(g)
343
344
33 Dekorateure
Einmal mehr zeigt die Ausgabe, was hier passiert:
Dies ist die Funktion 'f'
'f' ruft nun g auf!
Hallo, ich bin es, 'g'
Noch ein Beispiel:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
import math
def foo(func):
print(func.__name__ + " wurde an foo übergeben")
res = 0
for x in [1, 2, 2.5]:
res += func(x)
return res
print(foo(math.sin))
print(foo(math.cos))
Wir erhalten folgende Ausgabe:
sin wurde an foo übergeben
2.3492405557375347
cos wurde an foo übergeben
-0.6769881462259364
33.1.3
Funktionen als Rückgabewert
Die Rückgabe einer Funktion ist eine Referenz auf ein Objekt. Somit können auch Referenzen auf Funktionen zurückgegeben werden.
def f(x):
def g(y):
return y + x + 3
return g
nf1 = f(1)
nf2 = f(3)
print(nf1(1))
print(nf2(1))
Das vorige Beispiel liefert folgende Ausgabe:
5
7
33.1 Einführung Dekorateure
33.1.4
Fabrikfunktionen
Unter einer Fabrikfunktion versteht man eine Funktion, die bei einem Aufruf eine neue
Funktion produziert oder generiert.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Das folgende Beispiel einer Fabrikfunktion demonstriert, wie wir maßgeschneidert eine
Begrüßungsfunktion in Abhängigkeit der gewünschten Sprache mit der Fabrikfunktion
create_greeting_function erzeugen können:
def create_greeting_function(lang):
def greeting(name):
if lang == "de":
greeting = "Guten Tag "
elif lang == "fr":
greeting = "Bonjour "
elif lang == "it":
greeting = "Buon Giorno "
elif lang == "es":
greeting = "Buenos dias "
else:
greeting = "Hi "
return greeting + name
return greeting
say_hi = create_greeting_function("fr")
print(say_hi("Pierre"))
Mit dem Aufruf von create_greeting_function("fr") haben wir eine Funktion erzeugt,
die auf Französisch grüßt, wie wir an der Ausgabe Bonjour Pierre erkennen können.
Das folgende Beispiel demonstriert die Arbeitsweise von Fabrikfunktionen in einem mathematischen Umfeld, indem wir eine Fabrikfunktion für e definieren. Wir starten mit einer
Version für Polynome zweiten Grades.
p(x) = a · x 2 + b · x + c
Die Python-Implementierung als eine Polynom-Fabrikfunktion sieht wie folgt aus:
def polynomial_creator(a, b, c):
def polynomial(x):
return a * x**2 + b * x + c
return polynomial
p1 = polynomial_creator(2, 3, -1)
p2 = polynomial_creator(-1, 2, 1)
for x in range(-2, 2, 1):
print(x, p1(x), p2(x))
345
346
33 Dekorateure
Wir können dieses Prinzip auf auf Polynome beliebigen Grades ausdehnen:
n
X
a k · x k = a n · x n + a n−1 · x n−1 + . . . + a 2 · x 2 + a 1 · x + a 0
k=0
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Python-Implementierung sieht wie folgt aus:
def polynomial_creator(*coefficients):
""" coefficients are in the form a_0, a_1, ... a_n
"""
def polynomial(x):
res = 0
for index, coeff in enumerate(coefficients):
res += coeff * x** index
return res
return polynomial
p1 = polynomial_creator(4)
p2 = polynomial_creator(2, 4)
p3 = polynomial_creator(2, 3, -1, 8, 1)
p4 = polynomial_creator(-1, 2, 1)
for x in range(-2, 2, 1):
print(x, p1(x), p2(x), p3(x), p4(x))
p3 implementiert zum Beispiel das folgende Polynom:
p 3 (x) = x 4 + 8 · x 3 − x 2 + 3 · x + 2
Starten wir obiges Programm, erhalten wir folgende Ausgabe:
(-2, 4, -6, -56, -1)
(-1, 4, -2, -9, -2)
(0, 4, 2, 2, -1)
(1, 4, 6, 13, 2)
33.2
Ein einfacher Dekorateur
Jetzt haben wir alle zum Bau unseres ersten einfachen Dekorateurs wichtigen Funktionseigenschaften zusammen:
def our_decorator(func):
def function_wrapper(x):
print("Vor dem Aufruf von " + func.__name__)
func(x)
33.3 @-Syntax für Dekorateure
print("Nach dem Aufruf von " + func.__name__)
return function_wrapper
def foo(x):
print("foo wurde mit " + str(x) + " aufgerufen!")
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
foo("first")
foo = our_decorator(foo)
foo("second")
Wenn wir uns die folgende Ausgabe anschauen, dann sehen wir, was passiert. Nach der Dekoration foo = our_decorator(foo) ist foo eine Referenz auf die aus function_wrapper
generierte Funktion. foo wird innerhalb von dieser durch function_wrapper generierten
Funktion aufgerufen, aber vorher und nachher wird noch etwas Code ausgeführt – in diesem Beispiel zwei Print-Funktionen.
foo wurde mit first aufgerufen!
Vor dem Aufruf von foo
foo wurde mit second aufgerufen!
Nach dem Aufruf von foo
33.3
@-Syntax für Dekorateure
Die Dekoration in Python wird in der Regel nicht so gemacht, wie wir es im vorigen Beispiel
gezeigt haben. Wir haben mit der Schreibweise foo = our_decorator(foo) begonnen,
weil sie einfach zu verstehen und einprägsam ist. Allerdings gibt es bei diesem Ansatz ein
Designproblem. „foo” gibt es in dem Programm in zwei Versionen, und zwar sowohl vor
der Dekoration als auch nach der Dekoration.
Um dies zu vermeiden, gibt es eine alternative Definitionsweise für Dekorateure. Die Dekoration passiert in der Zeile über dem Funktionskopf: das Zeichen @, gefolgt von dem Funktionsnamen des Dekorateurs.
Wir ersetzen diese Anweisung
foo = our_decorator(foo)
durch
@our_decorator
Diese Zeile muss unmittelbar über der zu dekorierenden Funktion platziert werden. Das
komplette Beispiel sieht nun folgendermaßen aus:
def our_decorator(func):
def function_wrapper(x):
print("Vor dem Aufruf von " + func.__name__)
347
348
33 Dekorateure
func(x)
print("Nach dem Aufruf von " + func.__name__)
return function_wrapper
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
@our_decorator
def foo(x):
print("foo wurde mit " + str(x) + " aufgerufen!")
foo("first")
foo("second")
Alle Funktionen mit einem Parameter lassen sich mit dem Dekorateur our_decorator dekorieren. Im Folgenden werden wir dies demonstrieren. Wir haben den Wrapper etwas verändert, sodass wir die Ergebnisse der Funktionsaufrufe sehen können:
def our_decorator(func):
def function_wrapper(x):
print("Vor dem Aufruf von " + func.__name__)
result = func(x)
print(result)
print("Nach dem Aufruf von " + func.__name__)
return function_wrapper
@our_decorator
def succ(n):
return n + 1
succ(10)
Die Ausgabe des Beispiels:
Vor dem Aufruf von succ
11
Nach dem Aufruf von succ
Es ist ebenfalls möglich, weitere Funktionen, die von Dritten geschrieben worden sind, zu
dekorieren. Also Funktionen, die wir beispielsweise aus einem Modul importieren. In solchen Fällen und in unserem nächsten Beispiel können wir jedoch die Dekorationsweise
mit dem „@”-Zeichen nicht verwenden.
from math import sin, cos
def our_decorator(func):
def function_wrapper(x):
print("Vor dem Aufruf von " + func.__name__)
res = func(x)
print(res)
print("Nach dem Aufruf von " + func.__name__)
return function_wrapper
33.3 @-Syntax für Dekorateure
sin = our_decorator(sin)
cos = our_decorator(cos)
sin(3.1415)
cos(3.1415)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Ausgabe:
Vor dem Aufruf von sin
9.265358966049026e-05
Nach dem Aufruf von sin
Vor dem Aufruf von cos
-0.9999999957076562
Nach dem Aufruf von cos
Zusammenfassend können wir Folgendes festhalten: Ein Dekorateur ist ein aufrufbares
Python-Objekt, welches zur Modifikation von Funktions-, Methoden- oder Klassendefinitionen genutzt werden kann. Das Objekt, welches modifiziert werden soll, wird dem Dekorateur als Argument übergeben. Der Dekorateur liefert dann das modifizierte Objekt zurück.
Die vorige Wrapper-Funktion function_wrapper funktioniert nur für Funktionen mit genau einem Parameter. Im nachfolgenden Beispiel haben wir sie so umgeschrieben, dass sie
Funktionen mit beliebigen Parametern verarbeiten kann:
from random import random, randint, choice
def our_decorator(func):
def function_wrapper(*args, **kwargs):
print("Vor dem Aufruf von " + func.__name__)
res = func(*args, **kwargs)
print(res)
print("Nach dem Aufruf von " + func.__name__)
return function_wrapper
random = our_decorator(random)
randint = our_decorator(randint)
choice = our_decorator(choice)
random()
randint(3, 8)
choice([4, 5, 6])
Wir erhalten wie erwartet die folgenden Ergebnisse:
Vor dem Aufruf von random
0.16420183945821654
Nach dem Aufruf von random
Vor dem Aufruf von randint
8
349
350
33 Dekorateure
Nach dem Aufruf von randint
Vor dem Aufruf von choice
5
Nach dem Aufruf von choice
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
33.4
33.4.1
Anwendungsfälle für Dekorateure
Überprüfung von Argumenten durch Dekorateure
Die im folgenden definierte Funktion is_prime berechnet, ob eine positive ganze Zahl eine
Primzahl ist:
def is_prime(n):
return all(n % i for i in xrange(2, n))
Die Funktion all und damit auch die Funktion is_prime liefert True zurück, wenn alle
Zahlen zwischen 2 und (n-1) einen von 0 verschiedenen Rest liefern, wenn man n durch
sie teilt. Wir können nun einen Dekorateur argument_test_natural_number schreiben,
der prüft, ob ein Argument eine positive ganze Zahl ist. Damit können wir dann is_prime
dekorieren, da diese Funktion nur für positive ganze Zahlen definiert ist:
def argument_test_natural_number(f):
def helper(x):
if type(x) == int and x > 0:
return f(x)
else:
raise Exception("Argument is not an integer")
return helper
@argument_test_natural_number
def is_prime(n):
return all(n % i for i in range(2, n))
for i in range(1,10):
print(i, is_prime(i))
print(is_prime(-1))
Das obige Programm liefert folgende Ausgaben:
1
2
3
4
5
6
True
True
True
False
True
False
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
33.4 Anwendungsfälle für Dekorateure
7 True
8 False
9 False
Traceback (most recent call last):
File "prime_test.py", line 16, in <module>
print(is_prime(-1))
File "prime_test.py", line 6, in helper
raise Exception("Argument is not an integer")
Exception: Argument is not an integer
33.4.2
Funktionsaufrufe mit einem Dekorateur zählen
Im folgenden Beispiel zeigen wir, wie wir unter Benutzung eines Dekorateurs elegant die
Aufrufe einer Funktion zählen können. Wir können diesen Dekorateur aber nur für Funktionen mit einem Parameter benutzen:
def call_counter(func):
def helper(x):
helper.calls += 1
return func(x)
helper.calls = 0
return helper
@call_counter
def succ(x):
return x + 1
print(succ.calls)
for i in range(10):
succ(i)
print(succ.calls)
Die Ausgabe sieht wie folgt aus:
0
10
Unsere bisherigen Beispiele für Dekorateure waren nur für Funktionen mit einem Parameter geeignet. Wir zeigen nun, wie wir mittels *args und **kwargs beliebige Dekorateure
schreiben können, d.h. solche, die mit einer beliebigen Anzahl an Positions- und KeywordParametern umgehen können.
def call_counter(func):
def helper(*args, **kwargs):
helper.calls += 1
return func(*args, **kwargs)
351
352
33 Dekorateure
helper.calls = 0
return helper
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
@call_counter
def succ(x):
return x + 1
@call_counter
def mul1(x, y=1):
return x*y + 1
print(succ.calls)
for i in range(10):
succ(i)
mul1(3, 4)
mul1(4)
mul1(y=3, x=2)
print(succ.calls)
print(mul1.calls)
Die Ausgabe sieht dann so aus:
0
10
3
33.5
Dekorateure mit Parametern
Bisher hatten wir nur Dekorateure ohne Parameter geschrieben, auch wenn die Dekoratorfunktion einen Parameter hat, nämlich die Referenz auf die zu dekorierende Funktion. Unter „Dekorateur mit Parameter” verstehen wir Dekorationen der Art @greeting("Moin"),
wie wir in diesem Kapitel sehen werden.
Betrachten wir die beiden Dekorateure:
def evening_greeting(func):
def function_wrapper(x):
print("Good evening, " + func.__name__ + " returns:")
func(x)
return function_wrapper
def morning_greeting(func):
def function_wrapper(x):
print("Good morning, " + func.__name__ + " returns:")
func(x)
33.5 Dekorateure mit Parametern
return function_wrapper
@evening_greeting
def foo(x):
print(42)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
foo("Hi")
Diese beiden Dekorateure sind beinahe identisch außer dem Begrüßungstext. Wollten wir
nun noch weitere Begrüßungsfloskeln als Dekorateure definieren, müssten wir für jede
einen eigenen Dekorateur definieren.
Stattdessen werden wir nun einen Dekorateur mit einem Parameter schreiben, damit der
Gruß bei der Dekoration selbst verändert werden kann. Damit das klappt, müssen wir unsere Dekoratorfunktion in eine Wrapper-Funktion greeting einpacken. Dies versetzt uns
nun in die Lage auf verschiedenste Arten zu grüßen, so auch hanseatisch:
def greeting(expr):
def greeting_decorator(func):
def function_wrapper(x):
print(expr + ", " + func.__name__ + " returns:")
func(x)
return function_wrapper
return greeting_decorator
@greeting("Moin")
def foo(x):
print(42)
foo("Hi")
Die Ausgabe:
Moin, foo returns:
42
Wenn wir die @-Schreibweise nicht verwenden wollen oder können, so lässt sich dies auch
mittels Funktionsaufrufen realisieren:
def foo(x):
print(42)
special_greeting = greeting("Moin")
foo = special_greeting(foo)
foo("Hi")
Das Ergebnis ist identisch:
Moin, foo returns:
42
353
354
33 Dekorateure
Natürlich ist die zusätzliche Definition von special_greeting nicht nötig. Wir können die
Rückgabe aus greeting("Moin") direkt an foo übergeben:
foo = greeting("Moin")(foo)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
33.6
Benutzung von Wraps aus functools
Die Art und Weise, wie wir bisher Dekorateure definiert haben, hat nicht berücksichtigt,
dass die Attribute
■
__name__ (Name der Funktion),
■
__doc__ (der Docstring) und
■
__module__ (das Modul, in dem die Funktion definiert ist)
der originalen Funktionen bei der Dekoration verloren gehen.
Wir zeigen dies in dem folgenden Beispiel:
def greeting(func):
def function_wrapper(x):
""" Der Funktions-Wrapper von greeting """
print("Hi, " + func.__name__ + " liefert zurück:")
return func(x)
return function_wrapper
@greeting
def f(x):
""" nur eine Funktion """
return x + 4
f(10)
print("function name: " + f.__name__)
print("docstring: " + f.__doc__)
print("module name: " + f.__module__)
Wir erhalten ungewollte und gegebenenfalls unerwartete Ergebnisse:
Hi, f liefert zurück:
function name: function_wrapper
docstring: Der Funktions-Wrapper von greeting
module name: __main__
Wir können die originalen Attribute der Funktion f speichern, indem wir sie innerhalb des
Dekorateurs zuweisen. Wir passen den vorigen Dekorateur entsprechend an und lassen
den Rest des Programms unverändert:
def greeting(func):
def function_wrapper(x):
33.7 Klassen statt Funktionen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
""" Der Funktions-Wrapper von greeting """
print("Hi, " + func.__name__ + " liefert zurück:")
return func(x)
function_wrapper.__name__ = func.__name__
function_wrapper.__doc__ = func.__doc__
function_wrapper.__module__ = func.__module__
return function_wrapper
Jetzt erhalten wir die richtigen Ergebnisse:
Hi, f liefert zurück:
function name: f
docstring: nur eine Funktion
module name: __main__
Wir müssen zum Glück nicht diesen ganzen Code bei jedem Dekorateur hinzufügen, um
diese Ergebnisse zu erhalten. Dies kann der Dekorateur wraps aus dem Modul functools
für uns erledigen:
from functools import wraps
def greeting(func):
@wraps(func)
def function_wrapper(x):
""" Der Funktions-Wrapper von greeting """
print("Hi, " + func.__name__ + " liefert zurück:")
return func(x)
return function_wrapper
33.7
33.7.1
Klassen statt Funktionen
Die __call__-Methode
Bis hierher haben wir Funktionen als Dekorateure verwendet. Bevor wir einen Dekorateur
als Klasse definieren können, führen wir die __call__-Methode der Klassen ein. Wir hatten
bereits erwähnt, dass ein Dekorateur einfach ein aufrufbares Objekt ist, welches eine Funktion als Parameter entgegennimmt. Vielen Programmierern ist aber nicht bekannt, dass wir
Instanzen von Klassen ebenfalls aufrufbar machen können. Die __call__-Methode wird
aufgerufen, wenn eine Instanz „wie eine Funktion” aufgerufen wird. Sprich, man benutzt
Klammern. Das hedeutet, dass eine Instanz einer Klasse mit __call__-Methode aufrufbar
oder englisch „callable” ist.
class A:
def __init__(self):
print("Eine Instanz von A wurde initialisiert")
355
356
33 Dekorateure
def __call__(self, *args, **kwargs):
print("Die Argumente lauten:", args, kwargs)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
x = A()
print("Aufruf der gerade erzeugten Instanz:")
x(3, 4, x=11, y=10)
print("Rufen wir sie nochmals auf:")
x(3, 4, x=11, y=10)
Wir erhalten folgende Ausgabe:
Eine Instanz von A wurde initialisiert
Aufruf der gerade erzeugten Instanz:
Die Argumente lauten: (3, 4) {'x': 11, 'y': 10}
Rufen wir sie nochmals auf:
Die Argumente lauten: (3, 4) {'x': 11, 'y': 10}
Wir können eine Klasse für die Fibonacci-Funktion schreiben, indem wir __call__ benutzen:
class Fibonacci:
def __init__(self):
self.cache = {}
def __call__(self, n):
if n not in self.cache:
if n < 2:
self.cache[n] = n
else:
self.cache[n] = self.__call__(n-1) + self.__call__(n
-2)
return self.cache[n]
fib = Fibonacci()
for i in range(15):
print(fib(i), end=", ")
Die Ausgabe:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
33.8 Eine Klasse als Dekorateur benutzen
33.8
Eine Klasse als Dekorateur benutzen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir werden folgenden Dekorateur als Klasse umschreiben:
def decorator1(f):
def helper():
print("Decorating", f.__name__)
f()
return helper
@decorator1
def foo():
print("inside foo()")
foo()
Der folgende Dekorateur, der als Klasse implementiert ist, erledigt die gleiche Aufgabe:
class decorator2:
def __init__(self, f):
self.f = f
def __call__(self):
print("Decorating", self.f.__name__)
self.f()
@decorator2
def foo():
print("inside foo()")
foo()
Beide Versionen liefern das gleiche Ergebnis:
Decorating foo
inside foo()
357
358
33 Dekorateure
33.9
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
33.9.1
Memoisation
Bedeutung und Herkunft des Begriffs
Der Ausdruck „Memoisation”1 wurde im Jahre 1968 von Donald Michie geprägt. Der Ausdruck basiert auf dem lateinischen Wort „memorandum”, was „das zu Erinnernde” bedeutet. Memoisation ist eine Technik, die in der Programmierung benutzt wird, um den Programmablauf zu beschleunigen. Dies wird erreicht, indem Berechnungsergebnisse gespeichert werden wie zum Beispiel die Ergebnisse von Funktionsaufrufen. Wird eine Funktion
mit den gleichen Parametern aufgerufen wie
bei einem vorigen Aufruf, wird auf die gespeicherten Ergebnisse zugegriffen, statt die Werte
neu zu berechnen. In vielen Fällen wird ein einfaches Array für die Ergebnisspeicherung benutzt, aber es sind auch kompliziertere Strukturen möglich. So können beispielsweise assoziative Arrays verwendet werden, die in Perl
Hashes und in Python Dictionaries genannt
werden.
Die Memoisation kann explizit vom Program- Bild 33.2 Gehirn
mierer programmiert werden, aber einige Programmiersprachen wie auch Python stellen
Mechanismen zur Verfügung, welche die Implementierung der Memoisation erleichtern.
33.9.2
Memoisation mit Dekorateur-Funktionen
In unserem Kapitel über rekursive Funktionen hatten wir eine iterative und eine rekursive
Funktionsversion zur Berechnung der Fibonacci-Zahlen programmiert. Wir hatten gezeigt,
dass die direkte Umsetzung der mathematischen Definition der Fibonacci-Zahlen ein exponentielles Laufzeitverhalten aufzeigt, also eine Funktion wie die folgende:
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
In unserem Kapitel über Rekursion hatten wir auch einen Weg dargestellt, wie man das
Laufzeitverhalten der rekursiven Version verbessern kann. Wir hatten ein Dictionary dazu
verwendet, um bereits durch die Funktion berechnete Werte für spätere Aufrufe zu speichern. Dadurch wird eine Neuberechnung verhindert. Dabei handelte es sich bereits, ohne
1
Wir haben den englischen Fachbegriff eingedeutscht, da es keinen deutschen Begriff gibt. Im Englischen
kommt der Begriff in den Schreibweisen Memoisation und Memoization vor.
33.9 Memoisation
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
dass wir es so genannt hatten, um ein Beispiel, wie man explizit die Technik der Memoisation nutzen kann. Die Methode hatte jedoch einen entscheidenden Nachteil: Die Klarheit
und die Schönheit der ursprünglichen rekursiven Methode ging dabei verloren.
Das „Problem” besteht darin, dass wir den Code der rekursiven fib-Funktion ändern bzw.
erweitern mussten. Im folgenden Code präsentieren wir eine Lösung mit einem Dekorateur (die Funktion memoize), die ohne Änderungen der fib-Funktion auskommt. Dadurch
wird ihre Klarheit, Schönheit und Lesbarkeit nicht tangiert. Zu diesem Zweck definieren wir
eine Funktion, die wir memoize nennen. memoize benötigt zwei Argumente. Auch die Funktion memoize benutzt ein Dictionary, um die Funktionswerte zu speichern. Obwohl sowohl
die Variable „memo” (unser Dictionary zum Speichern der Werte) als auch die Funktion „f”
lokal innerhalb der Funktion memoize sind, werden ihre Werte durch die helper-Funktion
bewahrt. Im Englischen benutzt man statt „bewahrt” das Wort „captured”, was im Deutschen „eingefangen” oder „gefangen genommen” bedeutet. Diese Technik, d.h. das Bewahren von „lokalen” Werten oder Funktionen über ihren lokalen Geltungsbereich hinaus,
wird auch als Closure oder Funktionsabschluss bezeichnet. memoize liefert als Funktionswert eine Referenz auf die helper-Funktion zurück. Ein Aufruf der Funktion memoize(fib)
liefert eine Funktion zurück, die im Ein- und Ausgabeverhalten identisch mit fib ist, d.h.
sie tut, was fib tut. Zusätzlich speichert sie jedoch die Ergebnisse im memo-Dictionary.
def memoize(f):
memo = {}
def helper(x):
if x not in memo:
memo[x] = f(x)
return memo[x]
return helper
@memoize
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
print(fib(40))
33.9.3
Memoisation mit einer Klasse
Wir können das Zwischenspeichern der Ergebnisse auch in einer Klasse statt einer Funktion einkapseln. Wir demonstrieren dies im folgenden kleinen Beispiel:
359
360
33 Dekorateure
class Memoize:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __init__(self, fn):
self.fn = fn
self.memo = {}
def __call__(self, *args):
if args not in self.memo:
self.memo[args] = self.fn(*args)
return self.memo[args]
@Memoize
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
Da wir ein Dictionary verwenden, können wir keine veränderlichen2 Argumente verwenden. Anders ausgedrückt: Die Argumente dürfen nur unveränderlich3 , also z.B. Tupel, sein.
33.9.4
Memoisation mit functools.lru_cache
Das Modul functools enthält einen komfortablen Dekorateur zur Memoisation, und zwar
den lru_cache-Dekorateur. Die Buchstaben „lru” stehen für „least recently used” (am
längsten nicht verwendet), das bedeutet, dass sich der Dekorator nur die zuletzt berechneten Werte in einem limitierten Cache merkt.
import functools
@functools.lru_cache()
def fib(n):
if n < 2:
return n
else:
return fib(n-1) + fib(n-2)
Die Arbeitsweise dieses Dekorateurs können wir am besten aus folgendem Beispiel ersehen:
>>> import functools
>>> @functools.lru_cache()
2
3
englisch: mutable
englisch: immutable
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
33.9 Memoisation
... def f(x):
...
return x
...
>>> f.cache_info()
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
>>> f(1)
1
>>> f(3)
3
>>> f(1)
1
>>> f.cache_info()
CacheInfo(hits=1, misses=2, maxsize=128, currsize=2)
f.cache_info() liefert die Cache-Statistik (hits, misses, maxsize, currsize) zurück. f.cache_clear() löscht den Cache und die Statistik.
Bei dem lru_cache-Dekorateur handelt es sich um einen Dekorateur, der mit Parametern
aufgerufen wird. Er kann mit den beiden Schlüsselwortparametern maxsize und typed
aufgerufen werden. In unserem Beispielaufruf wurden für die Parameter die Default-Werte
maxsize = 128 und typed = False verwendet.
Falls maxsize auf None gesetzt wird, werden die LRU-Eigenschaften disabled, und der Cache kann ungebunden wachsen. Wir demonstrieren dies im folgenden Beispiel:
import functools
@functools.lru_cache(maxsize=None)
def f(x):
return x
for i in range(1000):
f(i)
print(f.cache_info())
An der Ausgabe können wir erkennen, dass der Cache auf 1000 Elemente angestiegen ist:
CacheInfo(hits=0, misses=1000, maxsize=None, currsize=1000)
Ruft man den Dekorateur entweder ohne Argumente oder mit der Zuweisung maxsize=128
auf, so können wir an der Ausgabe sehen, dass der Cache auf 128 begrenzt ist:
CacheInfo(hits=0, misses=1000, maxsize=128, currsize=128)
Falls typed auf True gesetzt ist, werden Argumente von verschiedenem Type separat gecashed. So werden dann z.B. f(3.0) und f(3) als verschiedene Aufrufe betrachtet. Die Argumente der dekorierten Funktion müssen hash-bar sein. Die zugrunde liegende Funktion
kann man mit f.__wrapped__ ansprechen.
361
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
362
33 Dekorateure
>>> @functools.lru_cache(typed=True)
... def g(x):
...
return x
...
>>> g(1)
1
>>> g(1.0)
1.0
>>> g.cache_info()
CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Teil IV
Weiterführende Themen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
34
34.1
Tests und Fehler
Einführung
Es ist kein Zufall, dass wir unseren weiterführenden
Teil mit einem Kapitel über Fehler, Fehlersuche und
Fehlervermeidung beginnen. Tests und Fehlersuche
gehören zum Alltag eines jeden Programmierers.
Die Zeit, die zur Fehlersuche und zum Testen eines Programms verwendet wird, hat üblicherweise
einen hohen Anteil an der Programmentwicklung,
Studien sprechen von bis zu 50 %.1 Dass Fehler
menschlich seien, wurde schon vor mehr als 2000
Jahren von Cicero festgestellt. Sein „errare humanum est”2 gilt häufig auch als Ausrede, um unzureichende Arbeitsergebnisse zu entschuldigen. Auch
Bild 34.1 Fehler sind menschlich.
wenn wir Fehler nie vollständig vermeiden können,
sollten wir danach trachten, die Anzahl der Fehler, die wir machen (und vor allen diejenigen, die im Produkt bleiben könnten), minimal zu halten.
Es gibt unterschiedliche Fehlerarten. Während der Programmentwicklung treten immer
wieder „kleine Fehler” auf, bei denen es sich meistens um Flüchtigkeitsfehler bzw. Tippfehler handelt. Irgendwo fehlt immer mal ein Doppelpunkt, so zum Beispiel hinter einem
„else”, oder man hat vielleicht „true” statt True geschrieben. All dies sind sogenannte syntaktische Fehler oder Syntaxfehler.3 Dabei handelt es sich um die Verletzung von Schreibweisen einzelner Wörter, also beispielsweise Schlüsselwörtern4 oder von Strukturregeln. In
1
2
3
4
Boehm und Barry sprechen von 40 - 50 %, die zum Beheben von Design- und Code-Fehlern benötigt werden: Boehm, Barry W. 1987. „Improving Software Productivity.” IEEE Computer, September: 43-57.; Jones,
Capers, ed. 1986.
Der Spruch stammt von Cicero und lautet in voller Länge: „Errare (Errasse) humanum est, sed in errare
(errore) perseverare diabolicum.” Auf Deutsch lautet dies: „Irren ist menschlich, aber auf Irrtümern zu beharren ist teuflisch.”
Unter der Syntax einer Programmiersprache versteht man die Beschreibung der erlaubten Zeichenketten
für Programme dieser Sprache. Fast alle Programmiersprachen werden mittels kontextfreier Grammatiken
syntaktisch beschrieben. Man spricht auch in natürlichen Sprachen von syntaktischen Fehlern. „I is learning Python” ist beispielsweise ein Syntaxfehler in der englischen Sprache.
englisch: key words
366
34 Tests und Fehler
der Linguistik würde man das als Satzbauregeln bezeichnen, also zum Beispiel der vergessene Doppelpunkt hinter dem „else” oder eckiger statt runder Klammer bei einem Funktionsaufruf.
Neben den syntaktischen Fehlern, die sich häufig relativ leicht finden und beheben lassen,
gibt es auch die semantischen Fehler.5
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Betrachten wir folgenden Programmausschnitt:
x = int(input("x? "))
y = int(input("y? "))
if x > 10:
if y == x:
print("Fine")
else:
print("So what?")
Es gibt zwei if-Anweisungen, aber nur eine else-Anweisung. Das Codefragment ist zwar
syntaktisch korrekt, aber es könnte durchaus sein, dass der Schreiber des Programms den
Text „So what?” nur ausgeben wollte, wenn x größer als 10 ist und der Wert von y dem von
x entspricht. Das bedeutet, dass er folgenden Code hätte schreiben müssen:
x = int(input("x? "))
y = int(input("y? "))
if x > 10:
if y == x:
print("Fine")
else:
print("So what?")
Beide Programme bzw. Programmfragmente sind syntaktisch korrekt. Aber einer von beiden verstößt gegen die intendierte Semantik, dass der Text „So what?” nur ausgegeben
werden sollte, wenn x größer als 10 ist und der Wert von y dem von x entspricht. Der Programmierer hatte wahrscheinlich die Semantik der if-Anweisung korrekt verstanden, aber
sein Problem nicht richtig umgesetzt. Es ist aber auch denkbar, dass ein Programmierer
die Semantik eines Python-Konstrukts nicht richtig verstanden und deshalb einen Fehler
produziert hat.
Betrachten wir hierzu das folgende Beispiel:
>>> for i in range(7):
...
print(i)
...
0
1
5
Die Bedeutung der einzelnen Sprachkonstrukte einer Sprache wird als die Semantik einer Programmiersprache bezeichnet. Normalerweise beißen Männer keine Hunde, sodass man im folgenden Satz von einem
Semantikfehler ausgehen kann: „The man bites the dog!”
34.2 Modultests
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
2
3
4
5
6
>>>
Aus der Tatsache, dass es keinen Fehler bei der Ausführung gab, ersehen wir, dass diese
Anweisungen syntaktisch korrekt sind. Ob sie jedoch semantisch korrekt sind, können wir
nicht entscheiden, da wir ja die Problemstellung nicht kennen. Nehmen wir an, dass der
Programmierer die Zahlen von 1 bis 7 hatte ausgeben wollen. In diesem Fall hat er einen semantischen Fehler begangen, der wohl daraus resultiert, dass er die range-Funktion nicht
richtig verstanden hat. Er ging irrtümlich davon aus, dass range(7) die Zahlen von 1 bis 7
produziere, d.h. 1, 2, 3, 4, 5, 6 und 7.
Wir können also die Semantikfehler in zwei Gruppen unterteilen:
■
Fehler, die aus dem mangelnden Verständnis eines Sprachkonstrukts herrühren.
■
Fehler, die aus der fehlerhaften Umsetzung des Problems resultieren.
34.2
Modultests
Für den Begriff Modultest oder Komponententest wird häufig im Deutschen der
englische Begriff „Unittest” („unit test”)
verwendet. Modultests verwendet man
in der Software-Entwicklung, um Module (also funktionale Einzelteile eines Programms) zu testen, d.h. man prüft, ob
sie die geforderte Funktionalität erfüllen.
Es empfiehlt sich, Tests auf Modulebene
durchzuführen, da die dortigen Funktionalitäten noch eine begrenzte bzw. überschaubare Komplexität zeigen und die
Bild 34.2 Testen und Messen
Schnittstellen noch klar definiert sind.
Häufig kann man so ein Modul vollständig
auf Korrektheit prüfen. Dies ist auf einem umfassenden Software-Paket in der Regel nicht
mehr möglich. Niemand kann beispielsweise ein Betriebssystem vollständig auf Korrektheit prüfen.
367
368
34 Tests und Fehler
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
34.3
Modultests unter Benutzung von
__name__
Jedes Python-Modul hat einen im built-in-Attribut __name__ definierten Namen. Nehmen
wir an, wir haben ein Modul mit dem Namen „abc” unter dem Dateinamen „abc.py” gespeichert. Wird dieses Modul mit „import abc” importiert, dann hat das built-in-Attribut
__name__ den Wert „abc”. Wird die Datei „abc.py” jedoch als eigenständiges Programm
aufgerufen, also mittels
$python3 abc.py
dann hat diese Variable den Wert ’__main__’.
Anhand des folgenden Moduls (das sowohl eine einzelne Fibonacci-Zahl für eine bestimmte Generation n als auch die Liste der Fibonacci-Zahlen bis zu einer Generation n berechnen kann) wollen wir veranschaulichen, wie man mit dem built-in-Attribut __name__
einen einfachen, aber sehr wirkungsvollen Modultest durchführen kann.
Das fibonacci-Modul sieht wie folgt aus und ist unter dem Namen fibonacci.py in unserem
Beispielverzeichnis zu finden:
""" Modul mit wichtigen Funktionen zur Fibonacci-Folge """
def fib(n):
""" Die Fibonacci-Zahl für die n-te
Generation wird iterativ berechnet """
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
def fiblist(n):
""" produziert die Liste der Fibbo-Zahlen
bis zur n-ten Generation """
fib = [0,1]
for i in range(1,n):
fib += [fib[-1]+fib[-2]]
return fib
Natürlich kann man dieses Modul auch „manuell” in der interaktiven Python-Shell testen:
>>>
>>>
0
>>>
1
>>>
55
>>>
[0,
>>>
from fibonacci import fib, fiblist
fib(0)
fib(1)
fib(10)
fiblist(10)
1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
fiblist(-8)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
34.3 Modultests unter Benutzung von __name__
[0, 1]
>>> fib(-1)
0
>>> fib(0.5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "fibonacci.py", line 6, in fib
for i in range(n):
TypeError: 'float' object cannot be interpreted as an integer
>>>
Wir sehen, dass die Funktionen auf Integer-Zahlen definiert sind. Für negative ganze Zahlen liefert die Funktion fib nur Nullen zurück, während fiblist nur die Liste [0,1] zurückliefert. Ruft man die Funktionen mit Float-Zahlen auf, gibt es einen TypeError, weil range
nicht für Float-Werte definiert ist.
Unser Modul könnten wir prüfen, indem wir testen, ob die Aufrufe für fib() und fiblist() für
bestimmte Werte definierte Ergebniswerte zurückliefern.
Man könnte also unser Modul direkt um eine oder mehrere if-Anweisungen erweitern:
if fib(0) == 0 and fib(10) == 55 and fib(50) == 12586269025:
print("Test für fib-Funktion erfolgreich")
else:
print("fib-Funktion liefert fehlerhafte Werte")
Ruft man das Programm dann direkt auf, erhält man folgende Ausgabe:
$ python3 fibonacci.py
Test für fib-Funktion erfolgreich
Nun wollen wir bewusst einen Fehler in unsere fib-Funktion einbauen. Dazu ändern wir
die Zeile
a, b = 0, 1
in
a, b = 1, 1
um.6
Ein Aufruf des veränderten Moduls liefert nun eine Fehlermeldung:
$ python3 fibonacci.py
fib-Funktion liefert fehlerhafte Werte
Dieses Vorgehen hat jedoch einen entscheidenden Nachteil. Wenn man das Modul importiert, wird auch das Ergebnis dieses oder ähnlicher Tests angezeigt:
>>> import fibonacci
Test für fib-Funktion erfolgreich
6
Im Prinzip liefert fib zwar noch die Fibonacci-Werte, aber um eins versetzt. Wollen wir den n-ten Wert (für
n größer als 0 berechnen), so müssen wir fib(n-1) aufrufen.
369
370
34 Tests und Fehler
Es ist aber störend und auch nicht üblich, wenn Module solche Meldungen beim import
ausgeben. Module sollen sich „schweigend” laden lassen.
Die Lösung für dieses Problem stellt das eingangs erwähnte built-in-Attribut __name__
dar. Wird unser Modul direkt gestartet, also nicht importiert, hat __name__ den Wert
„__main__”.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir können unser Modul nun so umschreiben, dass die Tests nur gestartet werden, wenn
das Modul direkt gestartet wird:
""" Modul mit wichtigen Funktionen zur Fibonacci-Folge """
def fib(n):
""" Iterative Fibonacci-Funktion """
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
def fiblist(n):
""" produziert Liste der Fibbo-Zahlen """
fib = [0,1]
for i in range(1,n):
fib += [fib[-1]+fib[-2]]
return fib
if __name__ == "__main__":
if fib(0) == 0 and fib(10) == 55 and fib(50) == 12586269025:
print("Test für fib-Funktion erfolgreich")
else:
print("fib-Funktion liefert fehlerhafte Werte")
Nun gibt es keine Ausgaben, wenn das Modul importiert wird, und zwar weder im Fehlerfall
noch im Erfolgsfall.
Diese Methode ist die einfachste und am weitesten verbreitetste Methode für Modultests.
34.4
doctest-Modul
Das doctest-Modul stellt eine weitere einfache Methode dar, Modultests durchzuführen.
Der eigentliche Test befindet sich bei dieser Methode, wie der Name vermuten lässt, im
Docstring.
Vorgehensweise: Man muss das Modul „doctest” importieren. Dann kopiert man einen
Auszug aus einer interaktiven Sitzung in den Docstring einer Funktion.
34.4 doctest-Modul
Im Folgenden zeigen wir das Vorgehen an einem simplen Beispiel. Dazu haben wir das
vorige fibonacci-Modul bis auf die Funktion fib abgespeckt:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
import doctest
def fib(n):
"""
Die Fibonacci-Zahl für die n-te
Generation wird iterativ berechnet
"""
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
Dieses Modul rufen wir nun in einer interaktiven Python-Shell auf und lassen ein paar Werte berechnen:
>>>
>>>
0
>>>
1
>>>
55
>>>
610
>>>
from fibonacci import fib
fib(0)
fib(1)
fib(10)
fib(15)
Diese Aufrufe mit den Ergebnissen kopieren wir aus der interaktiven Shell in den Docstring
unserer Funktion. Damit das Modul doctest aktiv wird, müssen wir die Methode testmod()
starten, falls das Modul direkt aufgerufen wird. Dies können wir wie üblich mit einem Test
des Attributs __name__ auf den Wert „__main__” machen. Das vollständige Modul sieht
nun wie folgt aus:
import doctest
def fib(n):
"""
Die Fibonacci-Zahl für die n-te
Generation wird iterativ berechnet
>>>
0
>>>
1
>>>
55
>>>
fib(0)
fib(1)
fib(10)
fib(15)
371
372
34 Tests und Fehler
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
610
>>>
"""
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
if __name__ == "__main__":
doctest.testmod()
Starten wir obiges Modul direkt mit dem Aufruf
$ python3 fibonacci_doctest.py
erhalten wir keine Ausgabe, weil alles okay ist.
Um das Verhalten im Fehlerfall zu sehen, bauen wir wieder einen kleinen Fehler ein. Dazu
ändern wir erneut die Zeile
a, b = 0, 1
in
a, b = 1, 1
um.
Nun erhalten wir folgende Ausgabe beim direkten Start des Moduls:
$ python3 fibonacci_doctest.py
**********************************************************************
File "fibonacci_doctest.py", line 8, in __main__.fib
Failed example:
fib(0)
Expected:
0
Got:
1
**********************************************************************
File "fibonacci_doctest.py", line 12, in __main__.fib
Failed example:
fib(10)
Expected:
55
Got:
89
**********************************************************************
File "fibonacci_doctest.py", line 14, in __main__.fib
Failed example:
fib(15)
Expected:
610
34.5 Testgetriebene Entwicklung oder „Im Anfang war der Test”
Got:
987
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
**********************************************************************
1 items had failures:
3 of
4 in __main__.fib
Test
Failed
***
*** 3 failures.
Es werden alle Aufrufe angezeigt, die ein fehlerhaftes Ergebnis geliefert haben. Wir sehen
jeweils den Beispielaufruf hinter der Zeile „Failed example:”. Hinter „Expected:” folgt der
erwartete Wert, also der korrekte Wert, und hinter „Got:” folgt der von der Funktion produzierte Ausdruck, also der Wert, den doctest beim Aufruf von fib erhalten hat.
34.5
Testgetriebene Entwicklung
Im vorigen Kapitel hatten wir bereits eine fertig geschriebene Fibonacci-Funktion, die man
zu testen hatte. Man kann aber auch so vorgehen, dass man bereits am Anfang – also bevor
man die zu testende Funktion geschrieben hat – Ergebnisse in den Docstring schreibt und
dann erst mit der Entwicklung der Funktion beginnt. Im Folgenden haben wir den Rückgabewert der Funktion fib fest auf 0 gesetzt:
import doctest
def fib(n):
"""
Die Fibonacci-Zahl für die n-te
Generation wird iterativ berechnet
>>>
0
>>>
1
>>>
55
>>>
610
>>>
fib(0)
fib(1)
fib(10)
fib(15)
"""
return 0
if __name__ == "__main__":
doctest.testmod()
373
374
34 Tests und Fehler
Es versteht sich von selbst, dass ein Test dieses Moduls außer für fib(0) nur Fehler liefert:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
$ python3 fibonacci_TDD.py
**********************************************************************
File "fibonacci_TDD.py", line 10, in __main__.fib
Failed example:
fib(1)
Expected:
1
Got:
0
**********************************************************************
File "fibonacci_TDD.py", line 12, in __main__.fib
Failed example:
fib(10)
Expected:
55
Got:
0
**********************************************************************
File "fibonacci_TDD.py", line 14, in __main__.fib
Failed example:
fib(15)
Expected:
610
Got:
0
**********************************************************************
1 items had failures:
3 of
4 in __main__.fib
***Test Failed*** 3 failures.
Man ändert bzw. schreibt nun den eigentlichen Code der Funktion fib solange, bis die Tests
im Doctest „bestanden” werden.
Dieses Vorgehen ist eine Methode der Software-Entwicklung, die man als „Testgetriebene
Entwicklung” oder auch „Testgesteuerte Entwicklung” bezeichnet. Aber wie so häufig in
der SW-Branche werden auch hier die englischen Begriffe benutzt, d.h. „test first development” oder noch geläufiger „test-driven development” (TDD).
34.6 unittest
34.6
unittest
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Für das Modul „unittest” standen JUnit7 von Erich Gamma und SUnit8 von Kent Beck Pate.
Ein deutlicher Unterschied zum Modul doctest besteht darin, dass die Testfälle bei dem
Modul „unittest” außerhalb des eigentlichen Programmcodes definiert werden, d.h. in einer eigenen Datei. Der Vorteil besteht unter anderem darin, dass die Programmdokumentation und die Testbeschreibungen voneinander getrennt sind. Der Preis dafür besteht besteht jedoch in einem erhöhten Aufwand für die Erstellung der Tests.
Wir wollen nun für unser Modul fibonacci.py einen Test mit unittest erstellen. In einer Datei fibonacci_unittest.py9 müssen wir das Modul unittest und das zu testende Modul, also
in unserem Fall fibonacci, importieren.
Außerdem müssen wir eine Klasse mit beliebigem Namen – wir wählen in unserem Beispiel „FibonacciTest” – erstellen, die von unittest.TestCase erbt. In dieser Klasse werden die
nötigen Testfälle in Methoden definiert. Der Name dieser Methoden ist beliebig, er muss
jedoch mit test beginnen. In unserer Methode „testCalculation” benutzen wir die Methode
assertEqual der Klasse TestCase. assertEqual(first, second, msg = None) prüft, ob der Ausdruck „first” gleich dem Ausdruck „second” ist. Falls die beiden Ausdrücke ungleich sind,
wird msg ausgegeben, wenn msg ungleich None ist.
import unittest
from fibonacci import fib
class FibonacciTest(unittest.TestCase):
def testCalculation(self):
self.assertEqual(fib(0), 0)
self.assertEqual(fib(1), 1)
self.assertEqual(fib(5), 5)
self.assertEqual(fib(10), 55)
self.assertEqual(fib(20), 6765)
if __name__ == "__main__":
unittest.main()
Rufen wir obigen Testfall auf, erhalten wir folgende Ausgabe:
$ python3 fibonacci_unittest.py
.
---------------------------------------------------------------------Ran 1 test in 0.000s
OK
7
8
9
Dabei handelt es sich um ein Framework zum Testen von Java-Programmen, das für automatisierte UnitTests von einzelnen Klassen und Methoden besonders geeignet ist.
SUnit ist ein Smalltalk-Testframework, das von Kent Beck erfunden wurde. SUnit wurde auf Java unter dem
Namen JUnit portiert.
Der Name kann beliebig gewählt sein. Wichtig ist lediglich, dass der Testfall in einer separaten Datei steht.
375
376
34 Tests und Fehler
Bei der normalen Programmentwicklung ist dies das von uns gewünschte Ergebnis. Hier
sind wir allerdings interessiert, was im Fehlerfall passiert. Wir produzieren deshalb wieder
unseren Fehler. Dazu ändern wir von Neuem die Zeile
a, b = 0, 1
in
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
a, b = 1, 1
um.
Jetzt sieht der Test wie folgt aus:
$ python3 fibonacci_unittest.py
F
======================================================================
FAIL: testCalculation (__main__.FibonacciTest)
---------------------------------------------------------------------Traceback (most recent call last):
File "fibonacci_unittest.py", line 7, in testCalculation
self.assertEqual(fib(0), 0)
AssertionError: 1 != 0
---------------------------------------------------------------------Ran 1 test in 0.000s
FAILED (failures=1)
Bereits die erste Anweisung in testCalculation hat eine Ausnahme erzeugt. In diesem Fall
wurden die weiteren assertEqual-Aufrufe nicht mehr ausgeführt. Wir verändern fib nun
dahingehend, dass wir nur einen falschen Wert erhalten, wenn n auf 20 gesetzt ist:
def fib(n):
""" Iterative Fibonacci-Funktion """
a, b = 0, 1
for i in range(n):
a, b = b, a + b
if n == 20:
a = 42
return a
Die Ausgabe eines Testlaufs sieht nun wie folgt aus:
$ python3 fibonacci_unittest.py
F
======================================================================
FAIL: testCalculation (__main__.FibonacciTest)
---------------------------------------------------------------------Traceback (most recent call last):
File "fibonacci_unittest.py", line 12, in testCalculation
self.assertEqual(fib(20), 6765)
34.7 Methoden der Klasse TestCase
AssertionError: 42 != 6765
---------------------------------------------------------------------Ran 1 test in 0.000s
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
FAILED (failures=1)
Jetzt wurden aber auch die folgenden Anweisungen durchgeführt, allerdings generierten
sie keine Ausnahme, da ihre Ergebnisse ja korrekt sind:
self.assertEqual(fib(0), 0)
self.assertEqual(fib(1), 1)
self.assertEqual(fib(5), 5)
34.7
Methoden der Klasse TestCase
Wir wollen nun näher auf die Klasse TestCase eingehen. Wir stellen dazu einige wichtige Methoden dieser Klasse vor. Zunächst stellen wir die beiden Hook-Methoden10 setUp()
und tearDown() vor.
Wir erweitern unser voriges Beispiel um eine setUp- und eine tearDown-Methode:
import unittest
from fibonacci import fib
class FibonacciTest(unittest.TestCase):
def setUp(self):
self.fib_elems = ( (0,0), (1,1), (2,1), (3,2), (4,3), (5,5) )
print ("setUp executed!")
def testCalculation(self):
for (i,val) in self.fib_elems:
self.assertEqual(fib(i), val)
def tearDown(self):
# Objekte können
# in diesem Fall
self.fib_elems =
print ("tearDown
gelöscht oder geändert werden
macht es jedoch wenig Sinn:
None
executed!")
if __name__ == "__main__":
unittest.main()
10
Ein Hook (auch Einschubmethode) bezeichnet eine Schnittstelle, mit der fremder Programmcode in eine
bestehende Anwendung integriert werden kann. Dadurch kann die Anwendung erweitert oder ihr Ablauf
verändert werden. In der objektorientierten Programmierung kann dies durch Vererbung geschehen. Eine
Klasse stellt leere Methoden – also Methoden, die nichts tun, wenn man sie aufruft – zur Verfügung, die
dann in Unterklassen von Anwendern der Klasse nach eigenem Bedarf implementiert werden können.
377
378
34 Tests und Fehler
Ein Aufruf führt zu folgendem Ergebnis:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
$ python3 fibonacci_unittest2.py
setUp executed!
tearDown executed!
.
---------------------------------------------------------------------Ran 1 test in 0.000s
OK
Die meisten der TestCase-Methoden verfügen über einen optionalen Parameter „msg”. Mit
„msg” kann man eine zusätzliche Beschreibung für einen Fehler ausgeben.
Tabelle 34.1 Methoden der Klasse TestCase
Methode
Bedeutung
setUp()
Bei der Methode setUp handelt es sich um eine
sogenannte Hook-Methode. Sie wird vor jedem Aufruf
der implementierten Testmethoden aufgerufen. Wird in
der Methode setUp eine Ausnahme generiert, so wird
diese auch als Error in der Testausgabe ausgegeben.
Selbstverständlich wird auch bei einer Ausnahme im
setUp-Code der Test abgebrochen.
tearDown()
Die Methode tearDown wird nach dem Aufruf einer
Testmethode gestartet. Ebenso wie bei setUp gilt, dass
im Code von tearDown generierte Ausnahmen auch in
der Testausgabe ausgegeben werden.
assertEqual(self, first, second,
msg=None)
Der Test schlägt fehl, wenn die Parameter „first” und
„second” nicht gleich sind. Dabei ist Gleichheit im Sinne
von „==” gemeint, also Wertegleichheit und nicht nur
reine Objektgleichheit.
assertAlmostEqual(self, first,
second, places=None, msg=None,
delta=None)
Diese Methode schlägt fehl, wenn die Differenz der
beiden Parameter „first” und „second” gleich 0 ist,
nachdem man sie vor dem Vergleich auf „places”
Nachkommastellen gerundet hatte. Der Default-Wert für
„places” ist 7.
assertCountEqual(self, first,
second, msg=None)
Die Parameter „first” und „second” müssen hierbei
sequentielle Datentypen sein. Es muss Folgendes
gelten: Alle Elemente müssen genauso oft in „first” wie in
„second” vorkommen.
Beispiel:
[0, 1, 1] und [1, 0, 1] gelten in obigem Sinne als gleich,
weil die 0 und die 1 jeweils gleich oft vorkommen.
[0, 0, 1] und [0, 1] sind verschieden, weil die 0 in der
ersten Liste zweimal vorkommt und in der zweiten Liste
nur einmal.
assertDictEqual(self, d1, d2,
msg=None)
Betrachtet die beiden Argumente als Dictionaries und
prüft auf Gleichheit.
(Fortsetzung nächste Seite)
34.7 Methoden der Klasse TestCase
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Tabelle 34.1 Methoden der Klasse TestCase (Fortsetzung)
Methode
Bedeutung
assertTrue(self, expr, msg=None)
Prüft, ob der Ausdruck „expr” True ist.
assertGreater(self, a, b,
msg=None)
Prüft, ob a > b gilt.
assertGreaterEqual(self, a, b,
msg=None)
Prüft, ob a >= b gilt.
assertFalse(self, expr, msg=None)
Prüft, ob der Ausdruck „expr” True ist.
assertLess(self, a, b, msg=None)
Prüft, ob a < b gilt.
assertLessEqual(self, a, b,
msg=None)
Prüft, ob a <= b gilt.
assertIn(self, member, container,
msg=None)
Prüft, ob a in b gilt.
assertIs(self, expr1, expr2,
msg=None)
Prüft, ob a is b gilt.
assertIsInstance(self, obj, cls,
msg=None)
Prüft, ob isinstance(obj, cls) gilt.
assertIsNone(self, obj,
msg=None)
Prüft, ob obj is None gilt.
assertIsNot(self, expr1, expr2,
msg=None)
Prüft, ob a is not b gilt.
assertIsNotNone(self, obj,
msg=None)
Prüft, ob obj nicht None ist.
assertItemsEqual(self,
expected_seq, actual_seq,
msg=None)
Auf zwei sequentiellen Datenobjekten wird eine
Gleichheit über die Anzahl der Elemente geprüft, d.h. es
wird geprüft, ob jedes Element in jeder Liste gleich häufig
vorkommt.
Beispiel: [0, 1, 1] und [1, 0, 1] gelten als gleich. [0, 0, 1]
and [0, 1] gelten nicht als gleich, da die Anzahl der
Nullen in beiden Listen variiert.
assertListEqual(self, list1, list2,
msg=None)
Listen werden auf Gleichheit geprüft.
assertMultiLineEqual(self, first,
second, msg=None)
Mehrzeilige Strings werden auf Gleichheit geprüft.
assertNotRegexpMatches(self,
text, unexpected_regexp,
msg=None)
Schlägt fehl, wenn der Text „text” den regulären Ausdruck
unexpected_regexp matcht.
assertTupleEqual(self, tuple1,
tuple2, msg=None)
Analog zu assertListEqual
379
380
34 Tests und Fehler
34.8
Aufgaben
1. Aufgabe:
Betrachten Sie folgendes Beispiel für einen doctest. Erkennen Sie ein Problem?
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
import doctest
def fib(n):
"""
Die Fibonacci-Zahl für die n-te
Generation wird iterativ berechnet
>>> fib(0)
0
>>> fib(1)
1
>>> fib(10)
55
>>> fib(40)
102334155
>>>
"""
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
if __name__ == "__main__":
doctest.testmod()
Lösung: Lösungen zu Kapitel 34 (Tests und Fehler), Seite 512
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
35
35.1
Daten konservieren
Persistente Speicherung
In diesem Kapitel geht es darum, Wege zu zeigen, wie man
Programmdaten über das Programmende oder den Programmabbruch hinaus speichern kann. Man bezeichnet
dies in der Informatik als persistente Daten. Darunter versteht man, dass die Daten eines Programms auch nach dem
kontrollierten oder auch unvorhergesehenen Ende bei einem erneuten Programmstart wieder zur Verfügung stehen. Daten, die nur im Hauptspeicher des Computers existieren, werden als flüchtige oder transiente Daten bezeichnet, da sie bei einem erneuten Programmstart nicht mehr
zur Verfügung stehen.
Zur Generierung von persistenten Daten gibt es verschiedene Möglichkeiten:
■
■
■
■
Bild 35.1 Daten „eingurken”
Manuelle Speicherung in einer Datei
Nicht zu empfehlen, da man dann die komplette Serialisierung der Daten selbst vornehmen muss.
marshal-Modul
Ähnlich wie das pickle-Modul. Wir werden dieses Modul jedoch nicht behandeln, da allgemein aus verschiedenen Gründen empfohlen wird, pickle zu verwenden.
pickle-Modul
Wir werden im Folgenden näher auf dieses Modul eingehen.
shelve-Modul
Shelve baut auf pickle auf und bietet eine Dictionary-ähnliche Struktur!
382
35 Daten konservieren
35.2
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
35.2.1
Pickle-Modul
Daten sichern mit pickle.dump
Beim Programmieren kommt es natürlich immer wieder mal vor, dass man in die Klemme
gerät, aber bei Python ist das wahrscheinlich – hoffen wir – seltener als in anderen Sprachen
der Fall. In die Klemme geraten heißt im Englischen „to get oneself into a pickle”. Aber „to
pickle” bedeutet eigentlich einlegen oder pökeln, z.B. saure Gurken. Aber was hat nun das
Pökeln von sauren Gurken mit Python zu tun? Na ja, ganz einfach: Python hat ein Modul,
das „pickle” heißt, und mit dem kann man Python-Objekte gewissermaßen einlegen, um
sie später, also in anderen Sitzungen oder anderen Programmläufen, wieder zu benutzen.
Aber jetzt wollen wir wieder seriös werden: Mit dem Modul pickle (dt. einlegen) lassen sich
Objekte serialisiert abspeichern, und zwar so, dass sie später wieder deserialisiert werden
können. Dazu dient die Methode dump, die in ihrer allgemeinen Syntax wie folgt aussieht:
dump(obj, file, protocol=None, *, fix_imports=True) -> None
dump() schreibt eine „gepickelte” Darstellung des Objekts „obj” in das Dateiobjekt file. Das
optionale Argument für das Protokollargument „protocol” steuert die Art der Ausgabe:
Protokollversion
Beschreibung
0
ist die ursprüngliche Ablageart des Pickle-Moduls, d.h. vor Python 3. Dabei
handelt es sich um ein für Menschen lesbares Format. Dieser Modus ist
abwärts kompatibel mit früheren Python-Versionen.
1
benutzt das alte Binärformat. Dieser Modus ist ebenfalls abwärts kompatibel mit früheren Python-Versionen.
2
wurde mit 2.3. eingeführt. Es stellt ein effizienteres „Pickling” zur Verfügung.
3
wurde speziell für Python 3.0 entwickelt. Dateien, die in diesem Modus erzeugt werden, können nicht mehr in Python 2.x mit dem zugehörigen PickleModul bearbeitet werden. Es handelt sich hierbei um den von Python 3.x
empfohlenen Modus. Er stellt auch den Default-Wert dar.
Wird das Argument „fix_imports” auf True gesetzt und hat „protocol” einen Wert kleiner
als 3, wird pickle versuchen, die neuen Python 3.x-Namen auf die alten Modulnamen zu
setzen, sodass der Pickle-Datenstrom mit Python 2.x lesbar ist.
Im folgenden Beispiel schreiben wir eine Liste in eine Pickle-Datei:
>>>
>>>
>>>
>>>
>>>
import pickle
cities = ["Bern", "Basel","St. Gallen", "Zürich"]
fh = open("data.pkl","wb")
pickle.dump(cities,fh)
fh.close()
Es stellt sich die Frage, welche Art von Daten man eigentlich pickeln kann? Im Prinzip alles,
was man sich vorstellen kann:
■
Alle Python-Basistypen wie Booleans, Integers, Floats, komplexe Zahlen, Strings, ByteArrays und Byte-Objekte und natürlich None.
35.3 Ein persistentes Dictionary mit shelve
■
Listen und Tupels, Mengen und Dictionaries
■
Beliebige Kombinationen der beiden vorigen Gruppen
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
Außerdem können auch Funktionen, Klassen und Instanzen unter gewissen Bedingungen gepickelt werden.
file-Objekte lassen sich nicht pickeln.
35.2.2
pickle.load
Objekte, die mit pickle.dump() in eine Datei geschrieben worden sind, können mit der
Pickle-Methode pickle.load(file) wieder eingelesen werden. pickle.load erkennt automatisch, in welchem Format eine Datei erstellt worden ist. Im Folgenden zeigen wir, wie wir
die eben geschriebene Liste in einer anderen Sitzung wieder einlesen können:
$ python3
Python 3.2.3 (default, Oct 19 2012, 19:53:57)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> f = open("data.pkl","rb")
>>> staedte = pickle.load(f)
>>> print(staedte)
['Bern', 'Basel', 'St. Gallen', 'Zürich']
35.3
shelve-Modul
Hat man das shelve-Modul importiert und beginnt die englische Beschreibung unter help(shelve) zu lesen, wundert
man sich vielleicht über die Rechtschreibung. Dort steht:
„A ’shelf’ is a persistent, dictionary-like object.” Wir haben
also ein Modul, was sich „shelve” nennt und ein dictionaryähnliches Objekt, was „shelf” geschrieben wird. Es handelt sich nicht um einen Verschreiber. Das Wort „shelf” ist
ein Substantiv und bedeutet „Regal” oder „Ablage”, während „shelve” als Verb benutzt wird und meistens bedeutet,
dass man etwas in ein Regal stellt. Nach dieser kurzen englischen Grammatikauffrischung sind wir auch schon der
Aufgabe des shelve-Moduls näher gekommen. shelve funktioniert wie ein Bücherregal, nur dass wir statt „Büchern”
Daten haben und unser Regal eine Datei ist. Prinzipiell ver- Bild 35.2 Bücherregal
hält sich ein shelf-Objekt wie ein Python-Dictionary. Allerdings gibt es zwei wesentliche Unterschiede. Bevor man ein shelf benutzen kann, muss
man es öffnen, d.h. man öffnet die Datei, und nach der Benutzung muss man die Datei wieder schließen. Außerdem gibt es eine Einschränkung bzgl. der Schlüssel, die man
383
384
35 Daten konservieren
verwenden kann. Während ein Python-Dictionary beliebige unveränderliche Datentypen
als Keys verwenden kann, also beispielsweise Integer, Floats, Strings und Tupel, kann ein
shelf nur Strings als Keys verwenden. Dies ist allerdings nur eine geringe Beschränkung der
Nutzbarkeit. Für die Werte gibt es hingegen keine Einschränkungen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Das Shelve-Modul lässt sich sehr einfach benutzen. Man importiert zuerst das Modul
selbst, und dann öffnet man eine Datei mit dem zu verwendenden Shelf-Objekt unter
Verwendung der shelve-Methode „open”:
>>> import shelve
>>> s = shelve.open("MyShelve")
Falls bereits eine Datei „MyShelve” existiert, versucht die open-Methode, diese zu öffnen.
Falls es sich um eine Datei handelt, die nicht unter Verwendung des shelve-Moduls angelegt worden ist, erfolgt eine Fehlermeldung, da es sich dann nicht um Daten im korrekten
Format handelt. Falls die Datei noch nicht existiert, wird eine Datei im korrekten Format
angelegt.
Jetzt kann man mit s arbeiten wie mit einem Dictionary:
>>> s["street"] = "Fleet Str"
>>> s["city"] = "London"
>>> for key in s:
...
print(key)
...
city
street
Bevor man das Programm verlässt, in dem man mit einem shelf-Objekt arbeitet, sollte man
das shelf-Objekt wieder schließen.
>>> s.close()
Wir können nun die Shelf-Datei in einem beliebigen Python-Programm oder in der interaktiven Shell wieder benutzen, und die alten Daten stehen sofort zur Verfügung:
$ python3
Python 3.2.3 (default, Feb 28 2014, 00:22:33)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import shelve
>>> s = shelve.open("MyShelve")
>>> s["street"]
'Fleet Str'
>>> s["city"]
'London'
>>>
35.3 Ein persistentes Dictionary mit shelve
Man kann ein shelve-Objekt auch einfach mit dem dict-Casting-Operator in ein Dictionary
wandeln:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> s
<shelve.DbfilenameShelf object at 0xb7133dcc>
>>> dict(s)
{'city': 'London', 'street': 'Fleet Str'}
>>>
Im folgenden Beispiel demonstrieren wir, dass wir in einem Shelf-Eintrag auch komplexe
Objekte wie beispielsweise Dictionaries abspeichern können:
>>>
>>>
>>>
>>>
import shelve
tele = shelve.open("MyPhoneBook")
tele["Mike"] = {"first":"Mike", "last":"Miller", "phone":"4689"}
tele["Steve"] = {"first":"Stephan", "last":"Burns", "phone
":"8745"}
>>> tele["Eve"] = {"first":"Eve", "last":"Naomi", "phone":"9069"}
>>> tele["Eve"]["phone"]
'9069'
Die Daten sind auch in diesem Fall persistent gespeichert, d.h. man kann sie auch wieder in
einem späteren Programmlauf benutzen. Dafür braucht man nur die Datei MyPhoneBook
zu öffnen:
$ python3
Python 3.2.3 (default, Feb 28 2014, 00:22:33)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import shelve
>>> tele = shelve.open("MyPhoneBook")
>>> tele["Steve"]["phone"]
'8745'
>>>
385
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
36
36.1
Reguläre Ausdrücke
Ursprünge und Verbreitung
In diesem Abschnitt präsentieren wir eine detaillierte und anschauliche Einführung in die regulären Ausdrücke ganz allgemein, aber vor allen Dingen auch unter
Python 3.
Der Begriff „regulärer Ausdruck” kommt
aus der Automatentheorie und der Theorie der formalen Sprachen, zwei Gebieten
der theoretischen Informatik. Als Erfinder
der regulären Ausdrücke gilt der amerika- Bild 36.1 Reguläre Ausdrücke
nische Mathematiker Stephen Cole Kleene, der in den 1950er Jahren reguläre Mengen einführte. In der theoretischen Informatik
dienen reguläre Ausdrücke zur formalen Definition einer Sprachfamilie mit bestimmten
Eigenschaften, die sogenannten regulären Sprachen. Zu jedem regulären Ausdruck existiert ein endlicher Automat, der die von dem Ausdruck spezifizierte Sprache akzeptiert.
In Programmiersprachen werden reguläre Ausdrücke meist zur Filterung von Texten oder
Textstrings genutzt, d.h. sie erlauben einem zu prüfen, ob ein Text oder ein String zu einem
RA „matcht”, d.h. zutrifft, übereinstimmt oder passt.
RA finden auch Verwendung, um Textersetzungen durchzuführen, die recht komplex sein
können.
Einen äußerst interessanten Aspekt von regulären Ausdrücken möchten wir nicht unerwähnt lassen: Die Syntax der regulären Ausdrücke ist in allen Programmiersprachen und
Skriptsprachen gleich, also z.B. in Python, Perl, Java, SED oder AWK. Außerdem werden sie
von vielen Texteditoren wie zum Beispiel dem vi benutzt.
36.2
Stringvergleiche
Ein einfacher Sonderfall von regulären Ausdrücken stellt die Suche nach einem String in
einem anderen String dar. Dazu kann man statt regulärer Ausdrücke natürlich auch den
388
36 Reguläre Ausdrücke
„in”-Operator benutzen, den wir im Abschnitt über sequentielle Datentypen bereits kennengelernt haben.
>>> s = "Reguläre Ausdrücke einfach erklärt!"
>>> "einfach" in s
True
>>>
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im obigen Beispiel wurde geprüft, ob das Wort „einfach” im String s vorkommt.
Im Folgenden zeigen wir Schritt für Schritt, wie die String-Vergleiche durchgeführt werden.
In dem String s = "xaababcbcd"
soll geprüft werden, ob der Substring sub = "abc"
vorkommt. Übrigens handelt es sich bei einem Substring bereits um einen regulären Ausdruck, wenn auch einen besonders einfachen.
Zuerst wird geprüft, ob die ersten Positionen übereinstimmen, also s[0] == sub[0]. Dies ist
in unserem Beispiel nicht erfüllt, was wir durch die Farbe Rot kenntlich machen:
Nun wird geprüft, ob gilt s[1:4] == sub. Dazu wird erst geprüft, ob sub[0] == s[1] erfüllt ist.
Dies gilt, was wir mit der Farbe Grün kenntlich machen. Dann wird weiter verglichen, aber
s[2] ist ungleich sub[1]:
Nun vergleichen wir s[2:5] mit sub. In diesem Fall stimmen sogar die ersten beiden Positionen des Substrings überein.
Für den nächsten Schritte benötigen wir wohl keine Erklärung mehr:
36.3 Überlappungen und Teilstrings
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im nächsten Schritt kann eine vollständige Übereinstimmung gefunden werden, denn es
gilt s[4:7] == sub
36.3
Überlappungen und Teilstrings
Mit regulären Ausdrücken können prinzipiell zwei Aufgaben bewerkstelligt werden:
1. Überlappungen: Feststellen, ob sich ein String vollständig durch einen regulären Ausdruck überlappen lässt. Zum Beispiel werden in Kontaktformularen im Internet reguläre Ausdrücke dazu verwendet, die Gültigkeit von E-Mail-Adressen oder Postleitzahlen
zu überprüfen.
2. Teilstringsuche: In einem String wird geprüft, ob es einen Teilstring gibt, der zu einem
gegebenen regulären Ausdruck passt.
36.4
Das re-Modul
Reguläre Ausdrücke gehören nicht zum Grundumfang von Python, d.h. sie sind nicht built
in. Um in Python mit regulären Ausdrücken arbeiten zu können, muss man erst das Modul
re importieren. re ist eine Standardbibliothek, die zahlreiche Funktionen und Methoden
zum Arbeiten mit regulären Ausdrücken zur Verfügung stellt.
Beginnen wir mit der wohl wichtigsten Funktion: search
Syntax:
re.search(pattern, string, flags=0)
Mit dieser Funktion wird ein String „string” durchsucht, ob der reguläre Ausdruck „pattern”
darin vorkommt. Gibt es eine Übereinstimmung, d.h. also einen Teilstring in „string”, auf
den der reguläre Ausdruck „pattern” passt, dann gibt die Funktion search ein MatchObject
zurück. Falls „pattern” nicht passt, wird None zurückgegeben.
Ein regulärer Ausdruck entspricht einem String in Python. Es empfiehlt sich aber, am besten immer einen raw-String zu verwenden, da es sonst häufig zu Problemen kommt, wenn
man einen Rückwärtsschrägstrich (Backslash) verwendet. Das Problem liegt darin, dass
Python Rückwärtsschrägstriche bereits auswertet, bevor es einen String an das re-Modul
übergibt.
„\b” wird in regulären Ausdrücken dazu verwendet, Wortgrenzen zu bezeichnen. Benutzt
man „\b” aber in einem String, wird er von Python als Backspace (also Rückwärtsschritt)
interpretiert. Man sieht dies am besten im folgenden Beispiel:
389
390
36 Reguläre Ausdrücke
Der reguläre Ausdruck „\b(\w+).*\b\1\b” wird Ihnen zum jetzigen Zeitpunkt noch wie
Hieroglyphen vorkommen, aber er dient dazu, in einem String festzustellen, ob ein Wort
mehr als einmal vorkommt.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Benutzt man in search einen normalen String statt eines Raw-Strings, funktioniert es nicht:
>>> import re
>>> t = "Manchmal ist ein Wort doch kein Wort."
>>> x = re.search("\b(\w+).*\b\1\b", t)
>>> print(x)
None
>>> x = re.search(r"\b(\w+).*\b\1\b", t)
>>> print(x)
<_sre.SRE_Match object at 0x8e89220>
Das liegt daran, dass Python, bevor es einen String an search übergibt, diesen interpretiert, d.h. Escape-Sequenzen werden aufgelöst. „\b” steht zum Beispiel für einen Backspace
(Rückwärtsschritt):
>>> s = "abc\bde\bf"
>>> print(s)
abdf
Die Buchstaben „c” und „e” wurden gewissermaßen aus dem String gelöscht. So werden
auch alle Zeichen vor einem „\b” aus einem regulären Ausdruck gelöscht, bevor dieser an
re.search übergeben wird. Dies geschieht aber nicht, wenn wir einen Raw-String verwenden.
Ohne Raw-Strings müsste man jeden Backslash mit einem Backslash versehen, um die Sequenz, also z.B. „\b”, vor einer Interpretation durch den Python-Interpreter zu schützen1 :
>>> s = "abc\\bde\\bf"
>>> print(s)
abc\bde\bf
36.5
Matching-Problem
Bevor wir mit der Besprechung der Syntax der regulären Ausdrücke beginnen, wollen wir
noch auf ein allgemeines Problem bei regulären Ausdrücken zu sprechen kommen. Wenn
Sie wollen, können Sie diesen Abschnitt momentan überspringen, um ihn später durchzuarbeiten.
Jeder String – also Strings ohne spezielle Zeichen mit Sonderbedeutungen – ist bereits ein
regulärer Ausdruck. So ist beispielsweise r"cat" ein regulärer Ausdruck. Er gehört zu den
wohl am einfachsten zu verstehenden Ausdrücken, denn er enthält keinerlei Metazeichen
(Funktionszeichen) mit Sonderbedeutungen. Unser Beispielausdruck r"cat" matcht beispielsweise die folgende Zeichenkette: „A cat and a rat can’t be friends.”
1
englisch: escape
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
36.5 Matching-Problem
Interessanterweise zeigt sich schon in diesem ersten Beispiel ein „beliebter” Fehler. Eigentlich will man Strings matchen, in denen das Wort cat vorkommt. Dies gelingt auch, aber
man erhält auch beispielsweise „cats”, was möglicherweise
noch erwünscht ist. Schlimmer sind jedoch eine ganze Menge zusätzlicher Wörter, in denen die Buchstabenfolge „cat”
als Teilstring vorkommt, also auch Wörter wie „education”,
„communicate”, „falsification”, „ramifications”, „cattle” und
viele andere. Dies ist ein Fall von „over matching”, d.h. wir
erhalten positive Ergebnisse, die nicht gewünscht sind.
Wir haben dies im nebenstehenden Diagramm mengenmäßig veranschaulicht. Der dunkelgrüne Kreis C entspricht der
Menge, die wir gerne erkennen wollen, aber wir erkennen die
Menge O (blauer Kreis). C ist eine Teilmenge von O. In die- Bild 36.2 „under” und „over”
sem Diagramm erkennt man auch eine Menge U (hellgrüner matching
Kreis), die eine Teilmenge unserer gewünschten Menge C ist.
Dies ist ein Fall von „under matching”. Ein Beispiel dafür erhalten wir, wenn wir versuchen,
unseren regulären Ausdruck zu verbessern. Wir könnten auf die Idee kommen, vor und hinter dem Wort cat im regulären Ausdruck ein Leerzeichen einzufügen, also r" cat ". Durch
diese Änderung würden wir nicht mehr auf Wörter wie „education”, „falsification” und „ramification” hereinfallen. Wie sieht es aber mit einem String "The cat, called Oscar, climbed
on the roof." aus? Er wird nun als nicht mehr passend eingestuft.
Diese Problematik wird im Folgenden noch klarer. Zunächst wollen wir uns jedoch mal
anschauen, wie man reguläre Ausdrücke in Python überprüfen kann. Dann sind wir auch
in der Lage, die folgenden Beispiele direkt in Python nachzuvollziehen.
>>> import re
>>> x = re.search("cat","A cat and a rat can't be friends.")
>>> print(x)
<_sre.SRE_Match object at 0x7fd4bf238238>
>>> x = re.search("cow","A cat and a rat can't be friends.")
>>> print(x)
None
Im vorigen Beispiel haben wir die Methode search aus dem re-Modul verwendet. Es ist
wohl die am wichtigsten und am häufigsten benutzte Methode. Mittels search(expr,s) wird
ein String s nach dem Vorkommen eines Teilstrings untersucht, der auf den regulären Ausdruck expr passt. Der erste gefundene Teilstring wird zurückgeliefert. Wenn wir das Ergebnis ausdrucken, sehen wir, dass im positiven Fall ein sogenanntes Match-Objekt zurückgegeben wird, während im negativen Fall ein „None” zurückgegeben wird. Mit diesem Wissen
kann man reguläre Ausdrücke bereits in einem Python-Skript nutzen, ohne Näheres über
die Match-Objekte zu wissen:
>>> if re.search("cat","A cat and a rat can't be friends."):
...
print("Der Ausdruck hat gepasst")
... else:
...
print("Der Ausdruck hat nicht gepasst")
...
Der Ausdruck hat gepasst
391
392
36 Reguläre Ausdrücke
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> if re.search("cow","A cat and a rat can't be friends."):
...
print("Der Ausdruck hat gepasst")
... else:
...
print("Der Ausdruck hat nicht gepasst")
...
Der Ausdruck hat nicht gepasst
36.6
36.6.1
Syntax der regulären Ausdrücke
Beliebiges Zeichen
Nehmen wir an, dass wir im vorigen Beispiel nicht daran interessiert waren, das Wort cat
zu finden, sondern dreibuchstabige Wörter, die mit „at” enden: Die regulären Ausdrücke
bieten einen Metacharacter „.”, der als Platzhalter für ein beliebiges Zeichen steht. Den
regulären Ausdruck könnte man so formulieren:
r" .at "
Der reguläre Ausdruck matcht nun durch Leerzeichen isolierte dreibuchstabige Wörter, die
mit „at” enden. Dieser Ausdruck passt nun auch auf Wörter wie „rat”, „cat”, „bat”, „eat”,
„sat” und andere.
Aber was ist, wenn im Text ein „@at” oder „3at” stünde? Die matchen dann auch, und wir
haben wieder ein „over matching” . Eine Lösung, um dies zu umgehen, lernen wir im nun
folgenden Unterabschnitt unserer Einführung kennen.
Im Folgenden sehen wir obigen regulären Ausdruck in einer Anwendung:
>>> import re
>>> for i in cat_and_more:
...
if re.search(r".at", i):
...
print("matched")
...
else:
...
print("no match")
...
matched
matched
matched
matched
no match
no match
>>>
36.7 Zeichenauswahl
36.7
Zeichenauswahl
Durch eckige Klammern „[” und „]” können wir eine Zeichenauswahl definieren. Der Ausdruck in eckigen Klammern steht dann für genau ein Zeichen aus dieser Auswahl. Betrachten wir den folgenden regulären Ausdruck:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
r"M[ae][iy]er"
Dieser Ausdruck passt auf vier verschiedene Schreibweisen des häufigen deutschen Familiennamens. Dem großen M kann ein kleines „a” oder ein kleines „e” folgen, dann muss ein
„i” oder „y” folgen, zum Abschluss dann „er”.
Statt einzelner Buchstaben – wie im vorigen Beispiel die Auswahl zwischen „e” oder „a”
(in RE-Notation [ea]) – benötigt man sehr häufig die Auswahl zwischen ganzen Zeichenklassen, also zum Beispiel eine Ziffer zwischen „0” und „5” oder ein Buchstabe zwischen
„a” und „e”. Dafür gibt es bei der Notation für reguläre Ausdrücke ein reserviertes Sonderzeichen innerhalb der Zeichenauswahl, nämlich den Bindestrich „-”. [a-e] ist eine abgekürzte Schreibweise für [abcde] oder [0-5] steht für [012345]. Der Vorteil beim Schreiben
wird sofort ersichtlich, wenn man die Zeichenauswahl „ein beliebiger Großbuchstabe” notieren will. Man kann [ABCDEFGHIJKLMNOPQRSTUVWXYZ] oder [A-Z] schreiben. Wen
das noch nicht überzeugt: Wie sieht es mit der Zeichenauswahl „ein beliebiger Klein- oder
Großbuchstabe” aus? [A-Za-z] Die umständliche Alternative überlassen wir der geneigten
Leserin oder dem geneigten Leser :-)
Aber es gibt noch eine Besonderheit mit dem Bindestrich, den wir benutzt hatten, um den
Anfang und das Ende einer Zeichenklasse zu markieren. Der Bindestrich hat nur eine Sonderbedeutung, wenn er innerhalb von eckigen Klammern steht, und auch dann nur, wenn
er nicht unmittelbar nach der öffnenden eckigen Klammer oder vor der schließenden eckigen Klammer positioniert ist. So bezeichnet der Ausdruck [-az nur die Auswahl zwischen
den drei Zeichen „-”, „a” und „z”, aber keine anderen Zeichen. Das Gleiche gilt für [az-].
Frage:
Welche Zeichenklasse wird durch [-a-z] beschrieben?
Antwort:
Das Zeichen „-”, weil es am Anfang direkt nach der öffnenden Klammer steht, und alle
Zeichen zwischen „a” bis „z”, also das ganze Alphabet der kleinen Buchstaben und der Bindestrich.
Das einzige andere Metazeichen innerhalb von eckigen Klammern ist das Caret-Zeichen
(auch Textcursor oder Einschaltungszeichen genannt). Wenn es direkt hinter der öffnenden eckigen Klammer positioniert ist, dann negiert es die Auswahl. [^0-9] bezeichnet die
Auswahl „irgendein Zeichen, aber keine Ziffer”. Die Position des Caret-Zeichen innerhalb
der eckigen Klammern ist entscheidend. Wenn es nicht als erstes Zeichen steht, dann hat
es keine spezielle Bedeutung und bezeichnet nur sich selbst. [^abc] bedeutet alles außer
„a”, „b” oder „c”. [a^bc] bedeutet entweder ein „a”, „b”, „c” oder ein „^”.
393
394
36 Reguläre Ausdrücke
36.8
Endliche Automaten
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Dieser Abschnitt ist nicht zum weiteren Verständnis für die praktische Anwendung von
regulären Ausdrücken notwendig. Sie können ihn also getrost überspringen.
Ein endlicher Automat wird manchmal auch als Zustandsmaschine oder Zustandsautomat
bezeichnet.2 Bei einem endlichen Automaten handelt es sich um ein Modell, bestehend
aus Zuständen, Zustandsübergängen und Aktionen. Man bezeichnet ihn als endlich, weil
die Zahl der Zustände, die er annehmen kann, endlich ist.
Wir wollen hier nur ein Beispiel liefern: einen endlichen Automaten zum Akzeptieren der Meyer/Meier/Mayer/MaierVarianten.
Vereinfachung im Diagramm: Eigentlich
müsste es im Startknoten einen Zeiger geben, der wieder auf den Startknoten zurückzeigt. Das heißt, man bleibt solange Bild 36.3 Endlicher Automat
auf dem Startknoten, wie man Zeichen
liest, die von „M” verschieden sind. Von allen anderen Knoten müsste es auch einen Pfeil zum Startknoten zurück geben, wenn man
kein Zeichen liest, was auf den ausgehenden Pfeilen vorhanden ist.
Wie bereits eingangs gesagt, kann man das eben über endliche Automaten Gesagte getrost
ignorieren, wenn man es nicht versteht. Es ist nicht wesentlich für die Anwendung von
regulären Ausdrücken.
36.9
Anfang und Ende eines Strings
Wie wir bereits ausgeführt hatten, ist der Ausdruck r"M[ae][iy]er" in der Lage, verschiedene Schreibweisen des Namen Meyer zu matchen. Dabei spielt es keine Rolle, ob sich der
Name am Anfang, im Inneren oder am Ende des Strings befindet.
>>> import re
>>> line = "He is a German called Mayer."
>>> if re.search(r"M[ae][iy]er",line):
...
print("I found one!")
...
I found one!
>>>
Aber wie sieht es aus, wenn wir nur Vorkommen des Namens direkt am Anfang eines
Strings suchen, d.h. dass der String unmittelbar mit dem „M” des Namens beginnt? Das
re-Modul von Python stellt zwei Funktionen zum Matchen von regulären Ausdrücken zur
2
englisch: finite state machine (FSM)
36.9 Anfang und Ende eines Strings
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Verfügung. Eine der beiden haben wir bereits kennengelernt, d.h. die Funktion search().
Die andere Funktion hat unserer Meinung nach einen irreführenden Namen, denn sie
heißt match(). Irreführend deshalb, weil match(re_str, s) nur prüft, ob eine Übereinstimmung am Anfang des Strings vorliegt. Eigentlich würde man erwarten, dass match eine
komplette Überlappung des regulären Ausdrucks mit dem String s überprüft. Aber egal
wie, match() ist eine Lösung auf unsere Fragestellung, wie wir im folgenden Beispiel sehen:
>>> import re
>>> s1 = "Mayer is a very common name."
>>> s2 = "He is called Meyer but he isn't German."
>>> print(re.search(r"M[ae][iy]er", s1))
<_sre.SRE_Match object at 0xb724a0c8>
>>> print(re.search(r"M[ae][iy]er", s2))
<_sre.SRE_Match object at 0xb724a0c8>
>>> print(re.match(r"M[ae][iy]er", s1))
<_sre.SRE_Match object at 0xb724a0c8>
>>> print(re.match(r"M[ae][iy]er", s2))
None
>>>
Auf diese Art können wir zwar den Anfang eines Strings matchen, aber diese Methode funktioniert nur in Python. Glücklicherweise stellt die Syntax der regulären Ausdrücke eine elegantere und allgemeingültige Möglichkeit zur Verfügung.
Das Zeichen „^” (Textcursor, Einfügezeichen) stellt sicher, dass der nachfolgende reguläre
Ausdruck nur direkt auf den Anfang des Strings angewendet wird, d.h. der reguläre Ausdruck mit einem führenden „^” muss also auf den Anfang des Strings passen. Außer im
MULTILINE-Modus, dann kann der Ausdruck immer auf ein Newline-Zeichen folgen.
>>> import re
>>> s1 = "Mayer is a very common name."
>>> s2 = "He is called Meyer but he isn't German."
>>> print(re.search(r"^M[ae][iy]er", s1))
<_sre.SRE_Match object at 0x7fc59c5f26b0>
>>> print(re.search(r"^M[ae][iy]er", s2))
None
Aber was passiert, wenn wir die beiden Strings s1 und s2 auf nachfolgende Art zusammenfügen?
s = s2 + "\n" + s1
Der String beginnt nicht mit einem Maier, egal in welcher Schreibweise:
>>> s = s2 + "\n" + s1
>>> print(re.search(r"^M[ae][iy]er", s))
None
>>>
Der Ausdruck konnte nicht matchen. Aber der Name kommt nach einem Newline-Zeichen
vor. Deshalb ändert sich das Ergebnis, wenn wir den MULTILINE-Modus zuschalten:
395
396
36 Reguläre Ausdrücke
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> s = s2 + "\n" + s1
>>> print(re.search(r"^M[ae][iy]er", s, re.MULTILINE))
<_sre.SRE_Match object at 0xb724a100>
>>> print(re.search(r"^M[ae][iy]er", s, re.M))
<_sre.SRE_Match object at 0xb724a100>
>>> print(re.match(r"^M[ae][iy]er", s, re.M))
None
>>>
Das vorige Beispiel zeigt auch, dass der Multiline-Modus keinen Einfluss auf die matchMethode hat. match() prüft nie etwas anderes als den Anfang des Strings unabhängig davon, ob man sich im Multiline-Modus befindet oder nicht.
Damit haben wir die Prüfung für den Anfang eines Strings erledigt. Die Prüfung, ob ein
regulärer Ausdruck auf das Ende eines Strings passt, sieht ähnlich aus. Dazu erhält das
„$”-Zeichen eine Sonderbedeutung. Wird ein regulärer Ausdruck von einem „$”-Zeichen
gefolgt, dann muss der Ausdruck auf das Ende des Strings passen, d.h. es darf kein weiteres Zeichen zwischen dem regulären Ausdruck und dem Newline des Strings stehen. Wir
demonstrieren dies im folgenden Beispiel:
>>> print(re.search(r"Python\.$","I like
<_sre.SRE_Match object at 0xb724a138>
>>> print(re.search(r"Python\.$","I like
None
>>> print(re.search(r"Python\.$","I like
Perl."))
None
>>> print(re.search(r"Python\.$","I like
Perl.", re.M))
<_sre.SRE_Match object at 0xb724a138>
>>>
Python."))
Python and Perl."))
Python.\nSome prefer Java or
Python.\nSome prefer Java or
Die Metazeichen „^” und „$” verhalten sich anders als die bisher von uns verwendeten
Zeichenliterale und Zeichenmengen. Wenn wir beispielsweise nach dem regulären Ausdruck r"c.t" in dem String "A cat and a rat can’t be friends." suchen, dann wird
für jedes Zeichen des regulären Ausdrucks nach einem passenden Zeichen in dem String
gesucht.
>>> import re
>>> s = "A cat and a rat can't be friends."
>>> x = re.search(r"c.t", s)
>>> x.group()
'cat'
>>>
Bei den eben besprochenen Metazeichen zur Erkennung der Stringbegrenzungen, also Anfang und Ende, wird aber nicht nach einem Zeichen, sondern nach einer Position in einem
String gesucht. Man bezeichnet die Zeichen „^” und „$” deshalb auch als Ankerzeichen.
Ankerzeichen können vor, zwischen oder nach Zeichen stehen. Sie dienen dazu, den Match
36.10 Vordefinierte Zeichenklassen
an bestimmten Positionen zu verankern. So „verankert” das „^” einen regulären Ausdruck
an den Anfang des Strings und das „$” entsprechend an das Ende.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
36.10
Vordefinierte Zeichenklassen
Im Prinzip kann es sehr mühsam werden, bestimmte Zeichenklassen auf die bisherige Art
und Weise zu konstruieren. Ein gutes Beispiel hierfür ist sicherlich die Zeichenklasse, die
einen gültigen Wortbuchstaben definiert. Dies sind alle Klein- und Großbuchstaben, alle
Ziffern und der Unterstrich „_”. Das entspricht der folgenden Zeichenklasse
r"[a-zA-Z0-9_]"
Deshalb gibt es für häufig vorkommende Zeichenklassen vordefinierte Kürzel:
Die vordefinierten Zeichenklassen \b und
\B der vorigen Übersicht werden häufig nicht richtig oder gar falsch verstanden. Während die anderen Klassen einzelne Zeichen matchen (so matcht beispielsweise \w unter anderem „a”, „b”, „m”, „3”
usw.), matchen sie aber keine Zeichen. Bei
diesen Metazeichen handelt es sich ebenBild 36.4 Wortbegrenzer
so wie bei dem „^”- und dem „$”-Zeichen
um Ankerzeichen. Sie dienen dazu, Wortgrenzen zu markieren. Sie matchen Positionen in
Abhängigkeit von deren Nachbarschaft, d.h. es hängt davon ab, welches Zeichen vor und
nach der Position steht. \b passt zum Beispiel auf die Position zwischen einem \W und eiTabelle 36.1 Vordefinierte Zeichenklassen
Kürzel
Bedeutung
\d
Eine Ziffer, entspricht [0-9].
\D
das Komplement von \d. Also alle Zeichen außer den Ziffern, entspricht
der Klassennotation [^0-9].
\s
Ein Whitespace, also Leerzeichen, Tabs, Newlines usw., entspricht der
Klasse [ \t \n \r \f \v ].
\S
Das Komplement von \s. Also alles außer Whitespace, entspricht
[^ \t \n \r \f \v ].
\w
Alphanumerisches Zeichen plus Unterstrich, also [a-zA-Z0-9_]. Wenn
die LOCALE gesetzt ist, matcht es auch noch die speziellen Zeichen der
LOCALE, also z.B. die Umlaute.
\W
Das Komplement von \w.
\b
Passt auf den leeren String, aber nur, wenn dieser am Anfang oder Ende
eines Strings ist.
\B
Passt wie \b auf den leeren String, aber nur, wenn dieser nicht am
Anfang oder Ende eines Strings ist.
\\
Ein Backslash.
397
398
36 Reguläre Ausdrücke
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
nem \w oder umgekehrt zwischen \w und \W. \B bezeichnet wie üblich das Komplement,
das bedeutet, es werden Positionen zwischen \W und \W und Positionen zwischen \w und
\w gematcht. Diesen Sachverhalt haben wir im nebenstehenden Diagramm veranschaulicht.
Der Anker \b wird oft benutzt, um Wörter aus einem String herauszufiltern. Im nächsten
Beispiel benutzen wir die Funktion findall, die wir erst im Detail im Abschnitt 36.15 besprechen werden. Prinzipiell ist findall aber einfach zu verstehen. findall liefert eine Liste
aller mit dem regulären Ausdruck gematchten Teilstrings zurück. Im folgenden Beispiel3
matchen wir alle Wörter in einem String mittels \b:
>>> import re
>>> txt = "Weit draußen im Meere ist das Wasser so blau wie die
Blütenblätter der schönsten Kornblume, und so klar wie das reinste
Glas, aber es ist dort sehr tief, tiefer als irgendein Ankertau
reicht, viele Kirchtürme müßten aufeinandergestellt werden, um
vom Grunde bis über das Wasser zu reichen."
>>>
>>> re.findall(r"\b\w+\b",txt)
['Weit', 'draußen', 'im', 'Meere', 'ist', 'das', 'Wasser', 'so', 'blau
',
'wie', 'die', 'Blütenblätter', 'der', 'schönsten', 'Kornblume', 'und',
'so', 'klar', 'wie', 'das', 'reinste', 'Glas', 'aber', 'es', 'ist',
'dort', 'sehr', 'tief', 'tiefer', 'als', 'irgendein', 'Ankertau',
'reicht', 'viele', 'Kirchtürme', 'müßten', 'aufeinandergestellt',
'werden', 'um', 'vom', 'Grunde', 'bis', 'über', 'das', 'Wasser',
'zu', 'reichen']
>>>
36.11
Optionale Teile
Falls Sie denken, dass wir bereits alle Schreibweisen der Namen Mayer und Co. erfasst hätten, dann irren Sie sich. Es gibt noch weitere Varianten in vielen anderen Schreibweisen. So
gibt es insbesondere in Bayern die Variante, in der das „e” vor dem „r” „verloren” gegangen
ist. Dadurch erhalten also noch vier weitere Schreibweisen: [’Mayr’, ’Meyr’, ’Meir’, ’Mair’]
zusätzlich zu unserer alten Liste [’Mayer’, ’Meyer’, ’Meier’, ’Maier’].
Wenn wir nun versuchen, einen passenden regulären Ausdruck zu konstruieren, fällt uns
auf, dass uns noch etwas fehlt: Wie können wir sagen: „e kann, aber muss nicht vorkommen”? Dafür hat man in der Syntax der regulären Ausdrücke dem Fragezeichen eine
Sonderbedeutung verpasst. Der Ausdruck „e?” bedeutet gerade, was wir wollen, also „der
Buchstabe e kann, aber muss nicht vorkommen”.
Unser finaler Mayer-Erkenner sieht nun wie folgt aus:
3
Der Text entspricht dem ersten Satz des Märchens „Die kleine Meerjungfer” von Hans Christian Andersen
36.12 Quantoren
r"M[ae][iy]e?r"
Ein Fragezeichen kann auch hinter einer runden Klammer stehen. Dann bedeutet das, dass
der komplette Unterausdruck innerhalb der Klammern vorkommen kann, aber nicht vorkommen muss. Mit dem folgenden Ausdruck können wir Teilstrings mit „Feb 2011” oder
„February 2011” erkennen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
r"Feb(ruary)? 2011"
36.12
Quantoren
Mit dem, was wir bisher an syntaktischen Mitteln kennengelernt haben, lassen sich bestimmte Eigenschaften nicht in regulären Ausdrücken abbilden. Beispielsweise benötigt
man immer wieder Möglichkeiten darzustellen, dass man bestimmte Teilausdrücke wiederholen will. Eine Form von Wiederholung hatten wir gerade eben kennengelernt: das
Fragezeichen. Ein Zeichen oder ein in runden Klammern eingeschlossener Teilausdruck
wird entweder einmal oder keinmal „wiederholt”.
Außerdem hatten wir zu Beginn dieser Einführung einen anderen Quantor kennengelernt,
ohne dass wir in besonderer Weise auf ihn eingegangen sind. Es handelte sich um den
Stern-Operator. Folgt ein Stern „*” einem Zeichen oder einem Teilausdruck, dann heißt
dies, dass dieses Zeichen oder der Teilausdruck keinmal oder beliebig oft vorkommen oder
wiederholt werden darf.
r"[0-9]*"
Der obige Ausdruck passt auf eine beliebige Folge von Ziffern, aber auch auf den leeren
String.
r".*"
passt auf eine beliebige Folge von Zeichen und auf den leeren String.
Übung:
Schreiben Sie einen regulären Ausdruck, der Strings matcht, die mit einer Folge von Ziffern
– wenigstens einer – beginnen und von einem Leerzeichen gefolgt werden.
Lösung:
r"[0-9][0-9]* .*"
So, Sie haben also das Plus-Zeichen verwendet? Das ist super, aber in diesem Fall haben Sie
wohl gemogelt, indem Sie weitergelesen haben, oder Sie wissen bereits mehr über reguläre
Ausdrücke als das, was wir bisher in unserem Kurs behandelt haben ,.
Also dann, wenn wir bereits beim Plus-Operator sind: Mit dem Plus-Operator kann man
auf angenehme Art und Weise die vorige Übung lösen. Im Prinzip funktioniert der PlusOperator wie der Sternchen-Operator, nur dass der Plus-Operator wenigstens ein Vorkommen des Zeichens oder Teilausdrucks verlangt.
Lösung unserer Aufgabe mit dem „+”-Operator:
r"[0-9]+ .*"
399
400
36 Reguläre Ausdrücke
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Aber auch mit Plus- und Sternchen-Operator fehlt noch etwas Wichtiges: Wir wollen in bestimmten Situationen die exakte Anzahl der Wiederholungen oder eine minimale oder maximale Anzahl von Wiederholungen angeben können. Nehmen wir an, dass wir Adresszeilen von Briefumschlägen in der Schweiz lesen wollen. Also die Zeile, in der die Postleitzahl
und der Ortsname steht, d.h. eine vierstellige Postleitzahl, gefolgt von einem Leerzeichen
und dem Ortsnamen. + bzw. * sind zu unspezifisch für diesen Fall, und die folgende Lösung
ist sicherlich zu umständlich:
r"^[0-9][0-9][0-9][0-9] [A-Za-z]+"
Glücklicherweise bietet die Syntax der regulären Ausdrücke eine optimale Lösung:
r"^[0-9]{4} [A-Za-z]*"
Nun wollen wir unseren regulären Ausdruck noch weiter verbessern. Nehmen wir an, dass
es keine Stadt oder keinen Ort in der Schweiz gibt, deren Name aus weniger als drei Buchstaben besteht. Diesen Umstand können wir mit [A-Za-z][3,} beschreiben. Nun wollen
wir auch noch Briefe mit erfassen, die nach Deutschland gehen. Postleitzahlen haben bekanntlich eine Stelle mehr in Deutschland. [0-9]{4,5} bedeutet, dass wir mindestens 4
Ziffern, aber höchstens 5 erwarten:
r"^[0-9]{4,5} [A-Z][a-z]{2,}"
Allgemein gilt: {min, max}: mindestens min-Mal und höchsten max-Mal. {, max} ist eine
abgekürzte Schreibweise für {0,to}, und {min,} ist eine Abkürzung für „höchstens minMal, aber keine Beschränkung nach oben”.
Ein praktisches Beispiel in Python
Bevor wir fortfahren, möchten wir eine kleine praktische Übung mit regulären Ausdrücken
in Python einschieben.
Dazu haben wir ein Telefonbuch4 der Simpsons. Genau, DIE SIMPSONS, die aus der berühmten amerikanischen Serie.
Allison Neu 555-8396
Bob Newhall 555-4344
C. Montgomery Burns 555-0001
C. Montgomery Burns 555-0113
Canine College 555-7201
Canine Therapy Institute 555-2849
Cathy Neu 555-2362
City of New York Parking Violation Bureau 555-BOOT
Dr. Julius Hibbert 555-3642
Dr. Nick Riviera 555-NICK
Earn Cash For Your Teeth 555-6312
Family Therapy Center 555-HUGS
Homer Jay Simpson (Plow King episode) 555-3223
Homer Jay Simpson (work) 555-7334
Jack Neu 555-7666
4
Das gesamte Telefonbuch finden Sie in unserem Programm- und Beispielverzeichnis.
36.13 Gruppierungen und Rückwärtsreferenzen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Jeb Neu 555-5543
Jennifer Neu 555-3652
Ken Neu 555-8752
Lionel Putz 555-5299
In dieser Liste befinden sich Leute, die mit Nachnamen „Neu” heißen. Die selbst auferlegte
Aufgabe besteht nun darin, diejenigen Leute zu finden, die den Namen Neu führen und
deren Vorname mit einem „J” beginnt. Dazu schreiben wir ein Python-Skript, das diese
Zeile einliest und Zeile für Zeile bearbeitet:
import re
fh = open("simpsons_phone_book.txt")
for line in fh:
if re.search(r"J.*Neu",line):
print(line.rstrip())
fh.close()
Startet man das Programm, erhält man folgende Ausgabe:
$ python3 phone_numbers.py
Jack Neu 555-7666
Jeb Neu 555-5543
Jennifer Neu 555-3652
36.13
Gruppierungen und
Rückwärtsreferenzen
Ausdrücke lassen sich, wie bereits erklärt, mit runden Klammern „(” und „)” zusammenfassen. Die gefundenen Übereinstimmungen der Gruppierungen werden von Python abgespeichert. Dadurch wird deren Wiederverwendung im gleichen regulären Ausdruck an
späterer Stelle ermöglicht. Dies bezeichnet man als Rückwärtsreferenzen (engl. back references). \n (n = 1, 2, 3, ... ) bezeichnet die n-te Gruppierung. Bevor wir jedoch mit Rückwärtsreferenzen weitermachen, wollen wir noch einen Paragraphen über Match-Objekte
einfügen, die wir im Folgenden benötigen.
36.13.1
Match-Objekte
Bisher waren wir immer nur daran interessiert, ob ein Ausdruck gepasst hatte oder nicht.
Wir nutzten die Tatsache, dass Python oder genauer die Methode re.search() ein MatchObjekt zurückliefert, wenn der reguläre Ausdruck gepasst hat, und ansonsten nur ein None. Uns interessierte bisher nicht, was gepasst hatte, also welcher Teilstring. Eine andere
Information wäre, wo der Match im String stattfand, also die Start- und die Endposition.
401
402
36 Reguläre Ausdrücke
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Ein match-Objekt enthält unter allem die Methoden group(), span(), start() und end(), die
man im folgenden Beispiel im selbsterklärenden Einsatz sieht:
>>> import re
>>> mo = re.search("[0-9]+", "Customer number: 232454, Date: February
12, 2013")
>>> mo.group()
'232454'
>>> mo.span()
(17, 23)
>>> mo.start()
17
>>> mo.end()
23
>>> mo.span()[0]
17
>>> mo.span()[1]
23
Diese Methoden sind nicht schwierig zu verstehen: span() liefert ein 2er-Tupel zurück, das
den Anfangs- und Endwert des Substrings enthält, auf den der reguläre Ausdruck passt. Für
den Anfangs- und Endwert gibt es noch zwei Funktionen start() und end(), wobei gilt, dass
span()[0] dem Wert von start() und span()[1] dem Wert von end() entspricht.
Wird group() ohne Argumente aufgerufen, liefert es den Substring zurück, der auf den RE
gepasst hat. Ruft man group mit einem Integer-Argument n auf, liefert es den Substring
zurück, auf den die n-te Gruppe gepasst hatte. Man kann group() auch mit mehr als einem
Integer-Wert aufrufen, z.B. group(n,m). Dann wird kein String zurückgeliefert, sondern ein
Tupel mit den Werten von group(n) und group(m). Also es gilt (group(n),group(m)) ist
gleich group(n,m):
>>> import re
>>> mo = re.search("([0-9]+).*: (.*)", "Customer number: 232454, Date:
February 12, 2013")
>>> mo.group()
'232454, Date: February 12, 2013'
>>> mo.group(1)
'232454'
>>> mo.group(2)
'February 12, 2013'
>>> mo.group(1,2)
('232454', 'February 12, 2013')
Ein sehr intuitives Beispiel stellt das Lesen von korrespondierenden schließenden Tags von
XML oder HTML dar. In einer Datei (z.B. „tags.txt”) steht folgender Inhalt:
<composer>Wolfgang Amadeus Mozart</composer>
<author>Samuel Beckett</author>
<city>London</city>
36.13 Gruppierungen und Rückwärtsreferenzen
Wir möchten diesen Text automatisch in folgendes Format umschreiben:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
composer: Wolfgang Amadeus Mozart
author: Samuel Beckett
city: London
Dies lässt sich mittels Python und regulären Ausdrücken mit folgendem Skript realisieren.
Der reguläre Ausdruck funktioniert wie folgt: Er versucht erst, das Symbol „<” zu finden.
Danach liest er eine Gruppe von Kleinbuchstaben, bis er auf das Größerzeichen „>” stößt.
Alles, was zwischen „<” und „>” steht, wird in einer Rückwärtsreferenz (back reference)
gespeichert, und zwar unter \1. Zuerst enthält \1 den Wert „composer”: Nachdem der Ausdruck das erste „>” erreicht hat, wird der reguläre Ausdruck so weiter ausgeführt, als hätte
er von Anfang an „<composer>(.*)</composer>” gelautet.
Das zugehörige Python-Skript:
import re
fh = open("tags.txt")
for i in fh:
res = re.search(r"<([a-z]+)>(.*)</\1>",i)
print(res.group(1) + ": " + res.group(2))
Wenn es mehr als ein Klammerpaar (runde Klammern) innerhalb eines regulären Ausdrucks gibt, dann sind die Rückwärtsreferenzen in der Reihenfolge der Klammern durchnummeriert: \1, \2, \3, ...
Übung:
Im nächsten Beispiel werden drei Rückwärtsreferenzen benutzt. Gegeben ist eine Telefonliste der Simpsons. Nicht jeder Eintrag enthält eine Telefonnummer, aber wenn eine Telefonnummer existiert, dann steht sie am Anfang des Strings. Dann folgt, getrennt durch ein
Leerzeichen, der Nachname. Durch ein Komma getrennt folgen dann Vornamen. Die Liste soll in der folgenden Form ausgegeben werden, d.h. zuerst der vollständige Name und
dann die Telefonnummer:
$ python3 simpsons_phone_book.py
Allison Neu 555-8396
C. Montgomery Burns
Lionel Putz 555-5299
Homer Jay Simpson 555-7334
Das folgende Python-Programm löst die Aufgabe:
import re
l = ["555-8396 Neu, Allison",
"Burns, C. Montgomery",
"555-5299 Putz, Lionel",
"555-7334 Simpson, Homer Jay"]
403
404
36 Reguläre Ausdrücke
for entry in l:
res = re.search(r"([0-9-]*)\s*([A-Za-z]+),\s+(.*)", entry)
print(res.group(3) + " " + res.group(2) + " " + res.group(1))
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
36.14
Umfangreiche Übung
In dieser Übung geht es darum, die Informationen aus zwei Listen, d.h. Dateien, unter
Benutzung von regulären Ausdrücken zusammenbringen. Die erste Datei beinhaltet über
13.000 Zeilen mit Postleitzahlen den zugehörigen Städtenamen sowie weiteren Informationen. Es folgen ein paar willkürlich ausgewählte Zeilen zur Verdeutlichung und zur Findung
von regulären Ausdrücken:
osm_id ort plz bundesland
1104550 Aach 78267 Baden-Württemberg
...
446465 Freiburg (Elbe) 21729 Niedersachsen
62768 Freiburg im Breisgau 79098 Baden-Württemberg
62768 Freiburg im Breisgau 79100 Baden-Württemberg
62768 Freiburg im Breisgau 79102 Baden-Württemberg
...
454863 Fulda 36037 Hessen
454863 Fulda 36039 Hessen
454863 Fulda 36041 Hessen
...
1451600 Gallin 19258 Mecklenburg-Vorpommern
449887 Gallin-Kuppentin 19386 Mecklenburg-Vorpommern
...
57082 Gärtringen 71116 Baden-Württemberg
1334113 Gartz (Oder) 16307 Brandenburg
...
2791802 Giengen an der Brenz 89522 Baden-Württemberg
2791802 Giengen an der Brenz 89537 Baden-Württemberg
...
Die andere Datei enthält eine Liste der 19 größten deutschen Städte. Jede Zeile enthält die
Position der Stadt, den Namen der Stadt, die Einwohnerzahl und das Bundesland. Was jedoch fehlt, ist die zugehörige Postleitzahl:
1.
2.
3.
4.
5.
6.
7.
8.
Berlin
3.382.169 Berlin
Hamburg
1.715.392 Hamburg
München
1.210.223 Bayern
Köln
962.884 Nordrhein-Westfalen
Frankfurt am Main 646.550 Hessen
Essen
595.243 Nordrhein-Westfalen
Dortmund
588.994 Nordrhein-Westfalen
Stuttgart
583.874 Baden-Württemberg
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
36.14 Umfangreiche Übung
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
Düsseldorf
Bremen
Hannover
Duisburg
Leipzig
Nürnberg
Dresden
Bochum
Wuppertal
Bielefeld
Mannheim
569.364
539.403
515.001
514.915
493.208
488.400
477.807
391.147
366.434
321.758
306.729
Nordrhein-Westfalen
Bremen
Niedersachsen
Nordrhein-Westfalen
Sachsen
Bayern
Sachsen
Nordrhein-Westfalen
Nordrhein-Westfalen
Nordrhein-Westfalen
Baden-Württemberg
Die Aufgabe besteht nun darin, die 19 größten Städte zusammen mit ihren Postleitzahlen
auszugeben. Die Dateien befinden sich in unserem Beispielverzeichnis unter den Namen
largest_cities_germany.txt und zuordnung_plz_ort.txt.
import re
with open("zuordnung_plz_ort.txt", encoding="utf-8") as fh_post_codes:
codes4city = {}
for line in fh_post_codes:
res = re.search(r"[\d ]+([^\d]+[a-z])\s(\d+)", line)
if res:
city, post_code = res.groups()
if city in codes4city:
codes4city[city].add(post_code)
else:
codes4city[city] = {post_code}
with open("largest_cities_germany.txt", encoding="utf-8") as
fh_largest_cities:
for line in fh_largest_cities:
re_obj = re.search(r"^[0-9]{1,2}\.\s+([\w\s-]+\w)\s+[0-9]",
line)
city = re_obj.group(1)
print(city, codes4city[city])
Die Ausgabe für obiges Programm sieht wie folgt aus. Allerdings haben wir die Ausgabe
gekürzt, d.h. wir haben alle außer den jeweils ersten drei Postleitzahlen pro Stadt rausgelöscht, um die Ausgabe klein zu halten:
$ python3 largest_cities_postcode.py
Berlin {'10715', '13158', '13187', ...}
München {'80802', '80331', '80807', ...}
Köln {'51065', '50997', '51067', ...}
Frankfurt am Main {'65934', '60529', '60308', ...}
Essen {'45144', '45134', '45309', ... }
Dortmund {'44328', '44263', '44369',...}
405
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
406
36 Reguläre Ausdrücke
Stuttgart {'70174', '70565', '70173', ...}
Düsseldorf {'40217', '40589', '40472', ...}
Bremen {'28207', '28717', '28777', ...}
Hannover {'30169', '30419', '30451', ...}
Duisburg {'47137', '47059', '47228', ...}
Leipzig {'4158', '4329', '4349', ...'}
Nürnberg {'90419', '90451', '90482', ...}
Dresden {'1217', '1169', '1324', ...}
Bochum {'44801', '44892', '44805', ...}
Wuppertal {'42109', '42119', '42287', ...}
Bielefeld {'33613', '33607', '33699', ...}
Mannheim {'68161', '68169', '68167', ...}
36.15
findall
Python oder besser das Modul re stellt noch eine weitere großartige Methode zur Verfügung, die sich Perl- und Java-Programmierer vergeblich wünschen:
re.findall(pattern, str[, flags])
findall liefert alle Übereinstimmungen des regulären Ausdrucks „pattern” im String „str” als
Liste zurück (zur Erinnerung: search() und match() liefern nur die erste Übereinstimmung
zurück). Der String „str” wird von links nach rechts gescannt, und die Einträge der Liste
entsprechen diesem Arbeitsablauf.
>>> import re
>>> t="A fat cat doesn't eat oat but a rat eats bats."
>>> mo = re.findall("[force]at", t)
>>> print(mo)
['fat', 'cat', 'eat', 'oat', 'rat', 'eat']
Falls eine oder mehrere Gruppen in dem regulären Ausdruck vorkommen, wird eine Liste
mit Tupels der einzelnen Gruppenergebnisse zurückgeliefert:
>>> import re
>>> items = re.findall("[0-9]+.*: .*", "Customer number: 232454, Date:
January 22, 2013")
>>> print(items)
['232454, Date: January 22, 2013']
>>> items = re.findall("([0-9]+).*: (.*)", "Customer number: 232454,
Date: January 22, 2013")
>>> print(items)
[('232454', 'January 22, 2013')]
36.16 Alternativen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
36.16
Alternativen
Zu Beginn unseres Kapitels hatten wir Zeichenklassen eingeführt. Zeichenklassen bieten
eine Auswahl aus einer Menge von Zeichen. Manchmal benötigt man etwas Analoges für
verschiedene Teilausdrücke. Also die Wahl zwischen verschiedenen Ausdrücken. Dazu verwendet man das Symbol „|”, da es sich um ein logisches „Oder” handelt. Im folgenden
Beispiel prüfen wir, ob in einem String die Städte London, Paris, Zürich oder Strasbourg
vorkommen, und zwar nach einem vorausgegangenen Wort „destination”:
>>> import re
>>> str = "The destination is London!"
>>> mo = re.search(r"destination.*(London|Paris|Zurich|Strasbourg)",
str)
>>> if mo: print(mo.group())
...
destination is London
Wer das letzte Beispiel für zu künstlich und nicht praxisnah genug hält, für den oder die
haben wir hier ein weiteres Beispiel. Nehmen wir an, wir wollen unsere E-Mails filtern. Wir
möchten die gesamte Korrespondenz mit Guido van Rossum, dem Schöpfer und Designer
von Python, finden. Der folgende reguläre Ausdruck dürfte dann recht hilfreich sein:
r"(^To:|^From:) (Guido|van Rossum)"
Dieser Ausdruck passt auf alle Zeilen, die entweder mit „To:” oder mit „From:” beginnen
und von einem Leerzeichen gefolgt werden, denen dann entweder der Vorname „Guido”
oder der Nachname „van Rossum” folgt.
36.17
Compilierung von regulären
Ausdrücken
Falls man denselben regulären Ausdruck mehrmals in seinem Skript verwenden will, ist
es eine gute Idee, den Ausdruck zu compilieren. Die allgemeine Syntax der compile()Funktion:
re.compile(patter[, flags])
compile lieferte ein regex-Objekt zurück, das man später zum Suchen und Ersetzen verwenden kann. Das Verhalten des Ausdrucks kann man mit Flag-Werten modifizieren.
Schauen wir uns dies in einem Beispiel an:
>>> import re
>>> ce = re.compile(r"01+")
>>> ce
<_sre.SRE_Pattern object at 0xb72ac440>
407
408
36 Reguläre Ausdrücke
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Tabelle 36.2 Flags für compile
Flag
Beschreibung
re.I, re.IGNORECASE
Groß- und Kleinschreibung wird nicht mehr unterschieden.
re.L, re.LOCALE
Das Verhalten von bestimmten Zeichenklassen wie z.B.
\w , \W , \b ,\s , \S wird von der eingestellten
Sprachumgebung (locale) abhängig gemacht.
re.M, re.MULTILINE
^ und $ passen defaultmäßig nur auf den Anfang und das Ende
eines Strings. Mit diesem Flag passen sie auch im Innern eines
Strings vor und nach einem Newline „\n”.
re.S, re.DOTALL
„.” passt dann auf alle Zeichen plus dem Newline „\n”.
re.U, re.UNICODE
\w , \W , \b , \B , \d , \D , \s , \S werden von
re.X, re.VERBOSE
Ermöglicht „wortreiche” (verbose) reguläre Ausdrücke, d.h.
Leerzeichen werden ignoriert. Das bedeutet, dass Leerzeichen,
Tabs, Wagenrücklauf „\c” usw. nicht als solche gematcht werden.
Wenn man ein Leerzeichen im Verbose-Modus matchen will, muss
man es mittels Backslash schützen (escape) oder es in eine
Zeichenklasse packen. # wird auch ignoriert, außer wenn dieses
Zeichen in einer Zeichenklasse oder hinter einem Backslash steht.
Alles hinter einem „#” wird bis zum Ende einer Zeile als Kommentar
ignoriert.
Unicode-Einstellungen abhängig.
Auf dem SRE_Pattern-Objekt ce kann man nun die Methode search anwenden, die nur
einen String braucht, auf dem versucht wird, das zuvor compilierte Pattern zu matchen:
>>> x = ce.search("Eine Binärzahl: 00111")
>>> x
<_sre.SRE_Match object at 0xb71af0c8>
>>> x.span()
(17, 21)
>>> x.group()
'0111'
Das SRE_Pattern-Objekt kennt auch eine Methode findall5 , wie wir im folgenden Beispiel
sehen:
>>> import re
>>> ce = re.compile(r"01+")
>>> x = ce.search("Eine Binärzahl: 00111 und noch eine 01011")
>>> x.findall()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: '_sre.SRE_Match' object has no attribute 'findall'
>>> x = ce.findall("Eine Binärzahl: 00111 und noch eine 01011")
>>> x
['0111', '01', '011']
5
ebenso wie match, split sub und andere
36.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Compilierte reguläre Ausdrücke sparen in der Regel nicht viel Rechenzeit, weil Python sowieso automatisch reguläre Ausdrücke compiliert und zwischenspeichert, auch wenn man
sie mit re.search() oder re.match() aufruft. Ein guter Grund für ihre Verwendung besteht
darin, die Definition und die Benutzung von regulären Ausdrücken zu trennen.
36.18
36.18.1
Aufspalten eines Strings mit oder
ohne regulären Ausdruck
split-Methode der String-Klasse
Es gibt eine String-Methode split(), mit deren Hilfe man
einen String in Teilstrings aufspalten kann.
str.split([sep[, maxsplit]])
Wenn das optionale Argument von split fehlt oder None
ist, werden alle Teilstrings, die aus Leerräumen (Whitespaces) bestehen, als Separatoren benutzt. Den zweiten
Parameter werden wir später besprechen.
Bild 36.5 Splitting Strings
Wir demonstrieren dieses Verhalten mit einem Zitat von Abraham Lincoln:
>>> law_courses = "Let reverence for the laws be breathed by every
American mother to the lisping babe that prattles on her lap. Let
it be taught in schools, in seminaries, and in colleges. Let it be
written in primers, spelling books, and in almanacs. Let it be
preached from the pulpit, proclaimed in legislative halls, and
enforced in the courts of justice. And, in short, let it become
the political religion of the nation."
>>> law_courses.split()
['Let', 'reverence', 'for', 'the', 'laws', 'be', 'breathed', 'by', '
every', 'American', 'mother', 'to', 'the', 'lisping', 'babe', '
that', 'prattles', 'on', 'her', 'lap.', 'Let', 'it', 'be', 'taught
', 'in', 'schools,', 'in', 'seminaries,', 'and', 'in', 'colleges
.', 'Let', 'it', 'be', 'written', 'in', 'primers,', 'spelling', '
books,', 'and', 'in', 'almanacs.', 'Let', 'it', 'be', 'preached',
'from', 'the', 'pulpit,', 'proclaimed', 'in', 'legislative', '
halls,', 'and', 'enforced', 'in', 'the', 'courts', 'of', 'justice
.', 'And,', 'in', 'short,', 'let', 'it', 'become', 'the', '
political', 'religion', 'of', 'the', 'nation.']
Wir schauen uns nun einen String an, der von seinem Aufbau von Excel oder von Calc (OpenOffice) kommen könnte. In unserem vorigen Beispiel hatten wir gesehen, dass standardmäßig Leerräume (Whitespaces) als Separatoren genommen werden. Im folgenden kleinen Beispiel wollen wir das Semikolon als Separator nehmen. Dazu müssen wir lediglich
„;” als Argument beim Aufruf übergeben:
409
410
36 Reguläre Ausdrücke
>>> line = "James;Miller;teacher;Perl"
>>> line.split(";")
['James', 'Miller', 'teacher', 'Perl']
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wie bereits erwähnt, hat die Methode split() noch einen weiteren optionalen Parameter:
maxsplit.
Falls maxsplit angegeben wird, dann wird der String an maximal „maxsplit” Separatoren
zerlegt, d.h. die Ergebnisliste besteht aus höchstens „maxsplit + 1” Elementen. Wir demonstrieren die Wirkungsweise von maxsplit mit einer Definition des Begriffs Mammon
aus dem „Devil’s Dictionary” von Ambrose Bierce:
>>> mammon = "The god of the world's leading religion. The chief
temple is in the holy city of New York."
>>> mammon.split(" ",3)
['The', 'god', 'of', "the world's leading religion. The chief temple
is in the holy city of New York."]
Wir benutzten im vorigen Beispiel ein Leerzeichen als Separator, was ein Problem darstellen kann, wenn mehrere Leerzeichen (irgendwelche Leerzeichen, also auch Tabs) hintereinander stehen. In diesem Fall wird split() nach jedem Leerzeichen den String aufsplitten,
und dadurch erhalten wir in unserer Ergebnisliste leere Strings und Strings, die nur ein „\t”
enthalten:
>>> mammon = "The god \t of the world's leading religion. The chief
temple is in the holy city of New York."
>>> mammon.split(" ",5)
['The', 'god', '', '\t', 'of', "the world's leading religion. The
chief temple is in the holy city of New York."]
>>>
Die leeren Strings können wir verhindern, indem wir None als erstes Argument, also für
den Separator, benutzen. Dann arbeitet split() mit der Standardeinstellung, also so, als
wenn man keinen Trennstring angegeben hat:
>>> mammon.split(None,5)
['The', 'god', 'of', 'the', "world's", 'leading religion. The chief
temple is in the holy city of New York.']
36.18.2
split-Methode des re-Moduls
In den meisten Fällen genügt die split-Methode aus dem str-Modul voll und ganz. Aber wie
sieht es beispielsweise aus, wenn man nur die reinen Wörter eines Texts herausfiltern will?
Wenn man also alles herausfiltern will, was nicht Buchstaben entspricht. Dann benötigen
wir eine Split-Funktion, die die Angabe von regulären Ausdrücken zur Definition von Separatoren zulässt. Das Modul re bietet eine solche Methode, die ebenfalls split() heißt. Wir
erläutern die Arbeitsweise mit re.split() in einem kurzen Text, dem Beginn der Metamorphosen von Ovid:
36.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> import re
>>> metamorphoses = "Of bodies chang'd to various forms, I sing: Ye
Gods, from whom these miracles did spring, Inspire my numbers with
coelestial heat;"
>>> re.split("\W+",metamorphoses)
['Of', 'bodies', 'chang', 'd', 'to', 'various', 'forms', 'I', 'sing',
'Ye', 'Gods', 'from', 'whom', 'these', 'miracles', 'did', 'spring
', 'Inspire', 'my', 'numbers', 'with', 'coelestial', 'heat', '']
Seine volle Wirkungskraft entfaltet split im nachfolgenden Beispiel. Als Beispieltext verwenden wir eine ironische Definition von Logik, wie sie Ambrose Bierce in seinem berühmten „Devil’s Dictionary” vorschlägt:
import re
logik = """Logik: Die Kunst des Denkens und Schlussfolgerns in
strenger Übereinstimmung mit den Beschränkungen und Unfähigkeiten
des menschlichen Missverständnisses. Die Grundlage der Logik ist
der Syllogismus (logischer Schluss), der aus einem Obersatz, einem
Untersatz und einer Konklusion (Schlussfolgerung) besteht - somit
:
Obersatz: Sechzig Männer können eine Arbeit sechzig mal so schnell
machen wie einer.
Untersatz: Ein Mann kann ein Pfostenloch in sechzig Sekunden graben;
deswegen:
Konklusion: Sechzig Männer können ein Pfostenloch in einer Sekunde
graben."""
definitions = re.split("\w+: ", logik)[1:]
print("Definition von Logik: \n" + definitions[0])
print("Beispiel für einen Obersatz: \n" + definitions[1])
print("Beispiel für einen Untersatz: \n" + definitions[2])
print("Konklusion: \n" + definitions[3])
Wenn man sich den String logik anschaut, sieht man, dass jede Definition bzw. die Beispiele
für den Obersatz und den Untersatz sowie die Konklusion durch den jeweiligen Begriff,
gefolgt von einem Doppelpunkt, eingeleitet werden. Wir können also als Separator den
regulären Ausdruck "\w+:"verwenden.6
Wir haben auf re.split("\w+: ", logik) den Slice-Operator [1:] angewendet, weil der
erste Eintrag der Ergebnisliste von re.split("\w+: ", logik) ein leerer String ist, denn
vor dem ersten Separator „Logik:” steht kein Zeichen.
6
Die Definitionen stammen von Ambrose Bierce, dem Autor von „The Devil’s Dictionary”. Übersetzung
stammt von der Webseite „Logik wider alle Vernunft” (http://www.klein-singen.de/logik/) von Bernd Klein.
411
412
36 Reguläre Ausdrücke
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Ausgabe des obigen Skripts sieht wie folgt aus:
$ python3 split_logics.py
Definition von Logik:
Die Kunst des Denkens und Schlussfolgerns in strenger Übereinstimmung
mit den Beschränkungen und Unfähigkeiten des menschlichen
Missverständnisses. Die Grundlage der Logik ist der Syllogismus (
logischer Schluss), der aus einem Obersatz, einem Untersatz und
einer Konklusion (Schlussfolgerung) besteht - somit:
Beispiel für einen Obersatz:
Sechzig Männer können eine Arbeit sechzig mal so schnell machen wie
einer.
Beispiel für einen Untersatz:
Ein Mann kann ein Pfostenloch in sechzig Sekunden graben; deswegen:
Konklusion:
Sechzig Männer können ein Pfostenloch in einer Sekunde graben.
36.18.3
Wörter filtern
Wir wollen nun die Wörter eines Texts herausfiltern, also ohne Satzzeichen und Sonderzeichen.
Betrachten wir folgenden String aus Shakespeares Hamlet:
>>>
>>>
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
import re
t = """HORATIO
Where, my lord?
HAMLET
HORATIO
In my mind's eye, Horatio.
I saw him once; he was a goodly king.
HAMLET
He was a man, take him for all in all,
I shall not look upon his like again.
HORATIO
My lord, I think I saw him yesternight.
HAMLET
Saw? who?
HORATIO
My lord, the king your father.
HAMLET
The king my father!"""
36.19 Suchen und Ersetzen mit sub
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Mithilfe von re.split() können wir eine Liste aller Wörter erzeugen:
>>> re.split(r"\W+",t)
['HORATIO', 'Where', 'my', 'lord', 'HAMLET', 'In', 'my', 'mind', 's',
'eye', 'Horatio', 'HORATIO', 'I', 'saw', 'him', 'once', 'he', 'was
', 'a', 'goodly', 'king', 'HAMLET', 'He', 'was', 'a', 'man', 'take
', 'him', 'for', 'all', 'in', 'all', 'I', 'shall', 'not', 'look',
'upon', 'his', 'like', 'again', 'HORATIO', 'My', 'lord', 'I', '
think', 'I', 'saw', 'him', 'yesternight', 'HAMLET', 'Saw', 'who',
'HORATIO', 'My', 'lord', 'the', 'king', 'your', 'father', 'HAMLET
', 'The', 'king', 'my', 'father', '']
Wir können uns auch sehr leicht die Menge der Wörter, also ohne mehrfache Vorkommen
erzeugen:
>>> words = { x.lower() for x in re.split(r"\W+",t)}
>>> len(words)
36
>>> words
{'', 'all', 'mind', 'hamlet', 'in', 'saw', 'your', 'again', 'eye', '
for', 'horatio', 'father', 'who', 'take', 'lord', 'he', 'was', '
his', 'shall', 'yesternight', 'upon', 'like', 'goodly', 'not', '
him', 'man', 'a', 'king', 'look', 'i', 'my', 's', 'the', 'where',
'think', 'once'}
36.19
Suchen und Ersetzen mit sub
re.sub(regex, replacement, subject)
Jede Übereinstimmung (match) des regulären Ausdrucks „regex” wird durch den String „replacement” ersetzt.
Beispiel:7
>>> import re
>>> str = "yes I said yes I will Yes."
>>> res = re.sub("[yY]es","no", str)
>>> print(res)
no I said no I will no.
7
Der im Beispiel verwendete String entspricht den letzten Worten des Romans „Finnegans Wake” von James
Joyce.
413
414
36 Reguläre Ausdrücke
36.20
Aufgaben
1. Aufgabe:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Welche der folgenden regulären Ausdrücke (a), . . . (g) passen auf welche Strings von
(1), . . . (6):
(1)
(2)
(3)
(4)
(5)
(6)
abc
ac
abbb
bbc
aabcd
b
(a)
(b)
(c)
(d)
(e)
(f)
(g)
ab+c?
a?b*c
b+c*
^b+c*$
a.+b?c
b{2,}c?
^a{1,2}b+.?d*
Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 512
2. Aufgabe:
Was matcht x(xy)*x?
(1)
(2)
(3)
(4)
(5)
xx
xyxyxyx
xxyxy
xyxx
xxyxyx
Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 512
3. Aufgabe:
Schreiben Sie einen Ausdruck, der US-Zip-Codes prüft. Ein Zip-Code besteht entweder aus 9 Zeichen, z.B. 205000001, oder 5 Ziffern, gefolgt von einem Strichpunkt,
gefolgt von 4 Ziffern, z.B. 20500-0001 (president), 20500-0002 (first lady). Außerdem
ist es auch zulässig, dass ein Zip-Code nur aus 5 Ziffern besteht, also z.B. 20500
(Washington DC)
Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 513
4. Aufgabe:
Finden Sie heraus, wie viele verschiedene Wörter in dem Roman „Ulysses” von James Joyce mit „ship” enden. Sie finden den Roman in unserem Beispielverzeichnis
unter dem Namen „ulysses.txt”.
Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 514
36.20 Aufgaben
5. Aufgabe:
Finden Sie in „Ulysses” alle Wörter, die aus zwei gleichen Teilwörtern bestehen, wie
beispielsweise „dumdum” oder „poohpooh”.
Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 514
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
6. Aufgabe:
Auch in dieser Aufgabe bleiben wir bei der Weltliteratur. Das längste Wort in „Ulysses”
ist übrigens das Wort „Nationalgymnasiummuseumsanatoriumandsuspensoriumsordinaryprivatdocent”. Damit hat dieses Wort noch zwei Buchstaben mehr als das Wort
„Grundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung”.
Berechnen Sie die Häufigkeitsverteilungen von Wortlängen in einem Text ohne Berücksichtigung von Groß- und Kleinschreibung.
Zur Erläuterung:
Besteht der Text nur aus dem String „Ein Wort ist nur ein Wort.”, dann haben wir folgende Verteilung:
4 Wörter der Länge 3
2 Wörter der Länge 4
Man könnte eine Häufigkeitsverteilung auch auf die verschiedenen Wörter anwenden,
dann sieht die Verteilung so aus:
1 Wort der Länge 4: „Wort”
3 Wörter der Länge 3: „ein”, „nur”, „ist”
Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 515
7. Aufgabe:
Schreiben Sie ein Programm, welches alle Wörter in dem Roman Ulysses findet, deren Buchstaben in aufsteigender Reihenfolge sortiert sind und mindestens 5 Buchstaben lang sind. Also zum Beispiel „bellow”, da „b” <= „e” <= „l” <= „l” <= „o” <= „w” gilt.
Weitere Beispiele sind „booty”, „accept” und „effort”. Alle Großbuchstaben sollen zuvor
in Kleinbuchstaben gewandelt werden.
Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 517
415
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
37
37.1
Typanmerkungen
Einführung
Mit Python 3.5 und 3.6 wurden die sogenannten Typhinweise (engl. type hints) in
Python eingeführt.1
Die Einführung der Typhinweise bedeutet
aber keine Abkehr von den Prinzipien von
Python. Python bleibt weiterhin eine dy- Bild 37.1 type annotations
namisch getypte Sprache. Außerdem sollen Typhinweise niemals verpflichtend werden,
noch nicht einmal per Konvention!2
Betrachten wir folgende Funktion:
def f(x, y):
return x * y
Schaut man sich die Definition von f an, so denkt man wohl in erster Linie an eine Funktion, die zwei numerische Argumente erwartet. Diese Funktion lässt sich aber auch mit einer
Vielzahl von anderen Typkombinationen aufrufen, ohne dass eine Ausnahme erzeugt wird.
>>> def f(x, y):
...
return x * y
...
>>>
>>> f(3, 4)
12
>>> f(3.69, 11.39)
42.0291
>>>
>>> f(25, "-")
1
2
Bitte beachten Sie, dass die Beispiele dieses Kapitels nur funktionieren, wenn Sie mindestens Python 3.6
installiert haben. Manche Beispiele funktionieren auch bereits mit Python 3.5.
„It should also be emphasized that Python will remain a dynamically typed language, and the authors have
no desire to ever make type hints mandatory, even by convention.”, PEP484, 29.09.2004, Guido van Rossum,
Jukka Lehtosalo und Łukasz Langa
418
37 Typanmerkungen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
'-------------------------'
>>> f("-", 25)
'-------------------------'
>>>
>>> f([3, 5], 2)
[3, 5, 3, 5]
Ruft man die Funktion aber beispielsweise mit zwei Listen oder zwei Strings auf, erhebt
man eine Ausnahme.
Es ist natürlich ganz schlechter Stil, eine Funktion ohne Docstring zu definieren. In einem
Docstring erhält man normalerweise wichtige Informationen über die möglichen Typen
der Eingabeparameter und des Typs des Ausgabeobjekts der Funktion. Schauen wir uns
beispielsweise help von der Funktion sample des Moduls random an:
sample(population, k) method of random.Random instance
Chooses k unique random elements from a population sequence or set
.
Returns a new list containing elements from the population
while leaving the original population unchanged.
...
Mittels obiger help-Information wissen wir, dass das Argument population ein sequentieller Datentyp oder eine Menge sein darf. Bei k können wir logisch schließen, dass es sich
um eine Integer-Zahl handeln muss, genaugenommen eine Integer-Zahl zwischen 0 und
der Anzahl der Elemente von population.
Bei Sprachen mit statischer Typdeklaration wird bereits mit der Funktionsdeklaration klargestellt, welche Typen diese erwartet.
Viele sahen das Fehlen von statischen Typdeklaration als Schwachpunkt von Python.
Die „Type Annotations” sind eine Möglichkeit, Typüberprüfungen schon vor der Laufzeit
durchzuführen. Andererseits haben sie keine Auswirkungen, während ein Programm läuft,
d.h. es erfolgen keine Typüberprüfungen zur Laufzeit. Es können also keine neuen Laufzeitfehler entstehen. Die Typdeklarationen werden vom Python-Compiler und Interpreter
absichtlich komplett ignoriert! Dies bedeutet, dass man Python-Programme weiterhin
ohne Typanmerkungen schreiben kann.
Die Typüberprüfung wird von speziellen Tools, z.B. mypy, vor der Ausführung eines Programms durchgeführt.
37.2
Einfaches Beispiel
Beginnen wir mit einem einfachen Beispiel. Das Programm mypy ist nicht Bestandteil der
Python-Installation, d.h. es muss also installiert werden. Wir können dies mit
python3 -m pip install mypy
tun.
37.3 Variablenanmerkungen
Im folgenden Programm haben wir die Funktion f mit Typanmerkungen versehen. Das
erste Argument von f muss nun ein Objekt vom Typ int sein und das zweite vom Typ str.
Mit der Notation -> str weisen wir f auch einen Ergebnistyp str zu:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def f(x: int, y: str) -> str:
return x * y
print(f(3, 4))
print(f(3.69, 11.39))
print(f(25, "-"))
print(f("-", 25))
Starten wir obiges Programm mit python3 type_hints.py, erhalten wir keine Fehlermeldungen, sondern die folgenden Ausgaben:
12
42.0291
-------------------------------------------------
Das Programm wurde so ausgeführt, als gäbe es keine Typanmerkungen. Sie werden also
von Python wie Kommentare betrachtet.3
Mittels mypy können wir jedoch die falschen Typzuweisungen finden:
bernd@marvin $ mypy type_hints_variables.py
function_with_hints.py:5: error: Argument 2
type "int"; expected "str"
function_with_hints.py:6: error: Argument 1
type "float"; expected "int"
function_with_hints.py:6: error: Argument 2
type "float"; expected "str"
function_with_hints.py:8: error: Argument 1
type "str"; expected "int"
function_with_hints.py:8: error: Argument 2
type "int"; expected "str"
37.3
to "f" has incompatible
to "f" has incompatible
to "f" has incompatible
to "f" has incompatible
to "f" has incompatible
Variablenanmerkungen
Auch für Variablen kann ein Typ mittels Anmerkungen deklariert werden. Hierbei gibt es
aber erhebliche syntaktische Unterschiede zwischen Python 3.5 und Python 3.6.
In Python 3.5 erfolgt die Deklaration mittels eines Kommentars, der hinter einer Variablendefinition steht:
3
Startet man das Programm allerdings mit einer Python-Version kleiner als 3.5, erhalten wir Fehlermeldungen wegen der Syntax der Typanmerkungen, aber nicht wegen der Typinkompatibilität!
419
420
37 Typanmerkungen
x = 3 # type: int
In Python 3.6 haben wir eine natürlichere Syntax. Im folgenden Beispiel deklarieren wir zuerst eine Variable vom Typ Integer, und dann weisen wir ihr in einer separaten Anweisung
den Wert 42 zu. Die Variable y wird gleichzeitig deklariert und definiert:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
x : int
x = 42
y : int = 17
Unten stehendes Programm funktioniert, wenn wir es in einer Python-Version größer oder
gleich 3.6 starten. Die Typanmerkungen werden auch in diesem Fall wie Kommentare behandelt.
x : int
x = 42
x = "Hallo"
Die Typfehler können wir jedoch finden, wenn wir mypy mit dem Programm starten:
bernd@marvin $ mypy type_hints_variables.py
type_hints_variables.py:5: error: Incompatible types in assignment (
expression has type "str", variable has type "int")
37.4
Listenbeispiel
Im nächsten Beispiel definieren wir eine Funktion silly_merge, die aus zwei Listen zwei
zufällige, ab dem ersten Element beginnende Teillisten erzeugt und diese konkateniert.
import random
def silly_merge(x : list, y : list) -> list:
endx = random.randint(0, len(x))
endy = random.randint(0, len(y))
return x[:endx] + y[:endy]
print(silly_merge([23, 27, 29, 21, 19],
[101, 134, 156, 199]))
print(silly_merge([23, "aus der Reihe tanzen", 19],
[101, 134, 156, 199]))
Wie wir in unserem Beispiel sehen können, kann eine Liste vom Typ list beliebige Datentypen enthalten, so wie Listen auch definiert sind. Oft sollen Listen jedoch homogene
Inhalte enthalten, also zum Beispiel nur Integer-Zahlen. Dann wäre der zweite Aufruf von
silly_merge nicht korrekt, da die erste Liste einen String enthält. Wir möchten nicht nur
deklarieren können, dass eine Variable vom Typ Liste ist, sondern auch den – homogenen
– Inhalt der Liste bestimmen können.
37.5 typing-Modul
Dieses Problem können wir mit dem typing-Modul lösen, welches wir im folgenden Kapitel
einführen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
37.5
typing-Modul
Das typing-Modul wurde eingeführt, um komplexere Datentypdeklarationen zu ermöglichen.4
Mit Hilfe des typing-Moduls können wir beispielsweise auch homogene Listen definieren,
also Listen, die nur aus einem Datentyp bestehen.
Unser silly_merge-Beispiel können wir nun wie folgt umschreiben:
import random
from typing import List
def silly_merge(x: List[int], y: List[int]) -> List[int]:
endx = random.randint(0, len(x))
endy = random.randint(0, len(y))
return x[:endx] + y[:endy]
print(silly_merge([23, 27, 29, 21, 19],
[101, 134, 156, 199]))
print(silly_merge([23, "aus der Reihe tanzen", 19],
[101, 134, 156, 199]))
Aus dem typing-Modul haben wir den Deklarationstyp List importiert, den wir nicht mit
list verwechseln dürfen. List[int] deklariert eine Liste, die nur Integer enthalten darf.
Rufen wir nun mypy mit obigem Programm5 auf, so erhalten wir folgende Fehlermeldung:
bernd@marvin $ mypy type_hints2.py
type_hints2.py:12: error: List item 1 has incompatible type "str"
Man kann auch Typaliasse anlegen, wie wir im folgenden Beispiel mit Umsaetze demonstrieren:
from typing import List
Umsaetze = List[float]
def umrechnen(umsatz_liste : Umsaetze, faktor : float) -> Umsaetze:
4
5
In der offiziellen Dokumentation befindet sich die Bemerkung: „Das typing-Modul wurde in die StandardLibrary auf vorläufiger Basis eingeführt. Neue Features können hinzugefügt werden und die Benutzerschnittstelle könnte sich auch bei Nebenversionsnummern ändern, falls dies den Entwicklern als notwendig erscheint.” (englisches Original: „The typing module has been included in the standard library on a provisional basis. New features might be added and API may change even between minor releases if deemed
necessary by the core developers.”
unter der Annahme, dass es unter type_hints2.py gespeichert ist.
421
422
37 Typanmerkungen
ergebnis_liste = []
for umsatz in umsatz_liste:
ergebnis_liste.append(umsatz*faktor)
return ergebnis_liste
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
print(umrechnen([312.9, 543.9, 100.45, 98.99], 1.18))
Der Tupel-Typ des typing-Moduls erlaubt es, inhomogene Datenstrukturen zu deklarieren. Im folgenden Beispiel schreiben wir eine Typdeklaration für eine Liste mit Vierertupel.
Die Liste soll einen Einkauf darstellen. Jedes Element von Einkauf ist ein Vierertupel mit
Anzahl, Artikelnummer, Artikeltext und Preis.
from typing import List, Tuple
Artikel = Tuple[int, str, float]
Einkauf = List[Artikel]
def aufsummieren(warenkorb : Einkauf) -> float:
total = sum( anz * preis for anz, nr, txt, preis in warenkorb )
return total
korb = [ (2, 68978, "Orangen", 3.98),
(1, 70876, "Milch", 1.19),
(3, 87866, "Mineralwasser", 3.49)]
print(aufsummieren(korb))
Als Nächstes zeigen wir, wie man Signaturen von Callables bzw. Funktionen deklariert.
Bei unserem ersten Beispiel handelt es sich um die Funktion polynomial_creator, die
wir in Kapitel Dekorateure, Seite 345, eingeführt haben. Wir schreiben sie nun mit entsprechenden Typanmerkungen:
from typing import Callable, Union
p1 : Callable[[float], float]
p2 : Callable[[float], float]
def polynomial_creator(a: float,
b: float,
c: float) -> Callable[[float], float]:
def polynomial(x: float) -> float:
return a * x**2 + b * x + c
return polynomial
p1 = polynomial_creator(2, 3, -1)
p2 = polynomial_creator(-1, 2, 1)
for x in range(-2, 2, 1):
print(x, p1(x), p2(x))
37.5 typing-Modul
Wir können auch Dekorateure so wie unseren memoize-Dekorator aus dem Kapitel Dekorateure, Seite 359 mit Typanmerkungen versehen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
from typing import Callable, Any, Dict
def memoize(f: Callable[[Any], Any] ) -> Callable[[Any], Any]:
memo : Dict[Any, Any]
memo = {}
def helper(x : Any) -> Any:
if x not in memo:
memo[x] = f(x)
return memo[x]
return helper
@memoize
def fib(n: int) -> int:
if n <= 1:
return n
else:
return fib(n-1) + fib(n-2)
Der Datentyp Any steht für einen beliebigen Datentyp, also beispielsweise int, float und
so weiter.
423
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
38
38.1
Systemprogrammierung
Systemprogrammierung
Unter Systemprogrammierung versteht man die Programmierung von Software-Komponenten, die Teil des Betriebssystems sind oder die eng mit diesem bzw. mit der darunter liegenden Hardware kommunizieren müssen. Systemnahe Software wie beispielsweise das sys-Modul in Python fungiert als Abstraktionsschicht zwischen der Anwendung,
dem Python-Programm und dem Betriebssystem. Mithilfe dieser Abstraktionsschicht lassen sich Applikationen plattformunabhängig implementieren, auch wenn sie direkt auf Betriebssystemfunktionalitäten zugreifen.
Python eignet sich besonders gut für die Systemprogrammierung, denn auch hier gelten
die Vorteile von Python:
■
einfach und übersichtlich
■
gute Strukturierbarkeit
■
hohe Flexibilität
38.2
Shell
Shell ist ein Begriff, der viel benutzt und
doch oft unterschiedlich oder auch falsch
verstanden wird. Shell kommt aus dem
Englischen und bedeutet Schale oder Hülle, beispielsweise die Schale eines PythonEis. Die Funktion einer Eierschale besteht
in erster Linie im Schutz des Inneren. Sie
trennt und schützt das Innere vom Äußeren. So trennt auch die Shell den Kern des
Betriebssystems vom Shell-Benutzer und
gibt ihm robuste und einfach zu bedienen- Bild 38.1 Shells und Schalen
de Funktionen an die Hand, damit er nicht
die komplizierten und fehleranfälligen Betriebssystemfunktionen benutzen muss. Generell unterscheidet man bei Betriebssystemen zwei Arten von Shells:
426
38 Systemprogrammierung
■
die Kommandozeile (CLI) und
■
die grafischen Benutzeroberflächen (GUI)
Meistens wird der Begriff jedoch als Synonym für den Kommandozeileninterpreter (CLI)
benutzt, so wie z.B. die Bourne-Shell, C-Shell oder Bash unter Linux und Unix.
In unserem Fall gibt es zwei Gründe, von Shells zu sprechen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
■
Die Module shutil, os, sys, die wir in diesem Kapitel besprechen, kennen wir aus den
Shells der Betriebssysteme, also beispielsweise von der Bash-Shell von Unix/Linux.
Andererseits bilden die genannten Module ihrerseits im oben genannten Sinne Shells.
Sie sind gewissermaßen eine Abstraktionsschicht über den Betriebssystemfunktionen,
um Python betriebssystemunabhängig zu machen.
38.3
os-Modul
Das os-Modul ist das wichtigste Modul zur Interaktion mit dem Betriebssystem und ermöglicht durch abstrakte Methoden ein plattformunabhängiges Programmieren. Gleichzeitig ist es auch mit Funktionen wie os.system() oder der exec*()-Funktionsfamilie möglich, betriebssystemabhängige Programmteile einzubauen. Das os-Modul bietet viele unterschiedliche Methoden wie beispielsweise zum Zugriff auf das Dateisystem.
Mithilfe des os-Moduls kann man beispielsweise die Zugriffsrechte einer Datei abfragen.
Allerdings geht dies nicht direkt. Man ruft zuerst die Funktion stat aus dem os-Modul auf.
Diese liefert ein 10-Tupel mit verschiedenen Dateimetadaten zurück. Die erste Komponente liefert die Zugriffsmaske als Integer. Wenn wir diese in eine Oktalzahl wandeln, erhalten
wir die unter Linux gewohnte Sichtweise, d.h. „644” in der letzten Zeile des folgenden Beispiels:
>>> import os
>>> info = os.stat("abc.py")
>>> type(info)
<class 'posix.stat_result'>
>>> info
posix.stat_result(st_mode=33188, st_ino=63253, st_dev=2055, st_nlink
=1, st_uid=1000, st_gid=1000, st_size=17, st_atime=1361559201,
st_mtime=1361455357, st_ctime=1361455357)
>>> info.st_ino
63253
>>> info.st_ino is info[1]
True
38.3 os-Modul
38.3.1
Vorbemerkungen
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Warnung:
Wir hatten bereits früher erwähnt, dass es sehr schlechter Stil ist, Module mit Sternchen
zu importieren, da gegebenenfalls Variablen und Funktionen überschrieben werden können. Importiert man os mittel „from os import *”, wird die eingebaute Funktion open()
durch os.open() überschrieben, die ein komplett anderes Verhalten hat. So führt dann beispielsweise die folgende, ansonsten korrekte Anweisung zum Öffnen einer Datei zu einer
Fehlermeldung:
>>> from os import *
>>> fh = open("abc.py")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: open() takes at least 2 arguments (1 given)
Für die im Folgenden beschriebenen Funktionen des os-Moduls gilt:
■
■
■
Alle Funktionen, die einen Pfad- oder Dateinamen als Argument haben, akzeptieren sowohl Byte- als auch String-Objekte. Das Ergebnis ist dann vom gleichen Typ, falls wieder
ein Pfad- oder Dateinamen zurückgeliefert wird.
Falls nicht anders vermerkt, sind alle Funktionen, die unter Unix laufen, auch unter dem
Mac OS X lauffähig.
Alle Funktionen dieses Moduls erzeugen einen OSError, falls ungültige oder nicht verfügbare Datei- oder Pfadnamen oder andere Argumente, die nicht vom Betriebssystem
akzeptiert werden, übergeben werden.
38.3.2
Umgebungsvariablen
os.environ ist ein Dictionary, das alle Umgebungsvariablen enthält. Wir können wie üblich darauf zugreifen. Beispielsweise liefert uns os.environ["USER"] den aktuellen Namen des Benutzers:
>>> os.environ["USER"]
'bernd'
Man kann dies natürlich auch über die im Folgenden beschriebene Funktion getenv erreichen:
■
getenv(key, default=None)
Liefert den Wert einer Umgebungsvariablen zurück. None wird zurückgeliefert, wenn die
Variable nicht existiert. Mit dem optionalen zweiten Parameter kann man einen alternativen Default-Wert setzen.
>>> os.getenv("USER")
'bernd'
427
428
38 Systemprogrammierung
■
getenvb( key, default=None)
Liefert den Wert einer Umgebungsvariablen zurück. None wird zurückgeliefert, wenn die
Variable nicht existiert. Mit dem optionalen zweiten Parameter kann man einen alternativen Default-Wert setzen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
„key”, „default” und das Ergebnis sind Byte-Strings!
>>> b = bytes("USER", 'utf-8')
>>> os.getenvb(b)
b'bernd'
■
putenv(key, value)
In der Help-Funktion steht hier nur:
putenv(...)
putenv(key, value)
Change or add an environment variable.
Man könnte nun erwarten, dass man eine Environment-Variable erzeugen und verändern kann und dies dann auch im aktuellen Prozess sichtbar ist. Dies ist aber nicht so,
wie wir im Folgenden sehen:
>>> import os
>>> os.putenv("ABC","42")
>>> os.getenv("ABC")
>>> print(os.getenv("ABC"))
None
Die obige Umgebungsvariable ist erst bekannt, wenn wir einen spawn durchführen oder
ein Unterprozess gestartet wird. Wir starten im folgenden Beispiel eine Subshell und lassen uns dort mittels echo den Wert der Umgebungsvariablen ABC ausgeben. Wir sehen,
dass wir jetzt den Wert 42 erhalten:
>>> command = """ echo $ABC """
>>> os.popen(command,"w")
<os._wrap_close object at 0x8b9e7ac>
>>> 42
Es stellt sich natürlich die Frage, wie wir eine Umgebungsvariable im laufenden Prozess
ändern bzw. neu erzeugen können. Wir erreichen dies, indem wir direkt das Dictionary
os.environ manipulieren:
>>> os.environ["ABC"] = "42"
>>> os.getenv("ABC")
'42'
>>> os.environ["ABC"]
'42'
■
supports_bytes_environ( )
Wenn dieses Attribut den Wert True hat, existiert auch ein Dictionary os.environb, welches die Umgebungsvariablen als Bytestrings enthält.
38.3 os-Modul
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
unsetenv( var )
Eine Umgebungsvariable wird entfernt. Hier gilt analog das zu putenv Gesagte. Die Umgebungsvariable existiert weiterhin im aktuellen Prozess, wird aber nicht mehr an Unterprozesse weitergegeben:
>>> import os
>>> os.environ["ABC"] = "42"
>>> os.getenv("ABC")
'42'
>>> os.unsetenv("ABC")
>>> os.getenv("ABC")
'42'
>>> command = """ echo $ABC """
>>> os.popen(command,"w")
<os._wrap_close object at 0x8bfd10c>
38.3.3
Dateiverarbeitung auf niedrigerer Ebene
In Kapitel 11 (Dateien lesen und schreiben) haben wir gesehen, mit welcher Leichtigkeit
und Eleganz sich Dateien mit Python lesen und schreiben lassen. Es stellt sich deshalb sofort die Frage, weshalb das os-Modul eigene Funktionen und Methoden zur Dateiverarbeitung bereitstellt. In den meisten Fällen genügen die eingebauten Python-Funktionalitäten
voll und ganz, und der Großteil der Python-Benutzer wird nie die os-Funktionen benötigen. Aber die Dateiverarbeitung von os geht mehr ins Detail, d.h. sie gibt dem Benutzer
mehr Möglichkeiten, direkt auf Byteebene zu arbeiten.
■
write
Zunächst wollen wir zeigen, wie man die os.write-Funktion auch im Zusammenspiel mit
einer von der eingebauten open-Funktion zum Schreiben geöffneten Datei nutzen kann:
import os
fh = open("nur_text.txt","w")
fh.write("Ein wenig Text\n")
fh.flush()
fd = fh.fileno()
os.write(fd, b"Noch etwas Text")
fh.close()
Startet man obiges Programm,1 erhält man eine Datei namens „nur_text.txt”. Den Inhalt
können wir uns mittels cat2 oder mit irgendeinem Editor anschauen:
1
2
Das Programm befindet sich in unserer Beispielsammlung unter dem Name „os_write.py”.
type unter Windows
429
430
38 Systemprogrammierung
bernd@saturn:~/bodenseo/python/tmp$ python3 os_write.py
bernd@saturn:~/bodenseo/python/tmp$ cat nur_text.txt
Ein wenig Text
Noch etwas Text
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Mit os.write lassen sich nur Bytestrings schreiben. Lässt man das „b” vor dem Ausgabestring in der Zeile „os.write(fd, b"Noch etwas Text")” weg, führt dies deshalb zu
folgender Fehlermeldung:
bernd@saturn:~/bodenseo/python/tmp$ python3 os_write.py
Traceback (most recent call last):
File "os_write.py", line 8, in <module>
os.write(fd, "Noch etwas Text\n")
TypeError: 'str' does not support the buffer interface
Sehr wichtig ist es auch, die Methode flush auf das Dateihandle anzuwenden, da ansonsten die Ausgaben von os.write an die falsche Stelle geschrieben werden. Im Folgenden
sehen wir den Inhalt der Datei „nur_text.txt” nach einem Programmlauf mit auskommentierter flush-Anweisung. Die beiden Zeilen der Datei sind vertauscht:
bernd@saturn:~/bodenseo/python/tmp$ cat nur_text.txt
Noch etwas Text
Ein wenig Text
■
open(file, flags[, mode])
Mit dieser Funktion wird eine Datei „file” zur Dateibearbeitung auf niedrigem Level geöffnet. Die Art der Bearbeitung ergibt sich sich aus den „flags” und gegebenenfalls dem
optionalen „mode”-Parameter.
– O_RDONLY
Numerischer Wert: 0, gesetztes Bit: keines
Datei wird nur zum Lesen geöffnet.
– O_WRONLY
Numerischer Wert: 1, gesetztes Bit: 0
Datei wird nur zum Schreiben geöffnet.
– O_RDWR
Numerischer Wert: 2, gesetztes Bit: 1
Datei wird zum Lesen und Schreiben geöffnet.
– O_CREAT
Numerischer Wert: 64, gesetztes Bit: 6
Datei wird erzeugt, falls sie noch nicht existiert.
– O_EXCL
Numerischer Wert: 128, gesetztes Bit: 7
Fehlermeldung, falls ein Erzeugen der Datei nicht erfolgreich war, weil die Datei bereits existiert.
– O_NOCTTY
Numerischer Wert: 256, gesetztes Bit: 8
Falls es sich bei der Datei um ein Terminal-Device handelt, soll es nicht zum Kontrollterminal für den Prozess werden.
38.3 os-Modul
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
– O_TRUNC
Numerischer Wert: 512, gesetztes Bit: 9
Falls die Datei existiert, eine reguläre Datei ist und die Datei erfolgreich mittels
O_RDWR oder O_WRONLY geöffnet wurde, wird ihre Länge auf 0 abgeschnitten.
Die Dateirechte und die Eigentumsverhältnisse bleiben unverändert. Die Funktion
sollte keine Effekte auf FIFO-Spezialdateien und auf Termindevice-Dateien haben.
Effekte auf andere Dateitypen sind implementierungsabhängig. Was passiert, wenn
man O_TRUNC zusammen mit O_RDONLY benutzt, ist undefiniert.
– O_APPEND
Numerischer Wert: 1024, gesetztes Bit: 10
Mit jedem write wird ans Ende der Datei geschrieben.
– O_NDELAY und O_NONBLOCK
Numerischer Wert: 2048, gesetztes Bit: 11
Diese Flags beeinflussen aufeinanderfolgende reads und writes. Wenn sowohl
O_NDELAY als auch O_NONBLOCK gesetzt sind, hat O_NONBLOCK den Vorrang.
Falls O_NDELAY oder O_NONBLOCK gesetzt ist, wird ein open für read-only ohne
Verzögerung ausgeführt. Ein open for write-only führt zu einem Fehler, falls die Datei
noch von keinem anderen Prozess zum Lesen geöffnet ist.
Falls O_NDELAY und O_NONBLOCK nicht gesetzt sind, gilt: Ein open für ein readonly wird solange geblockt, bis ein Prozess die Datei zum Schreiben öffnet. Ein open
für ein write-only wird solange geblockt, bis ein Prozess die Datei zum Lesen öffnet.
– O_DSYNC
Numerischer Wert: 4096, gesetztes Bit: 12
– O_ASYNC
Numerischer Wert: 8192, gesetztes Bit: 13
– O_DIRECT
Numerischer Wert: 16384, gesetztes Bit: 14
Cache-Effekte sollen eliminiert bzw. reduziert werden.
– O_LARGEFILE
Numerischer Wert: 32768, gesetztes Bit: 15
– O_DIRECTORY
Numerischer Wert: 65536, gesetztes Bit: 16
– O_NOFOLLOW
Numerischer Wert: 131072, gesetztes Bit: 17
Symbolischen Links wird nicht gefolgt.
– O_NOATIME
Numerischer Wert: 262144, gesetztes Bit: 18
– O_RSYNC
Numerischer Wert: 1052672, gesetztes Bit: 20
– O_SYNC
Numerischer Wert: 1052672, gesetztes Bit: 20
Das folgende Skript öffnet eine Datei für den Write-only-Zugriff „os.O_WRONLY”.
os.O_CREAT stellt sicher, dass die Datei „blabla.txt” beim Öffnen erzeugt wird, falls
sie noch nicht existiert.
431
432
38 Systemprogrammierung
import os
fd = os.open( "blabla.txt", os.O_WRONLY|os.O_CREAT )
os.write(fd, b"Etwas Text\n")
os.write(fd, b"und noch eine Zeile\n")
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
os.close(fd)
Falls blabla.txt noch nicht existiert und man kein „os.O_CREAT” angegeben hat, also
fd = os.open( "blabla.txt", os.O_WRONLY), dann erfolgt beim Start des Skripts eine Fehlermeldung wie hier zu sehen:
bernd@saturn:~/bodenseo/python/tmp$ python3 os_write.py
Traceback (most recent call last):
File "os_write.py", line 3, in <module>
fd = os.open( "blabla.txt", os.O_WRONLY )
OSError: [Errno 2] No such file or directory: 'blabla.txt'
■
read(fd,n)
Die Funktion read liest höchstens „n” Bytes vom Dateideskriptor „fd”. Sie liefert einen
Byte-String zurück, der die gelesenen Bytes enthält.
Falls das Dateiende der durch „fd” referenzierten Datei erreicht ist, wird ein leerer String
zurückgeliefert.
Im Folgenden lesen wir die ersten 80 Bytes des Romans „Ulysses” und lassen uns den
zurückgelieferten Typ zusätzlich ausgeben:
import os, sys
fd = os.open("ulysses.txt",os.O_RDWR)
res = os.read(fd,80)
print(res)
print("type of returned object: ", type(res))
os.close(fd)
Das Skript liefert folgendes Ergebnis:
bernd@saturn:~/bodenseo/python/beispiele$ python3 os_read.py
b'ULYSSES\r\n\r\nby James Joyce\r\n\r\n\r\n\r\n\r\n-- I --\r\n\r\
nStately, plump Buck Mulligan came '
type of returned object: <class 'bytes'>
■
■
close(fd)
Die mit dem Dateideskriptor „fd” referenzierte Datei wird geschlossen. Die Funktion liefert keinen return-Wert zurück.
openpty( )
Die Funktion openpty() öffnet ein Pseudoterminal-Paar und liefert ein Deskriptorenpaar
(master, slave) für pty und tty zurück.
Im folgenden Skript sehen wir die Funktionsweise dieser Funktion:
38.3 os-Modul
import os
master_pty,slave_tty = os.openpty()
terminal_name = os.ttyname(slave_tty)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
print(master_pty, slave_tty, terminal_name)
Obiges Programm liefert folgende Ausgaben:
bernd@saturn:~/bodenseo/python/tmp$ python3 openpty.py
3 4 /dev/pts/3
■
lseek(fd, pos, how)
Die Funktion lseek positioniert die aktuelle Position des Dateideskriptors auf die gegebene Position „pos” in Abhängigkeit von „how”.
Mittels lseek haben wir eine index-sequentielle Zugriffsmethode.
Parameter:
– fd
Der Dateideskriptor, der bearbeitet wird.
– pos
Integer-Wert, der die Position innerhalb der Datei mit dem Dateideskriptor „fd” angibt. Die Position hängt vom Wert von „how” ab. Hat „how” den Wert os.SEEK_SET
oder 0, berechnet sich der Wert von pos vom Beginn der Datei. os.SEEK_CUR oder 1,
dann berechnet sich „pos” relativ zur aktuellen Position; os.SEEK_END oder 2 bedeutet in Relation zum Dateiende.
– how
Dieser Parameter bestimmt den Referenzpunkt innerhalb der Datei:
os.SEEK_SET oder 0 steht für den Anfang der Datei, os.SEEK_CUR oder 1 bezeichnet
die aktuelle Position, und os.SEEK_END oder 2 kennzeichnet das Ende der Datei.
import os, sys
fd = os.open("customer_numbers.txt",os.O_RDWR)
for customer in [2,1,4,0,3]:
os.lseek(fd, customer * 7, os.SEEK_SET)
res = os.read(fd,6)
print("customer number " + str(customer + 1) +": " + str(res))
print("\nEine Iteration über die Kundennummern:")
# iteration über die Kundennummern:
os.lseek(fd, 0, 0)
for customer in range(1,6):
res = os.read(fd,6)
os.lseek(fd, 1, os.SEEK_CUR) # skip the newline character
print("customer number " + str(customer) +": " + str(res))
433
434
38 Systemprogrammierung
print("\n... und nun von hinten:")
for customer in range(1,6):
os.lseek(fd, -7 * customer, os.SEEK_END)
res = os.read(fd,6)
print(str(customer) + " from last: " + str(res))
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
os.close(fd)
In der ersten for-Schleife des obigen Programms können wir sehen, wie wir auf beliebige Kundennummern zugreifen können, ohne sie sequentiell durchlaufen zu müssen. In
der zweiten for-Schleife benutzen wir os.seek mit dem how-Parameter „os.SEEK_CUR”,
d.h. nachdem wir eine Kundennummer eingelesen haben, überspringen wir das nächste
Byte mit dem os.seek-Kommando. In der letzten for-Schleife durchlaufen wir die Kundennummern von hinten. Die Ausgabe des Programms sieht wie folgt aus:
bernd@saturn:~/bodenseo/python/beispiele$ python3 os_seek.py
customer number 3: b'ZH8765'
customer number 2: b'AB9876'
customer number 5: b'QW5185'
customer number 1: b'JK3454'
customer number 4: b'KL9878'
Eine Iteration über die Kundennummern:
customer number 1: b'JK3454'
customer number 2: b'AB9876'
customer number 3: b'ZH8765'
customer number 4: b'KL9878'
customer number 5: b'QW5185'
... und nun von hinten:
1 from last: b'QW5185'
2 from last: b'KL9878'
3 from last: b'ZH8765'
4 from last: b'AB9876'
5 from last: b'JK3454'
■
dup()
Liefert die Kopie eines Datei-Handles3 zurück.
38.3.4
Die exec-„Familie”
Die Funktionen execl, execlp, execle, execv, execvp und execvpe des os-Moduls entsprechen den gleichnamigen UNIX/Linux-Funktionen. Jede dieser Funktionen aus der execFamilie ersetzt das aktuelle Prozessabbild durch einen neuen Prozess. Das neue Prozessabbild wird aus einer regulären und ausführbaren Datei konstruiert. Man nennt diese Datei
3
engl. file descriptor
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
38.3 os-Modul
Bild 38.2 Systematik der exec-Familie
„Prozessabbilddatei”. Die Prozessabbilddatei ist entweder ein ausführbares Objekt oder eine Datendatei, die von einem Interpreter ausgeführt werden kann. Führt man eine dieser
Funktionen aus, gibt es keine Rückkehr (return) nach einem erfolgreichen Aufruf, denn der
aufgerufene Prozess überlagert das aktuelle Prozessabbild.
Schaut man sich die exec*-Funktionen in obigem Schaubild an, fällt auf, dass alle Funktionen exec als Präfix haben. Dann folgen bis zu drei der folgenden Buchstaben: „e”, „l”, „p”
oder „v”
Diese Buchstaben haben folgende Bedeutung:
■
■
■
■
e
Ein Dictionary mit Umgebungsvariablen wird an den auszuführenden Prozess übergeben.
v
Kommandozeilenargumente werden als Liste oder Tupel an das Skript übergeben.
l
Kommandozeilenargumente für das aufzurufende Skript werden direkt und einzeln im
exec*-Aufruf angegeben.
p
Die $PATH-Umgebungsvariable wird benutzt, um das Skript, dessen Name als Parameter
dem String übergeben wurde, zu finden.
Fehler bei einer exec*-Funktion werden als OSError generiert.
Zur Beschreibung der folgenden exec-Befehle benutzen wir ein Bash-Skript zum Testen.
Wir nehmen an, dass dieses Skript unter dem Namen „test.sh” im Verzeichnis „/home/bernd/bin2” steht.
Das Bash-Skript „test.sh”:
#!/bin/bash
script_name=$0
arg1=$1
current=`pwd`
echo $script_name, $arg1
435
436
38 Systemprogrammierung
echo "XYZ: "$XYZ
echo "PATH: "$PATH
echo "current directory: $current"
Achtung:
test.sh muss ausführbar sein, und bin2 sollte, damit die folgenden Beispiele funktionieren,
nicht in $PATH enthalten sein.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
execvp( scriptname, args )
Wie bereits eingangs erklärt, bedeutet das „p” im Namen, dass der Skriptname „scriptname” in den Pfaden der Systemvariablen $PATH gesucht wird. Das „v” bedeutet, dass wir
Kommandozeilenargumente mit dem Parameter „args” übergeben können, und zwar in
Form einer Liste oder eines Tupels.
Wir starten unsere interaktive Python-Shell in einem Verzeichnis, was nicht gleich dem
bin2-Verzeichnis ist. Außerdem ist „/home/bernd/bin2” nicht Teil unserer Umgebungsvariablen „$PATH”.
Dann passiert Folgendes, wenn wir versuchen, unser Test-Shellskript mit execvp aufzurufen:
>>> import os
>>> args = ("test","abc")
>>> os.execvp("test.sh", args)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/os.py", line 353, in execvp
_execvpe(file, args)
File "/usr/lib/python3.2/os.py", line 398, in _execvpe
raise last_exc.with_traceback(tb)
File "/usr/lib/python3.2/os.py", line 388, in _execvpe
exec_func(fullname, *argrest)
OSError: [Errno 2] No such file or directory
execvp konnte die Skriptdatei „test.sh” nicht finden, da sie sich im Verzeichnis „/home/bernd/bin2” befindet und dieser Pfad nicht Teil von $PATH ist.
Rufen wir unsere interaktive Python-Shell direkt im Verzeichnis „/home/bernd/bin2”
auf, funktioniert alles:
bernd@saturn:~/bin2$ python3
Python 3.2.3 (default, Oct 19 2012, 19:53:57)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more
information.
>>> import os
>>> args = ("test", "abc")
>>> os.execvp("test.sh", args)
./test.sh, abc
XYZ:
PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm
:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
38.3 os-Modul
usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/
bernd/bin:.::bin/bck_scripts/:bin/bck_scripts/
current directory: /home/bernd/bin2
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Alternativ kann man auch den Pfad „/home/bernd/bin2” in die $PATH-Umgebungsvariable mit aufnehmen. Dann wird test.sh immer gefunden – egal, wo wir uns aufhalten,
also auch zum Beispiel im Home-Verzeichnis:
bernd@saturn:~$ PATH=$PATH:/home/bernd/bin2
bernd@saturn:~$ python3
Python 3.2.3 (default, Oct 19 2012, 19:53:57)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more
information.
>>> import os
>>> args = ("test", "abc")
>>> os.execvp("test.sh", args)
/home/bernd/bin2/test.sh, abc
XYZ:
PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm
:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/
bernd/bin:.::/home/bernd/bin2
current directory: /home/bernd
bernd@saturn:~$
■
execvpe(scriptname, args, env )
Wählen wir die execvpe-Variante unserer exec-Familie, dann können wir zusätzlich noch
neue Umgebungsvariablen beim Aufruf mit übergeben. Dazu schreiben wir die neuen
Umgebungsvariablen in ein Dictionary, in unserem Fall „env”. „filename” ist der Name
des auszuführenden Skripts und „args” eine Liste oder ein Tupel mit den an „filename”
zu übergebenden Argumenten.
Das Verzeichnis „/home/bernd/bin2” befindet sich zwar noch in unserer PATH-Umgebungsvariablen, aber beim Aufruf werden nur die in env definierten Umgebungsvariablen an das neue Skript übergeben. Deshalb funktioniert der folgende Aufruf nur, wenn
wir uns im bin2-Verzeichnis befinden:
>>> import os
>>> env = {"ABC":"Von Anfang", "KLM":"bis zum ", "XYZ":"Ende"}
>>> args = ("erstes","zweites")
>>> os.execvpe("test.sh", args, env)
test.sh, zweites
XYZ: Ende
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
current directory: /home/bernd/bin2
Wir wollen nun auch PATH und die anderen alten Umgebungsvariablen an das Skript
übergeben. Wir können dies mit der update-Methode erreichen. os.environ enthält al-
437
438
38 Systemprogrammierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
le definierten Umgebungsvariablen der aktuellen Shell. Dann können wir unser Skript
wieder von allen Orten aufrufen, sofern bin2 noch im PATH enthalten ist:
>>> import os
>>> env = {"ABC":"Von Anfang", "KLM":"bis zum ", "XYZ":"Ende"}
>>> env.update(os.environ)
>>> args = ("erstes", "zweites")
>>> os.execvpe("test.sh", args, env)
/home/bernd/bin2/test.sh, zweites
XYZ: Ende
PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm
:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/
bernd/bin:.::bin/bck_scripts/:/home/bernd/bin2
current directory: /home/bernd/bodenseo/python
bernd@saturn:~/bodenseo/python$
■
execv( scriptname, args)
Wie „execvp”, aber der Skriptname „scriptname” muss den kompletten Pfad des Skripts
enthalten, wenn das Skript sich nicht im aktuellen Arbeitsverzeichnis befindet, da der
Name nicht in den Verzeichnissen von der Umgebungsvariablen PATH gesucht wird:
>>> import os
>>> args = ("arg1", "arg2")
>>> os.execv("test.sh", args)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 2] No such file or directory
>>> os.execv("/home/bernd/bin2/test.sh", args)
/home/bernd/bin2/test.sh, arg2
XYZ:
PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm
:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/
bernd/bin:.::bin/bck_scripts/:/home/bernd/bin2
current directory: /home/bernd/bodenseo/python
bernd@saturn:~/bodenseo/python$
■
execve(scriptname, args, env )
Analog zu „execv”, allerdings wird bei dieser Variante noch ein Dictionary mit Umgebungsvariablen mit übergeben:
>>> import os
>>> args = ("arg1", "arg2")
>>> env = {"ABC":"Van Anfang", "KLM":"bis zum ", "XYZ":"Ende"}
>>> os.execve("/home/bernd/bin2/test.sh", args, env)
/home/bernd/bin2/test.sh, arg2
XYZ: Ende
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
38.3 os-Modul
current directory: /home/data/bodenseo/python
bernd@saturn:~/bodenseo/python$
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
execlp( scriptname, arg0, arg1, ...)
Die Funktion „execlp” unterscheidet sich von „execvpe” in der Art, wie die Argumente
übergeben werden. Bei „execvpe” hatten wir ein Tupel bzw. eine Liste mit den Parametern als Strings übergeben. Nun wird jeder Parameter direkt in „execlpe” durch Komma
getrennt als String angegeben, also arg0, arg1, ... . arg0 entspricht dabei dem Namen des
Skripts, wird aber in der Implementierung nicht genutzt. Der Skriptname wird stattdessen an „scriptname” als Name übergeben.
„scriptname” und „env” sind wie bereits in execvpe beschrieben.
Um das Verhalten dieser Funktion besser demonstrieren zu können, benutzen wir
ein Bash-Skript, das alle übergebenen Argumente ausdruckt, nämlich das Bash-Skript
„test_params.sh”:
#!/bin/bash
script_name=$0
echo $script_name
while test $# -gt 0
do
echo $1
shift
done
Nun rufen wir das Skript „test_params.sh” mit „execlpe” in der interaktiven Python-Shell
auf:
>>> import os
>>> os.execlp("test_params.sh", "test_params.sh", "a1","a2", "a3", "
a4")
/home/bernd/bin2/test_params.sh
a1
a2
a3
a4
bernd@saturn:~/bodenseo/python$
Wir sehen, dass wir beim Aufruf den Namen des Skripts zweimal übergeben haben. arg0
sollte also auch den Namen des Skripts enthalten, wird aber von exec ignoriert. Wir können stattdessen auch irgendetwas eingeben, und es hat keinen Einfluss auf das Skript:
>>> import os
>>> os.execlp("test_params.sh", "unsinn.sh", "a1","a2", "a3", "a4")
/home/bernd/bin2/test_params.sh
a1
a2
a3
a4
bernd@saturn:~/bodenseo/python$
439
440
38 Systemprogrammierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
execl( )
Analog zu „execlp”. Allerdings müssen wir nun den kompletten Pfad angeben, damit das
Skript gefunden werden kann, denn die Informationen aus der Umgebungsvariablen
$PATH werden in dieser exec*-Variante nicht verwendet:
>>> import os
>>> os.execl("test_params.sh", "test_params.sh", "arg1", "arg2")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/os.py", line 321, in execl
execv(file, args)
OSError: [Errno 2] No such file or directory
>>> os.execl("/home/bernd/bin2/test_params.sh", "test_params.sh", "
arg1", "arg2")
/home/bernd/bin2/test_params.sh
arg1
arg2
bernd@saturn:~/bodenseo/python$
■
■
execlp( scriptname, arg0, arg1, ... , env)
Wie execlp, nur dass noch ein Dictionary mit Umgebungsvariablen übergeben wird. Siehe dazu die Beschreibung der Funktion „execvpe”.
execle( )
Analog zu execl, aber es wird noch zusätzlich ein Dictionary mit Umgebungsvariablen
übergeben:
>>>
>>>
>>>
>>>
import os
env = {"ABC":"Von Anfang", "KLM":"bis zum ", "XYZ":"Ende"}
env.update(os.environ)
os.execle("/home/bernd/bin2/test.sh", "test.sh", "arg1", "arg2",
env)
/home/bernd/bin2/test.sh, arg1
XYZ: Ende
PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm
:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/
bernd/bin:.::bin/bck_scripts/:/home/bernd/bin2
current directory: /home/bernd/bodenseo/python
bernd@saturn:~/bodenseo/python$
38.3 os-Modul
38.3.5
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
Weitere Funktionen im Überblick
abort( )
Der Interpreter wird unmittelbar abgebrochen. Das englische Wort „abort” bedeutet
„abbrechen”, aber im Sinne von „fehlschlagen lassen”, „scheitern” und „vorzeitig beenden”. In der Help-Ausgabe zum os-Modul steht unter abort: „This ’dumps core’ or
otherwise fails in the hardest way possible on the hosting operating system”. Das bedeutet also, dass der schlimmstmögliche Fehler unter dem jeweiligen Betriebssystem
erzeugt wird.
Unter Linux bedeutet das zunächst einmal, dass man nach dem Abbruch die folgende
Fehlermeldung erhält:
>>> os.abort()
Aborted (core dumped)
bernd@saturn:~/bodenseo/python$
Außerdem klappen zwei Fenster auf, die normalerweise nur bei ernsten Systemfehlern
auftauchen:
■
access(path, mode)
Die Funktion „os.access” dient zur Überprüfung, welche Rechte das laufende PythonProgramm für den Pfad „path” hat. Mit dem optionalen Parameter „mode” kann eine
Bitmaske übergeben werden, welche die zu überprüfenden Rechte enthält.
Folgende Werte können einzeln oder mit dem bitweisen ODER kombiniert übergeben
werden:
os.F_OK : Prüft, ob der Pfad überhaupt existiert.
os.R_OK : Prüft, ob der Pfad gelesen werden darf.
os.W_OK : Prüft, ob der Pfad geschrieben werden darf.
os.X_OK : Prüft, ob der Pfad ausführbar ist.
os.access liefert True zurück, wenn alle für „mode” übergebenen Werte auf den Pfad zutreffen. Gilt mindestens ein Zugriffsrecht nicht, wird False zurückgegeben.
■
chdir(path)
Das Arbeitsverzeichnis wird auf das durch die Stringvariable „path” bezeichnete Verzeichnis geändert:
>>> os.getcwd()
'/home/bernd'
>>> os.chdir("/home/bernd/bodenseo/python")
>>> os.getcwd()
'/home/data/bodenseo/python'
441
442
38 Systemprogrammierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
chmod( path, mode)
Die Zugriffsrechte eines Pfads bzw. einer Datei werden entsprechend der Eingabemaske
„mode” umgesetzt. „mode” muss als Oktalzahl eingegeben werden, d.h. eine Zahl mit
Präfix 0o:
>>> permissions = oct(os.stat("abc.py")[0])
>>> permissions
'0o100644'
>>> os.chmod("abc.py",0o644)
>>> os.chmod("abc.py",0o755)
>>> permissions = oct(os.stat("abc.py")[0])
>>> permissions
'0o100755'
■
chown(path, uid, gid)
Die Funktion chown ändert den Besitzer und die Gruppen-ID entsprechend der Parameter „uid” und „gid”. Möchte man einen Wert unverändert lassen, setzt man ihn beim
Aufruf auf -1. Das Skript benötigt allerdings Superuser-Rechte, um diese Änderungen
durchführen zu können.
Ruft man chown mit ungenügenden Rechten auf, erhält man folgende Fehlermeldung:
>>> os.chown("abc.py",1001,-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 1] Operation not permitted: 'abc.py'
Im folgenden Beispiel starten wir die interaktive Python-Shell mit den entsprechenden
Superuser-Rechten:
bernd@saturn:~/bodenseo/python$ sudo python3
[sudo] password for bernd:
Python 3.2.3 (default, Oct 19 2012, 19:53:57)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more
information.
>>> import os
>>> os.chown("abc.py",1001,-1)
■
chroot(path)
Auch zum Ausführen dieser Funktion benötigt das Skript Superuser-Rechte. Das rootVerzeichnis des aktuellen Prozesses wird mit diesem Kommando auf den Pfad „path”
gesetzt.
Im folgenden Beispiel setzen wir das root-Verzeichnis auf „/tmp”.
>>> os.chroot("/tmp")
38.3 os-Modul
■
extsep
Ein Attribut mit dem Wert des Separators für die Dateikennung4 , also „.”
>>> os.extsep
'.'
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
■
fork( )
siehe 39 (Forks)
forkpty( )
siehe 39 (Forks)
get_exec_path(env=None)
Die Funktion get__exec_path liefert die Liste der Verzeichnisse zurück, die zur Ausführung eines Programms durchsucht werden. Diese Verzeichnisse entsprechen den Verzeichnissen der Umgebungsvariablen PATH.
Die Umgebungsvariable PATH wird standardmäßig in os.environ gesucht. Wird für den
Parameter „env” ein Dictionary übergeben, so wird PATH dort gesucht.
>>> os.get_exec_path()
['/home/bernd/perl5/bin', '/home/bernd/bin', '/usr/lib/lightdm/
lightdm', '/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/
usr/bin', '/sbin', '/bin', '/usr/games', '/usr/local/games', '/
home/writing/etrusker_ag/bin', '/home/bernd/bin', '.']
■
■
■
■
getcwd()
Liefert das aktuelle Arbeitsverzeichnis als Unicode-String zurück.
getcwdb( )
Liefert das aktuelle Arbeitsverzeichnis als Byte-String zurück.
getegid( )
Liefert die effektive Gruppen-ID zurück.
geteuid( )
Liefert die effektive Benutzer-ID5 zurück. Die effektive Benutzer-ID ist im Allgemeinen
identisch mit der „normalen” Benutzer-ID.
>>> os.getuid()
1000
■
getgid( )
Liefert die Gruppen-ID zurück.
>>> os.getgid()
1000
■
getgroups( )
Die Funktion getgroups liefert eine Liste der Gruppen des aktuellen Prozesses zurück.
>>> os.getgroups()
[4, 24, 27, 30, 46, 107, 124, 1000]
4
5
file extension
engl. user ID
443
444
38 Systemprogrammierung
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
setgroups(list)
Die Gruppen des aktuellen Prozesses werden auf die Liste „list” gesetzt.
getloadavg( )
Liefert die Anzahl der Prozesse in der Systemwarteschlange der laufenden Prozesse zurück. Die Zahlen entsprechen den Durchschnittswerten für die letzte Minute, die letzten
fünf und die letzten 15 Minuten. Falls die Durchschnittsbelastung (load average) nicht
ermittelt werden konnte, wird ein OSError generiert.
>>> os.getloadavg()
(0.07, 0.21, 0.22)
■
getlogin( )
Rückgabewert ist der aktuelle Login-Name.
>>> os.getlogin()
'bernd'
■
■
■
■
■
getpgid(pid )
Die Funktion getpgid() liefert die Prozess-Gruppen-ID des Prozesses mit der Prozess-ID
„pid” zurück. Falls der Wert von „pid” 0 ist, liefert getpgid() die Prozessgruppen-ID des
aufrufenden Prozesses zurück.
getpgrp( )
Liefert die Gruppen-ID des aktuellen Prozesses zurück.
getpid( )
Liefert die Prozess-ID des laufenden Prozesses zurück.
getppid( )
Liefert des Prozess-ID des Elternprozesses zurück.
getresgid( )
Liefert ein Dreiertupel (rgid, egid, sgid) zurück. Dies entspricht den Werten „reale
Gruppen-ID” (real group ID), „effektive Gruppen-ID” (effective groupID) und „gespeicherte Gruppen-ID” (saved group ID).
>>> os.getresgid()
(1000, 1000, 1000)
■
getresuid( )
Liefert ein Dreiertupel (ruid, euid, suid) zurück. Dies entspricht den Werten „reale
Benutzer-ID” (real user ID), „effektive Benutzer-ID” (effective user ID) und „gespeicherte Benutzer-ID” (saved user ID).
>>> os.getresuid()
(1000, 1000, 1000)
■
■
getuid( )
Liefert die Benutzer-ID vom aktuellen Prozess zurück.
kill(pid, sig)
Diese Funktion entspricht dem Linux/UNIX-Kommando „kill”. Der Befehl dient dazu,
Signale „sig” zu verschicken. Standardmäßig wird bei dem UNIX-Kommando kill das Signal SIGTERM versendet, welches den entsprechenden Prozess dazu auffordert, sich zu
38.3 os-Modul
beenden. Im Prinzip ist der Name „kill” irreführend, da das Beenden („Töten”) von Prozessen nur ein Anwendungsfall von kill ist. Allgemein dient kill der Interprozesskommunikation.
Im folgenden Beispiel wollen wir einen Prozess mit der pid 7036 beenden:
>>> import os
>>> os.kill(7036,9)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
■
■
■
killpg(pgid, sig)
Eine Prozessgruppe wird mit einem Signal beendet.
linesep( )
link(src, dst )
Diese Funktion erzeugt einen Hardlink „dst” auf die Datei „src”.
listdir( )
>>> import os
>>> os.listdir()
['a.txt', 'abc', 'c.txt', 'b.txt']
>>> os.listdir("/home/bernd/Documents")
['fritz.cfg','unterlagen.pdf', 'test.py', 'output.pdf', 'fritz2.cfg
']
■
■
lstat(path)
Wie stat(path), aber symbolischen Links wird nicht gefolgt.
major(device)
Die Funktion major extrahiert die Major-Nummer eines raw-Devices, die häufig vom
st_dev oder st_rdev Feld von stat()-Ergebnis stammt.
Beispiel: siehe minor-Funktion
■
minor(device)
Die Funktion minor extrahiert die Minor-Nummer eines raw-Devices, die häufig vom
st_dev oder st_rdev Feld von stat()-Ergebnis stammt.
Beispiel:
import os
path = "ulysses.txt"
info = os.lstat(path)
major_no = os.major(info.st_dev)
minor_no = os.minor(info.st_dev)
print("Major Device Number :", major_no)
print("Minor Device Number :", minor_no)
Das Ergebnis sieht wie folgt aus:
445
446
38 Systemprogrammierung
bernd@saturn:~/bodenseo/python/beispiele$ python3 os_major.py
Major Device Number : 8
Minor Device Number : 7
bernd@saturn:~/bodenseo/python/beispiele$
■
makedev( )
Diese Funktion erzeugt eine Gerätenummer aus der Minor- und der Major-Nummer.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiel:
import os
path = "ulysses.txt"
info = os.lstat(path)
major_no = os.major(info.st_dev)
minor_no = os.minor(info.st_dev)
print("Major Device Number :", major_no)
print("Minor Device Number :", minor_no)
dev_no = os.makedev(major_no, minor_no)
print("Device Number :", dev_no)
Folgende Ausgaben werden von obigem Skript erzeugt:
bernd@saturn:~/bodenseo/python/beispiele$ python3 os_makedev.py
Major Device Number : 8
Minor Device Number : 7
Device Number : 2055
■
mkdir(path[, mode])
Die Funktion mkdir() entspricht der Linux/Unix-Funktion mkdir. Ein Verzeichnis „path”
wird erzeugt, aber nur, wenn alle oberen Verzeichnisse bereits existieren. Im folgenden
Beispiel führt der Versuch, das Verzeichnis „x2/x22/x33” zu erzeugen, zu einer Fehlermeldung, da bereits das Verzeichnis „x2” nicht existiert.
Der optionale Parameter „mode” entspricht dem Modus, also z.B. „0755”.
Beispiel:
>>> import os
>>> os.path.isdir("x1")
False
>>> os.mkdir("x1", 0755)
>>> os.path.isdir("x1")
True
>>> os.path.isdir("x2")
False
>>> os.mkdir("x2/x22/x33")
Traceback (most recent call last):
38.3 os-Modul
File "<stdin>", line 1, in <module>
OSError: [Errno 2] No such file or directory: 'x2/x22/x33'
■
makedirs(path[, mode] )
Wie der Befehl mkdir, aber makedirs erzeugt auch gegebenenfalls übergeordnete Verzeichnisse, wenn sie noch nicht existieren.
Der optionale Parameter „mode” entspricht dem Modus, also z.B. „0755”.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiel:
>>> import os
>>> os.path.isdir("x2")
False
>>> os.makedirs("x2/x22/x33")
>>> os.path.isdir("x2")
True
>>> os.path.isdir("x2/x22/x33")
True
■
■
mkfifo(path[, mode])
Funktion zum Erzeugen einer benannten Pipe mit Namen „path”. Optional kann der Modus mit dem Parameter „mode” gesetzt werden.
nice(inc)
Die Funktion vermindert die Priorität des Prozesses durch „inc” und liefert den Wert der
neuen Priorität zurück.
>>>
>>>
2
>>>
5
>>>
6
>>>
11
■
import os
os.nice(2)
os.nice(3)
os.nice(1)
os.nice(5)
popen(command[, mode[, bufsize]] )
Die Funktion popen öffnet eine Pipe. os.popen eignet sich in besonderer Weise, ShellBefehle auszuführen, wie man es von der Kommandozeile des Betriebssystems gewohnt
ist. Ein Kommandostring „command” wird dazu von der Default-Shell des Betriebssystems ausgeführt. Der Rückgabewert ist ein Objekt der Klasse „os._wrap_close”. Dieses
Objekt dient auch dazu, gegebenenfalls die Ergebnisse des Kommandostrings „command” aufzunehmen. Damit hat man dann die Möglichkeit, im Python-Programm mit
diesen Ergebnissen weiterzuarbeiten. Beim Modus „r”, d.h. lesend, werden die Ergebnisse der Ausführung des Kommandostrings „command” im os._wrap_close-Objekt gespeichert. Wählt man den Modus „w”, also schreibend, dann werden die Ergebnisse nicht an
das os._wrap_close-Objekt übergeben, sondern an die Standardausgabe des laufenden
Programms.
447
448
38 Systemprogrammierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Der Parameter „bufsize” bestimmt die Größe des zu verwendenden Puffers. Falls für
„bufsize” der Wert 0 gewählt wird, erfolgt keine Pufferung. Ist der Wert auf 1 gesetzt,
ist die Pufferung aktiviert, und zwar ohne Limit. Gibt man eine positive Zahl größer 1
an, entspricht dies der Puffergröße in Bytes. Ein negativer Wert entspricht dem DefaultVerhalten.
In einem etwas umfangreicheren Beispiel wollen wir demonstrieren, wie man popen
sowohl im „w”-Modus als auch im „r”-Modus betreibt. Zuerst suchen wir mit dem
Shell-Kommando „grep catholic *.txt” aus allen Dateien mit der Extension „.txt” alle
Zeilen, die das Wort „catholic” enthalten. Da wir popen im Modus „w” starten, wird
die Shell-Ausgabe unmittelbar in die Ausgabe unseres Skripts weitergeleitet. Im zweiten
popen starten wir den gleichen Befehl im „r”-Modus. Jetzt wird das Ergebnis in einem
„os._wrap_close”-Objekt zurückgeliefert, das wir in einer Variablen „res” speichern. Im
Folgenden separieren wir alle Wörter, die in diesen Zeilen vorkommen, und geben die
Menge dieser Wörter aus, d.h. wir haben mehrfache Vorkommen auf eines reduziert:
import os, re
command = """ grep catholic *.txt """
print("""Kommando "command" wird im Modus "w" gestartet.""")
print("""Die Ausgabe geht in die Standardausgabe des laufenden
Programms!""")
os.popen(command,"w")
print(""" "command" wird wieder gestartet, aber nun im "r"-Modus
:""")
res = os.popen(command,"r")
list_of_lines = list(res)
words = []
for line in list_of_lines:
line = line[12:] # Abschneiden des Präfixes "ulysses.txt:"
words = words + re.split(r"\W+", line)
words = set(words)
print("""Wörter im Kontext von "catholic":""")
print(words)
Starten wir das Programm, erhalten wir folgende Ausgabe:
bernd@saturn:~/bodenseo/python/beispiele$ python3 os_popen.py
Kommando "command" wird im Modus "w" gestartet.
Die Ausgabe geht in die Standardausgabe des laufenden Programms!
"command" wird wieder gestartet, aber nun im "r"-Modus:
ulysses.txt:the holy Roman catholic and apostolic church.
ulysses.txt:of their brazen bells: _et unam sanctam catholicam et
apostolicam
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
38.3 os-Modul
ulysses.txt:Gill's, catholic club, the industrious blind. Why? Some
reason. Sun or
ulysses.txt:who is this used to say he was a queer breedy man great
catholic all the
ulysses.txt:are liege subjects of the catholic chivalry of Europe
that foundered at
ulysses.txt:the most Roman of catholics call _dio boia_, hangman god
, is doubtless
ulysses.txt:practical catholic: useful at mission time.
ulysses.txt:--You as a good catholic, he observed, talking of body
and soul, believe
ulysses.txt:--That's right, the old tarpaulin corroborated. The
Irish catholic
ulysses.txt:a cocked hat. He infinitely preferred the sacred music
of the catholic
ulysses.txt:corporation emergency dustbuckets, the Roman catholic
church,
ulysses.txt:prepuce, the carnal bridal ring of the holy Roman
catholic apostolic
ulysses.txt:Roman catholicism at the epoch of and with a view to his
matrimony
Wörter im Kontext von "catholic":
{'', 'all', 'mission', 'queer', 'useful', 'Gill', 'industrious', '
corroborated', 'infinitely', 'to', 'bridal', 'hat', 'good', '
emergency', 'preferred', 'sanctam', '_dio', 'view', 'right', '
old', 'Sun', 'soul', 'observed', 'are', 'carnal', 'et', 'That',
'god', 'He', 'body', 'doubtless', 'apostolic', 'reason', '_et',
'great', 'of', 'matrimony', 'practical', 's', 'or', 'blind', '
dustbuckets', 'chivalry', 'cocked', 'apostolicam', 'church', '
ring', 'Europe', 'liege', 'catholicism', 'tarpaulin', 'their', '
call', 'prepuce', 'music', 'was', 'boia_', 'catholics', 'holy',
'that', 'club', 'believe', 'with', 'he', 'Irish', 'this', 'as',
'breedy', 'catholicam', 'unam', 'and', 'is', 'talking', 'say', '
his', 'at', 'You', 'foundered', 'catholic', 'corporation', '
epoch', 'subjects', 'sacred', 'Why', 'bells', 'used', 'who', '
brazen', 'most', 'Roman', 'hangman', 'The', 'man', 'a', 'Some',
'time', 'the'}
■
readlink( )
Die Funktion readlink liefert einen String zurück, der den kompletten Pfad enthält, auf
den ein symbolischer Link „path” zeigt:
>>> import os
>>> os.readlink("proj")
'/home/data/projects/'
■
remove(path )
Entspricht der Funktion unlink. Eine Datei „path” wird entfernt.
449
450
38 Systemprogrammierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
removedirs(path)
Ein Verzeichnis wird rekursiv entfernt. Handelt es sich um das einzige Unterverzeichnis
des Elternprozesses, so wird auch der Elternprozess entfernt. Dies wird rekursiv solange
fortgesetzt, bis ein Pfad nicht mehr das einzige Element eines Elternprozesses ist.
bernd@saturn:~/tmp$ tree x
x
+-- y
|
+-- a
|
|-- b
|
+-- c
+-- z
|-- a
+-- b
7 directories, 0 files
Aus dem folgenden Beispiel lernen wir, dass wir keine Verzeichnisse mit removedirs löschen können, die nicht leer sind. Der anfängliche Versuch, das Unterverzeichnis y mit
os.removedirs("x/y") zu löschen, scheitert. Nachdem alles aus y gelöscht ist außer
dem Verzeichnis c, führt die Ausführung von os.removedirs("x/y/c") auch zu einer
Löschung des Elternverzeichnisses „y”.
>>> import os
>>> os.removedirs("x/y")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/os.py", line 179, in removedirs
rmdir(name)
OSError: [Errno 39] Directory not empty: 'x/y'
>>> os.removedirs("x/y/a")
>>> os.removedirs("x/y/b")
>>> os.removedirs("x/y/c")
>>> os.removedirs("x/y")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/os.py", line 179, in removedirs
rmdir(name)
OSError: [Errno 2] No such file or directory: 'x/y'
Nach obigen Anweisungen sieht unser Baum wie folgt aus:
bernd@saturn:~/tmp$ tree x
x
+-- z
|-- a
+-- b
3 directories, 0 files
38.3 os-Modul
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
rename(src, dst)
Mit rename kann eine Datei oder Verzeichnis von „src” nach „dst” umbenannt werden.
Falls der neue Name „dst” bereits existiert, erfolgt eine Fehlermeldung.
renames(old, new)
>>> import os
>>> tree = os.walk("x")
>>> list(tree)
[('x', ['y', 'z'], ['b.txt', 'a.txt']), ('x/y', ['a', 'b', 'c'], ['b
.txt']), ('x/y/a', [], []), ('x/y/b', [], []), ('x/y/c', [], [])
, ('x/z', ['a', 'b'], []), ('x/z/a', [], []), ('x/z/b', [], [])]
>>> os.renames("x", "x_new")
>>> tree2 = os.walk("x_new")
>>> list(tree2)
[('x_new', ['y', 'z'], ['b.txt', 'a.txt']), ('x_new/y', ['a', 'b', '
c'], ['b.txt']), ('x_new/y/a', [], []), ('x_new/y/b', [], []),
('x_new/y/c', [], []), ('x_new/z', ['a', 'b'], []), ('x_new
■
rmdir(path)
Das Verzeichnis „path” wird entfernt, aber nur, wenn es leer ist. Falls es nicht leer ist, wird
die Ausnahme „OSError: [Errno 39] Directory not empty: ’x_new’” generiert:
>>> import os
>>> os.rmdir("x_new")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 39] Directory not empty: 'x_new'
Will man ein nicht leeres Verzeichnis löschen, empfiehlt es sich, die Funktion rmtree des
Moduls shutil zu benutzen:
>>> import shutil
>>> shutil.rmtree("x_new")
■
■
■
■
■
■
■
setegid(gid)
Die effektive Gruppen-ID des aktuellen Prozesses wird auf „gid” gesetzt.
setgid(gid)
Die Gruppen-ID des aktuellen Prozesses wird auf „gid” gesetzt.
seteuid(uid)
Die effektive User-ID des aktuellen Prozesses wird auf „uid” gesetzt.
setuid( )
Die User-ID des aktuellen Prozesses wird auf „uid” gesetzt.
setregid(rgid, egid)
Die effektive und reale Gruppen-ID des Prozesses wird gesetzt.
setresgid(rgid, egid, sgid)
Die effektive, reale und gespeicherte Gruppen-ID des Prozesses wird gesetzt.
setpgid( )
Der Systemaufruf setpged() wird ausgeführt.
451
452
38 Systemprogrammierung
■
■
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
■
setpgrp( )
Der aktuelle Prozess wird zum Gruppenleader.
setresuid(ruid, euid, suid)
Die effektive, reale und gespeicherte User-ID des Prozesses wird gesetzt.
setreuid(ruid, euid)
Die effektive und reale User-ID des Prozesses wird gesetzt.
setsid( )
Der Systemaufruf setsid() wird aufgerufen.
stat()
Ein stat-Systemaufruf wird durchgeführt.
Die Ergebniswerte sind:
– st_mode – protection bits,
– st_ino – Inode-Nummer
– st_dev – Gerätenummer (device)
– st_nlink – Anzahl der Hardlinks
– st_uid – User-ID des Besitzers
– st_gid – Gruppen-ID des Besitzers
– st_size – Dateigröße
– st_atime – letzte Zugriffszeit auf die Datei
– st_mtime – letzte Modifikationszeit der Datei
– st_ctime – plattformabhängig: Zeit der letzten Änderung der Metadaten unter Unix/Linux und die Entstehungszeit der Datei unter Windows:
>>> import os
>>> os.stat("b.txt")
posix.stat_result(st_mode=33204, st_ino=16781124, st_dev=2055,
st_nlink=1, st_uid=1000, st_gid=1000, st_size=0, st_atime
=1361693338, st_mtime=1361693338, st_ctime=1361693475)
■
stat_float_times([newval])
Diese Funktion dient dazu, das Ausgabeverhalten von os.[lf]stat zu beeinflussen: Falls
„newval” True ist, liefern zukünftige Aufrufe von stat() (bzw. lstat, fstat) float-Werte zurück. Wenn „newval” False ist, werden zukünftige Aufrufe Integers zurückliefern.
Ruft man die Funktion ohne Parameter auf, wird die aktuelle Einstellung zurückgeliefert.
■
strerror(code)
Zu einer gegebenen Fehlernummer „code” wird der entsprechende Fehlerstring zurückgeliefert. Im folgenden Beispiel lassen wir uns die ersten 10 von 132 Fehlermeldungen
anzeigen:
>>> import os
>>> for i in range(1,11):
...
print(i, os.strerror(i))
...
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
38.3 os-Modul
1 Operation not permitted
2 No such file or directory
3 No such process
4 Interrupted system call
5 Input/output error
6 No such device or address
7 Argument list too long
8 Exec format error
9 Bad file descriptor
10 No child processes
■
symlink(src, dst)
symlink erzeugt einen Link „dst”, der auf „src” zeigt.
Im folgenden Beispiel erzeugen wir zunächst einen symbolischen Link namens
„link2abc.py” auf die Datei „abc.py” mit der Funktion symlink:
>>> os.symlink("abc.py", "link2abc.py")
>>> os.path.islink("abc.py")
False
>>> os.path.islink("link2abc.py")
True
■
sysconf( )
Dieser Befehl steht nur unter Linux und anderen Unix-Betriebssystemen zur Verfügung
und entspricht dem Betriebssystembefehl sysconf des Betriebssystems.
■
system( command )
„command” ist ein Kommandostring mit einem gültigen Befehl oder einer Befehlsfolge
der voreingestellten Betriebssystemshell, also beispielsweise der Bash in Linux. Dieser
Kommandostring wird in einer Untershell ausgeführt. Der Rückgabewert ist der ExitStatus des Shellbefehls, also die „0” im folgenden Beispiel:
>>> os.system("for i in *.pdf; do echo $i; done")
Python.pdf
Galois.pdf
0
>>>
Diese Funktionalität ist veraltet. Man sollte stattdessen das Modul subprocesses benutzen.
■
times( )
times liefert ein Tupel mit den verschiedenen Prozesszeiten zurück:
(utime, stime, cutime, cstime, elapsed_time)
>>> os.times()
(0.14, 0.04, 0.04, 0.01, 17210084.65)
■
umask( )
453
454
38 Systemprogrammierung
■
uname( )
Die Funktion uname liefert ein Tupel zurück, welches die folgenden Informationen über
das Betriebssystem enthält:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
(sysname, nodename, release, version, machine)
>>> import os
>>> os.uname()
('Linux', 'saturn', '3.5.0-23-generic', '#35-Ubuntu SMP Thu Jan 24
13:05:29 UTC 2013', 'i686')
■
■
unlink(path)
Eine Datei wird entfernt. Entspricht der Funktion remove(path).
urandom(n)
Die Funktion urandom liefert n Zufallsbytes zurück, die für kryptografische Anwendungen benutzt werden können:
>>> os.urandom(10)
b'9\xc3\xe9\x02\xe8\xa2z\xea\xfcQ'
>>> os.urandom(10)
b'\xdd\x06\x9fK\x17{H{>\xa6'
>>> os.urandom(10)
b'^)\xdb\x9c\x8b\x99\x82\x96\x89\xc4'
■
■
■
■
■
utime(path, (atime, mtime) )
Die letzte Zugriffszeit (access time) und die Modifikationszeit (modification time) einer
Datei werden auf die Werte des Tupels „(atime, mtime)” gesetzt. Werden keine Zeiten
angegeben, werden die Zeiten der Datei „path” auf die aktuelle Zeit gesetzt.
wait()
Wartet darauf, dass ein Kindprozess terminiert, und liefert ein Tupel zurück, das die PID
und den exit-Status enthält, also das Tupel (pid, status).
wait3( )
Wie wait, aber es wird ein 3-Tupel (pid, status, rusage) zurückgeliefert. „rusage” bezeichnet die „resource”-Benutzungsinformation.
waitpid(pid, options)
Wie wait, aber mit den Parametern „pid” und „status”.
walk( )
Betrachten wir folgenden Teilbaum eines Dateisystems:
38.3 os-Modul
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Nun wenden wir walk auf diesen Teilbaum an. Wir starten Python in dem Verzeichnis, in
dem sich das Unterverzeichnis abc befindet:
>>> import os
>>> os.walk("abc")
<generator object walk at 0xb714ba7c>
>>> for path in os.walk("abc"):
...
print(path)
...
('abc', ['a', 'b', 'c'], [])
('abc/a', [], ['a1.txt', 'a2.txt'])
('abc/b', [], ['b2.txt', 'b3.txt', 'b1.txt'])
('abc/c', [], ['c1.txt'])
Die Funktion eignet sich also ideal, um einen Dateibaum rekursiv zu durchwandern.
38.3.6
os.path - Arbeiten mit Pfaden
Man kann das Modul „path” auf zwei Arten importieren: entweder direkt mit „import
os.path” oder indirekt über „import os”
■
abspath(name)
Liefert einen Pfad zurück, der sich aus dem aktuellen Arbeitsverzeichnis und dem Wert
von Namen zusammensetzt. Dabei spielt es keine Rolle, ob der daraus resultierende Pfad
wirklich existiert. Im folgenden Beispiel befinden wir uns im Homeverzeichnis des Benutzers bernd. Dort gibt es ein Unterverzeichnis „python”, aber keines, was „nonsense”
heißt:
bernd@saturn:~$ python3
Python 3.2.3 (default, Oct 19 2012, 19:53:57)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more
information.
>>> import os
>>> os.path.abspath(".")
'/home/bernd'
>>> os.path.abspath("python")
'/home/bernd/python'
>>> os.path.abspath("nonsense")
'/home/bernd/nonsense'
■
basename(p)
Liefert die letzte Komponente eines Pfadnamens zurück.
Beispiel:
>>> os.path.basename("/home/bernd/nonsense")
'nonsense'
>>> os.path.basename("/home/bernd/hellO\_world.py")
455
456
38 Systemprogrammierung
'hellO\_world.py'
>>> os.path.basename("/home/bernd/python/")
''
■
commonprefix(pf )
Liefert den größtmöglichen Pfad zurück, mit dem alle Pfade der Liste „pf” beginnen.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiel:
>>> paths = ["/home/bill/Documents", "/home/bill/bin", "/home/bill/
Documents/articles/"]
>>> os.path.commonprefix(paths)
'/home/bill/'
■
sep
Attribut mit dem Trennsymbol in Pfaden, also in Linux „/” und unter Windows „\\”.
>>> os.sep
'/'
■
dirname(p)
Liefert den Anfang des Pfades „p” bis zum letzten Schrägstrich „/” zurück:
>>> path = "/home/bill/documents"
>>> os.path.dirname(path)
'/home/bill'
>>> path = "/home/bill/documents/"
>>> os.path.dirname(path)
'/home/bill/documents'
>>> path = "/home/bill/documents/beispiel.py"
>>> os.path.dirname(path)
'/home/bill/documents'
■
exists(path)
Mit dieser Funktion kann man testen, ob ein Pfad existiert, d.h. „True” wird zurückgeliefert, wenn ein Pfad existiert, ansonsten „False”. „False” wird auch zurückgeliefert, wenn
es sich um einen „broken link” handelt.
Beispiel:
Bei der Funktion „abspath” hatten wir vermerkt, dass in „/home/bernd/” ein Unterverzeichnis „python” existiert, aber keines mit Namen „nonsense”. Im Folgenden prüfen wir
dies mittels „exists” nach:
bernd@saturn:~$ python3
Python 3.2.3 (default, Oct 19 2012, 19:53:57)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more
information.
>>> import os
>>> os.path.exists("/home/bernd/python")
True
>>> os.path.exists("/home/bernd/nonsense")
False
38.3 os-Modul
■
expanduser(path)
„~” und „~user” werden durch den vollständigen Pfad ersetzt, d.h. „~” wird mit dem Wert
der Shell-Umgebungsvariablen $HOME ersetzt bzw. mit dem Homeverzeichnis des Benutzers „user”. Falls $HOME nicht gesetzt ist oder im Fall von „~user” der Benutzer „user”
nicht existiert, wird der Wert von „path” nicht verändert.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Im folgenden Beispiel existiert kein Benutzer mit Namen „homer”, aber einer namens
„bill”:
>>> os.path.expanduser("~")
'/home/bernd'
>>> os.path.expanduser("~homer")
'~homer'
>>> os.path.expanduser("~bill")
'/home/bill'
>>>
■
expandvars(path)
Variablen bzw. Variablen in Pfaden werden durch ihren Wert ersetzt. Falls eine Variable
nicht definiert ist, bleibt sie unverändert:
>>> os.path.expandvars("$SHELL")
'/bin/bash'
>>> os.path.expandvars("$ABC")
'$ABC'
>>> os.path.expandvars("/home/$USER/")
'/home/bernd/'
■
getatime(filename)
Liefert für einen existierenden Datei- oder Verzeichnisnamen die letzte Zugriffszeit zurück. Der Name kann sowohl durch einen relativen Pfad als auch durch einen absoluten
Pfad gegeben sein. Falls ein Name nicht existiert, wird ein OSError produziert mit der
Meldung: „ No such file or directory:”.
atime entspricht der Unix- bzw. Linux-atime, also dem letzten lesenden oder schreibenden Zugriff auf die Datei.
Beispiel:
>>> import os
>>> os.path.getatime("/home/bernd/bodenseo/python/buch.tex")
1361384210.8701274
>>> os.path.getatime("buch.tex")
1361384210.8701274
>>> os.path.getatime("buecher.tex")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/genericpath.py", line 59, in getatime
return os.stat(filename).st_atime
OSError: [Errno 2] No such file or directory: 'buecher.tex'
457
458
38 Systemprogrammierung
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
getctime(filename)
Wie getatime, jedoch wird hier die ChangeTime, also die Zeit der letzten Änderung an
der Datei ausgegeben. Änderung bedeutet hier, dass entweder der Inhalt der Datei selbst
oder die Metadaten6 der Datei geändert wurden.
getmtime(filename)
Diese Funktion ist analog zu getctime und getatime. Die mtime ändert sich jedes Mal,
wenn in eine Datei geschrieben wird, also auch, wenn sie erstellt wird. Die mtime ändert
sich aber im Unterschied zur ctime nicht, wenn sich die Metadaten einer Datei ändern.
Wir schauen uns nun getatime, getmtime und getctime im Zusammenspiel an. Zunächst
erzeugen wir eine leere Datei names „abc.py”. Wir erkennen, dass die ctime und die mtime gleich sind, während atime einen etwas früheren Wert aufweist; die atime wird beim
Schreiben nicht mehr verändert:
>>> fh = open("abc.py","w")
>>> fh.close()
>>> os.path.getatime("abc.py")
1361433115.8920438
>>> os.path.getmtime("abc.py")
1361433693.0429058
>>> os.path.getctime("abc.py")
1361433693.0429058
Nun ändern wir die Zugriffsrechte der Datei „abc.py”. Wie erwartet ändert sich nun nur
die ctime, da sich die mtime nur ändert, wenn wirklich der Inhalt einer Datei verändert
wurde, was ja hier nicht der Fall ist:
>>> os.chmod("abc.py",755)
>>> os.path.getatime("abc.py")
1361433115.8920438
>>> os.path.getmtime("abc.py")
1361433693.0429058
>>> os.path.getctime("abc.py")
1361433817.0595207
Jetzt ändern wir wirklich den Inhalt, indem wir weiteren Text an die Datei anhängen. Wir
erkennen, dass nun sowohl getmtime und getctime die gleichen Werte zurückliefern:
>>> fh = open("abc.py","a")
>>> fh.write("Just some spam!")
15
>>> fh.close()
>>> os.path.getatime("abc.py")
1361433115.8920438
>>> os.path.getmtime("abc.py")
1361433951.7121885
>>> os.path.getctime("abc.py")
1361433951.7121885
6
Als Metadaten bezeichnet man in diesem Zusammenhang „Zugriffsrechte”, „Besitzer” oder Ähnliches.
38.3 os-Modul
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
getsize(filename)
Liefert die Dateigröße zurück, d.h. die Anzahl der Bytes.
>>>
>>>
>>>
0
>>>
>>>
17
>>>
>>>
17
■
fh = open("abc.py","w")
fh.close()
os.path.getsize("abc.py")
fh = open("abc.py","a")
fh.write("Do you like spam?")
fh.close()
os.path.getsize("abc.py")
isabs(s)
Die Funktion prüft, ob es sich bei einem Dateinamen um einen absoluten oder einen
relativen Dateinamen handelt. Dabei spielt es keine Rolle, ob ein Pfadname wirklich im
Dateisystem existiert. Es handelt sich um eine reine syntaktische Prüfung.
>>> os.path.isabs("/monty/python")
True
>>> os.path.isabs("python")
False
>>> os.path.exists("/monty/python")
False
Achtung:
Obwohl es sich bei Pfadnamen wie „~/Documents” oder „~monty” um absolute Pfadnamen handelt, wird in diesem Fällen jedoch der Wert „False” zurückgeliefert:
>>> os.path.isabs("~/python")
False
>>> os.path.isabs("~monty/python")
False
■
isdir(s)
Liefert True zurück, wenn der Pfadname „s” ein existierendes Verzeichnis bezeichnet:
>>> os.path.isdir("beispiele")
True
>>> os.path.isdir("monty")
False
>>> os.path.isdir("abc.py")
False
>>> os.path.exists("abc.py")
True
>>> os.path.exists("/home/bernd/")
True
459
460
38 Systemprogrammierung
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
isfile(path)
Liefert True zurück, wenn der Pfadname „path” eine existierende Datei bezeichnet:
>>> os.path.isfile("abc.py")
True
>>> os.path.isfile("/home/bernd/bodenseo/python/abc.py")
True
>>> os.path.isfile("/home/bernd/bodenseo/python/spam.py")
False
■
islink(path)
Liefert True zurück, wenn es sich bei „path” um einen symbolischen Link handelt, ansonsten wird False zurückgegeben. Im folgenden Beispiel erzeugen wir zunächst einen
symbolischen Link namens „link2abc.py” auf die Datei „abc.py” mit der Funktion symlink:
>>> os.symlink("abc.py", "link2abc.py")
>>> os.path.islink("abc.py")
False
>>> os.path.islink("link2abc.py")
True
■
ismount(path)
Testet, ob es sich bei dem Pfad „path” um einen Mount-Punkt handelt:
>>> os.path.ismount("/media/bernd/")
False
>>> os.path.ismount("/media/bernd/CDD8-B9BF")
True
■
join(a, *p)
Zwei oder mehr Pfadnamenskomponenten werden vereinigt, wobei ein Schrägstrich „/”
bei Bedarf eingefügt wird. Falls eine der Komponenten ein absoluter Pfadname ist, werden alle vorher stehenden Komponenten weggelassen. Steht als letzte Komponente ein
leerer String, wird ein Schrägstrich angefügt, falls der Pfad sonst nicht mit einem Schrägstrich geendet hätte.
>>> os.path.join("abc","def","xyz")
'abc/def/xyz'
>>> os.path.join("/home/monty","def","xyz")
'/home/monty/def/xyz'
>>> os.path.join("abc","/home/monty","xyz")
'/home/monty/xyz'
>>> os.path.join("abc","home/monty","xyz")
'abc/home/monty/xyz'
>>> os.path.join("abc","home/monty","xyz", "")
'abc/home/monty/xyz/'
>>> os.path.join("abc","home/monty","xyz/")
'abc/home/monty/xyz/'
38.3 os-Modul
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
lexists(path)
Liefert True zurück, wenn der Pfad „path” existiert. Falls path ein symbolischer Link ist,
wird auch True zurückgeliefert, wenn es sich um einen „broken link” handelt. Zum Verständnis des folgenden Beispiels muss man wissen, dass es in dem existierenden Verzeichnis „/home/bernd/” kein Unterverzeichnis „monty” gibt und dass es sich bei „/home/bernd/bodenseo/python/monty” um einen „broken link” handelt.
>>> os.path.lexists("/home/bernd/monty")
True
>>> os.path.lexists("/home/bernd/monty")
False
>>> os.path.lexists("/home/bernd/bodenseo/python/monty")
True
■
normcase(s)
Normalisiert Groß- und Kleinschreibung eines Pfades. Diese Funktion hat keine Wirkung unter Posix, also nicht unter Linux und Unix. Unter Windows spielt aber Großund Kleinschreibung bei Pfad- und Dateinamen keine Rolle, d.h. „ABC.PY”, „abc.py”
oder „Abc.py” sind alles gültige Schreibweisen für das gleiche Dateiobjekt. Wendet man
normcase unter Windows auf diese Namen an, erhält man jeweils den Namen komplett
in Kleinschreibung.
Beispiel unter Windows:
>>> os.path.normcase("ABC.PY")
'abc.py'
>>> os.path.normcase("Abc.py")
'abc.py'
>>> os.path.normcase("abc.py")
'abc.py'
>>> os.path.normcase("c:/Users/Bernd/")
'c:\\users\\bernd\\
Unter Linux sieht es hingegen wie folgt aus:
>>> os.path.normcase("ABC.PY")
'ABC.PY'
>>> os.path.normcase("Abc.py")
'Abc.py'
>>> os.path.normcase("abc.py")
'abc.py'
■
normpath(path)
Der Pfad „path” wird normalisiert, d.h. mehrfache Schrägstriche und Schrägstriche am
Ende eines Pfades werden entfernt.
>>> os.path.normpath("abc.py/")
'abc.py'
>>> os.path.normpath("/home///bernd//abc.py/")
'/home/bernd/abc.py'
461
462
38 Systemprogrammierung
■
realpath(filename)
Die Funktion „realpath” liefert den kanonischen Pfad, d.h. ein Pfad ohne symbolische
Links, eines Pfades „filename” zurück. Symbolische Links werden dabei aufgelöst, wie
wir im Beispiel sehen können.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
In der folgenden interaktiven Python-Shell befinden wir uns im Verzeichnis „/home/
bernd/bodenseo/python/beispiele/intranet”. Dort legen wir einen symbolischen Link
„intranet” auf „/var/www/intranet” an:
>>> os.symlink("/var/www/intranet", "intranet")
>>> os.path.realpath("intranet")
'/home/data/intranet'
>>> os.path.realpath("/home/bernd/bodenseo/python/beispiele/intranet
")
'/home/data/intranet'
■
relpath(path, start=None)
Es wird ein Pfadname relativ zur aktuellen Position für den Pfad „path” generiert:
>>> os.getcwd()
'/home/data/bodenseo/python/beispiele'
>>> os.path.relpath("/home/bernd/")
'../../../../bernd'
■
samefile(f1, f2)
Es wird getestet, ob zwei Pfadnamen die gleiche Datei referenzieren. Es wird nicht geprüft, ob zwei Dateien den gleichen Inhalt haben, wie wir im Folgenden auch erkennen
können:
>>> os.path.samefile("abc.py", "../python/abc.py")
True
>>> os.symlink("abc.py", "monty.py")
>>> os.path.samefile("abc.py", "monty.py")
True
>>> import shutil
>>> shutil.copyfile("abc.py", "beispiele/abc.py")
>>> os.path.samefile("beispiele/abc.py", "monty.py")
False
>>> os.path.samefile("beispiele/abc.py", "abc.py")
False
■
split(p)
Ein Pfadname wird in den Dateinamen bzw. das tiefste Verzeichnis und den Basispfad,
also alles vor dem letzten Schrägstrich aufgeteilt. Beide Teile können jeweils leer sein.
Beispiel:
>>> os.path.split("/abc/xyz/beispiel.py")
('/abc/xyz', 'beispiel.py')
>>> os.path.split("/abc/xyz/")
('/abc/xyz', '')
38.4 shutil-Modul
>>> os.path.split("/abc/xyz")
('/abc', 'xyz')
>>> os.path.split("xyz.py")
('', 'xyz.py')
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
splitdrive(p)
Diese Funktion ist im Prinzip auf das Windows-Betriebssystem zugeschnitten. Ein Pfadname wird in den Laufwerksnamen (drive) und den Rest des Pfades zerlegt. Unter Linux
und Unix ist der erste Wert immer der leere String, weil es keine Laufwerkskomponente
geben kann.
Das folgende Beispiel wurde unter Windows erstellt:
>>> os.path.splitdrive("c:\\windows\users")
('c:', '\\windows\\users')
Unter Linux sieht es wie folgt aus, auch wenn der Pfad keinen Sinn ergibt:
>>> os.path.splitdrive("c:/abc/xyz/beispiel.py")
('', 'c:/abc/xyz/beispiel.py')
■
splitext(p)
Die Dateierweiterung wird von einem Pfad abgesplittet, d.h. alles, was nach dem letzten
Punkt folgt:
>>> os.path.splitext("/abc/xyz/beispiel.py")
('/abc/xyz/beispiel', '.py')
>>> os.path.splitext("/abc/xyz/beispiel")
('/abc/xyz/beispiel', '')
>>> os.path.splitext("/abc/xyz/beispiel/")
('/abc/xyz/beispiel/', '')
>>>
38.4
shutil-Modul
Im vorigen Abschnitt hatten wir bereits eine Datei unter Benutzung des shutil-Moduls kopiert. Viele Python-Nutzer, die shutil noch nicht kennen, vermuten die Kopierfunktion
zunächst im os-Modul. Dieses Modul stellt eine plattformunabhängige Schnittstelle zur
Verfügung, um Dateien und Dateibäume zu kopieren, zu entfernen oder zu archivieren.
Bei der Beschreibung der folgenden Funktionen benutzen wir häufig die String-Parameter
„src” und „dst”. Dabei bezeichnet „src” die Quelldatei7 bzw. das Quellverzeichnis, und „dst”
steht für die Zieldatei bzw. Zielverzeichnis.8
7
8
src steht für das englische Wort source, was auf Deutsch Quelle bedeutet.
dst steht dabei als Abkürzung für destination, was auf Deutsch Ziel bedeutet.
463
464
38 Systemprogrammierung
■
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
■
■
copyfile(src, dst)
Kopiert die Datei „src” nach „dst”. Wenn die Datei unter dst bereits existiert, wird sie
überschrieben. Dabei muss der Pfad dst schreibbar sein. Ansonsten wird ein IOError geworfen.
copy(src, dst)
Kopiert die Datei src nach dst unter Beibehaltung der Zugriffsrechte. Existiert „dst” bereits, wird „dst” überschrieben. Entspricht „dst” einem Ordner, wird innerhalb dieses
Ordners eine Datei mit dem Dateinamen von src angelegt oder überschrieben, falls diese Datei bereits in „dst” existiert.
copy2(src, dst)
Wie copy, allerdings werden auch die Zugriffsrechte, der Eigentümer und der Zeitstempel mitkopiert. Der Befehl entspricht dem Unix/Linux-Befehl: „cp -p src dst”
copyfileobj(fsrc, fdst, length=16384)
Der Inhalt des zum Lesen geöffneten Dateiobjekts „fsrc” wird in das zum Schreiben geöffnete „fdst”-Objekt kopiert. Mit dem optionalen Parameter kann gesteuert werden, wie
viele Bytes jeweils gelesen und anschließend gespeichert werden.
>>>
>>>
>>>
>>>
>>>
■
■
■
fh = open("abc.txt")
fhw = open("spam.txt", "w")
shutil.copyfileobj(fh,fhw)
fh.close()
fhw.close
copymode(src, dst)
Die Bits für die Zugriffsrechte werden von „src” nach „dst” kopiert.
copystat(src, dst)
Alle Statusinformationen, d.h. mode bits, atime, mtime, flags, werden von „src” nach
„dst” kopiert.
copytree(src, dst, symlinks=False, ignore=None, copy_function=<function copy2>,
ignore_dangling_symlinks=False)
Ein Verzeichnisbaum „src” wird rekursiv in einen Verzeichnisbaum „dst” kopiert.
Wir betrachten das Beispiel, das wir bereits in Beispiel 38.3.5 (Weitere Funktionen im
Überblick) kennengelernt haben:
>>> import shutil
>>> shutil.copytree("abc","xyz")
Der Baum „dst” darf noch nicht existieren. Wenn wir obigen Befehl wieder anwenden,
erhalten wir deshalb eine Fehlermeldung:
>>> shutil.copytree("abc","xyz")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/shutil.py", line 203, in copytree
os.makedirs(dst)
File "/usr/lib/python3.2/os.py", line 152, in makedirs
mkdir(name, mode)
OSError: [Errno 17] File exists: 'xyz'
38.4 shutil-Modul
Interessant ist vor allen Dingen der Fall, wenn es sich bei dem Pfadnamen „dst” um einen
Pfad handelt, der im Innern des durch „src” bezeichneten Teilbaums liegt. Anders ausgedrückt ist „src” ein Teilstring von „dst”, wenn man von Pfadnamen in kanonischer Darstellung ausgeht (also ohne Links).
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir kopieren nun den gesamten Teilbaum „abc” in das Verzeichnis „abc” mit dem neuen
Namen „xyz”, also parallel zu den Verzeichnissen „a”, „b” und „c”. Mithilfe der Funktion
walk des Moduls os schauen wir uns die Bäume jeweils an:
>>> import shutil
>>> import os
>>> os.walk("abc")
<generator object walk at 0xb71d6054>
>>> list(os.walk("abc"))
[('abc', ['a', 'b', 'c'], []), ('abc/a', [], ['a1.txt', 'a2.txt']),
('abc/b', [], ['b2.txt', 'b3.txt', 'b1.txt']), ('abc/c', [], ['
c1.txt'])]
>>> shutil.copytree("abc","abc/xyz")
>>> list(os.walk("abc"))
[('abc', ['xyz', 'a', 'b', 'c'], []), ('abc/xyz', ['a', 'b', 'c'],
[]), ('abc/xyz/a', [], ['a1.txt', 'a2.txt']), ('abc/xyz/b', [],
['b2.txt', 'b3.txt', 'b1.txt']), ('abc/xyz/c', [], ['c1.txt']),
('abc/a', [], ['a1.txt', 'a2.txt']), ('abc/b', [], ['b2.txt', '
b3.txt', 'b1.txt']), ('abc/c', [], ['c1.txt'])]
Falls der optionale Parameter symlinks auf True gesetzt ist, werden symbolische Links
im Quellbaum auch als symbolische Links im Zielbaum realisiert. Ist der Parameter auf
False gesetzt, was per Default der Fall ist, werden die Inhalte der Dateien, auf die symbolische Links zeigen, kopiert. Falls ein symbolischer Link nicht existiert, wird dies als
Fehler ausgegeben. Um diese Fehlermeldung zu unterdrücken, setzt man den optionalen Parameter ignore_dangling_symlinks auf True.
Der optionale „ignore”-Parameter ist ein aufrufbares Objekt wie zum Beispiel eine Funktion. Es erhält den Pfad „src” und eine Liste der Namen „names”, die von copytree besucht werden, als Parameter, so wie sie von os.listdir() erzeugt wird. Das aufrufbare Objekt liefert dann eine Liste „ignored_names” zurück. Also eine Liste der Namen, die nicht
von copytree kopiert werden sollen.
Man kann auch eine eigene Funktion übergeben, die festlegt, wie die Dateien zu kopieren sind. Dazu dient der optionale Parameter copy_function. Als Default wird copy2()
benutzt.
■
get_archive_formats()
Liefert eine Liste der unterstützten Formate zum Archivieren und Dearchivieren zurück.
Jedes Element dieser Ergebnisliste ist ein Zweiertupel mit dem Namen und einer Beschreibung:
>>> shutil.get_archive_formats()
[('bztar', "bzip2'ed tar-file"), ('gztar', "gzip'ed tar-file"), ('
tar', 'uncompressed tar file'), ('zip', 'ZIP file')]
465
466
38 Systemprogrammierung
■
get_unpack_formats()
Liefert eine Liste der Dekomprimierformate zurück. Jedes Element der Ergebnisliste entspricht einem Tupel der Form: (Name, Extension, Beschreibung)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
>>> shutil.get_unpack_formats()
[('bztar', ['.bz2'], "bzip2'ed tar-file"), ('gztar', ['.tar.gz', '.
tgz'], "gzip'ed tar-file"), ('tar', ['.tar'], 'uncompressed tar
file'), ('zip', ['.zip'], 'ZIP file')]
■
ignore_patterns(*patterns)
Diese Funktion kann als „ignore”-Parameter bei copytree benutzt werden.
Die „patterns” entsprechen glob-Pattern der Linux-Shells.
■
make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
dry_ run=0, owner=None, group=None, logger=None)
Eine Archivdatei wird erzeugt. Dafür stehen die gängigen Archivierungsformate zur
Verfügung, wie zum Beispiel das zip- oder tar-Format.
Die Funktion liefert den vollständigen Pfad und Namen der erzeugten Archivdatei zurück.
„base_name” ist der Name der Archivdatei, die ohne die übliche Endung erzeugt werden soll. Der vollständige Name der Archivdatei ergibt sich mit dem Parameter „format”,
welcher der Extension des Archivformats entspricht, also „zip”, „tar”, „bztar” oder „gztar”.
Der vollständige Name der Archivdatei wird somit durch die folgende Konkatenation gebildet:
base_name + "."+ format
„root_dir” entspricht dem Root-Verzeichnis, von dem relative Pfadnamen in base_dir
beginnen. „base_dir” ist das Root-Verzeichnis des zu archivierenden Teilbaums.
Im folgenden Beispiel wird ein Verzeichnisbaum mit dem Namen „./abc” archiviert. Dieser Baum muss sich im aktuellen Unterverzeichnis befinden, denn „root_dir” ist auf „.”
gesetzt:
>>> import shutil
>>> shutil.make_archive("my_archive", "bztar", ".", "./abc")
'/home/bernd/tmp/my_archive.tar.bz2'
Die erzeugte Archivdatei befindet sich nun im aktuellen Verzeichnis.
Nun rufen wir obigen Befehl in einem Verzeichnis auf, in dem sich kein Unterverzeichnis
„./abc” befindet, d.h. in unserem Beispiel das Homeverzeichnis des Benutzers „bernd”.
Wir erhalten dann eine Fehlermeldung:
>>> import shutil
>>> shutil.make_archive("my_archive", "bztar", ".", "./abc")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/shutil.py", line 585, in make_archive
filename = func(base_name, base_dir, **kwargs)
File "/usr/lib/python3.2/shutil.py", line 426, in _make_tarball
tar.add(base_dir, filter=_set_uid_gid)
38.4 shutil-Modul
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
File "/usr/lib/python3.2/tarfile.py", line 2065, in add
tarinfo = self.gettarinfo(name, arcname)
File "/usr/lib/python3.2/tarfile.py", line 1937, in gettarinfo
statres = os.lstat(name)
OSError: [Errno 2] No such file or directory: './abc'
Falls wir den Pfad in base_dir nicht relativ, sondern absolut angeben, wird der Teilbaum
„abc” unabhängig vom Wert von root_dir gefunden. Die Archivdatei wird jedoch im von
root_dir definierten Pfad abgelegt:
>>> import shutil
>>> shutil.make_archive("my_archive", "bztar", "/home/bernd/", "/
home/bernd/tmp/abc")
'/home/bernd/my_archive.tar.bz2'
■
move(src, dst)
Dieser Befehl ist ähnlich wie der Unix/Linux-Befehl „mv”. „move” dient dazu, eine Datei
oder ein Verzeichnis an einen anderen Speicherort zu verschieben.
Falls der Zielpfad „dst” ein Verzeichnis oder ein symbolischer Link auf ein Verzeichnis
ist, dann wird das durch „src” bezeichnete Objekt, also beispielsweise eine Datei oder
ein Verzeichnis, in das Verzeichnis „dst” verschoben. Wenn das Verzeichnis „dst” noch
nicht existiert, wird es erzeugt.
Wenn das Ziel „dst” bereits existiert, aber kein Verzeichnis, sondern eine Datei ist, wird
diese überschrieben, sofern die entsprechenden Rechte vorhanden sind. Dies aber nur,
falls es sich bei „src” auch um eine Datei handelt. Ist „src” ein Verzeichnis, erhält man
eine Fehlermeldung.
■
■
■
register_archive_format(name, function, extra_args=None, description=”)
Ein Archivformat registrieren. „name” ist der Name des Formats. „function” ist eine
Funktion („callable”), die benutzt wird, um ein Archiv zu erzeugen. Der optionale
Parameter „extra_args” ist ein sequentielles Datenobjekt mit (name, value)-Tupels,
die der Funktion „function” übergeben werden. „description” ist optional und
kann die Beschreibung des Formats enthalten. Außerdem wird „description” von der
get_archive_formats()-Funktion zurückgeliefert.
register_unpack_format(name, extensions, function, extra_args=None, description=”)
Analog zu register_archive_format, allerdings für ein Format zum Entpacken. „name”
ist der Name des Formats. „extensions” ist eine Liste von Extensions für dieses Format.
„function” ist eine Funktion, die benutzt wird, um ein Archiv zu entpacken. Als Parameter wird ein Archiv übergeben. Falls die Funktion mit dem Archiv nicht klarkommt, d.h.
es nicht entpacken kann, wird ein ReadError generiert. Der optionale Parameter „extra_args” ist ein sequentielles Datenobjekt mit (name, value)-Tupels, die der Funktion
„function” übergeben werden. „description” ist optional und kann die Beschreibung
des Formats enthalten. Außerdem wird „description” von der get_unpack_formats()Funktion zurückgeliefert.
rmtree(path, ignore_errors=False, onerror=None)
Ein Verzeichnisbaum „path” wird rekursiv gelöscht. Falls „ignore_errors” gesetzt ist, werden Fehler ignoriert. Falls „onerror” gesetzt ist, d.h. ein Zeiger auf ein aufrufbares Objekt,
467
468
38 Systemprogrammierung
dann wird dieses Objekt mit den Argumenten (func, path, exc_info) aufgerufen, wobei
„func” entweder „os.listdir”, „os.remove” oder „os.rmdir” ist, „path” der Pfad ist, der den
Fehler verursacht hat, und exc_info ein Tupel ist, was von sys.exc_info() zurückgeliefert
wurde. Falls sowohl „ignore_errors” auf False gesetzt ist und „onerror” auf None, dann
wird eine Ausnahme generiert.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■
■
■
unpack_archive(filename, extract_dir=None, format=None)
Entpackt ein Archiv. „filename” ist der Name des Archivs. „extract_dir” ist der Name des
Zielverzeichnisses, wohin das Archiv entpackt werden soll. Falls dieser optionale Parameter nicht angegeben wird, wird das aktuelle Arbeitsverzeichnis als Zielverzeichnis verwendet. „format” ist das verwendete Archivierungsformat, also eines der Formate „zip”,
„tar”, oder „gztar” oder ein anderes registriertes Format (siehe register_archive_format).
Falls kein Format angegeben wird, prüft „unpack_archive”, ob es für die Dateiendung
einen registrierten Entpacker gibt. Falls keiner gefunden wird, wird ein ValueError generiert.
unregister_archive_format(name)
Ein Format wird deregistriert.
unregister_unpack_format(name)
Entfernt das pack-Format aus der Registrierung.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
39
39.1
Forks
Fork
Schon lange vor der Biologie hat sich die Informatik mit dem
Klonen beschäftigt. Aber man nannte es nicht Klonen sondern Forken. fork bedeutet in Englisch Gabel, Gabelung, Verzweigung und als Verb gabeln, aufspalten und verzweigen.
Aber es bedeutet auch eine Aufspaltung oder als Verb aufspalten. Im letzteren Sinn wird es bei Betriebssystemen verwendet, vor allem bei Unix und Linux. Prozesse werden aufgespalten – geklont würde man heute eher sagen – und führen dann ein eigenständiges „Leben”.
In der Informatik steht der Begriff Fork für die Bezeichnung
verschiedener Sachverhalte:
■
■
■
Bild 39.1 Zellteilung
Ein vom Betriebssystem bereitgestellter Systemaufruf, der
den bestehenden Prozess aufspaltet – und dabei eine exakte Kopie des Prozesses erzeugt
– und dann beide Prozesse gewissermaßen parallel laufen lässt.
In der Software-Entwicklung bezeichnet ein Fork eine Abspaltung von einem (Haupt-)
Projekt.
Die Fähigkeit einiger Dateisysteme zur Unterteilung von Dateien.
39.2
Fork in Python
Falls Sie Python nur unter Windows benutzen, können Sie den Rest des Kapitels getrost
überspringen, da Forks unter Windows nicht unterstützt werden. Sie laufen aber unter Linux und Unix.
Beim Systemaufruf os.fork erzeugt der aktuelle Prozess eine Kopie von sich selbst, welche
dann als Kindprozess des erzeugenden Programms läuft. Der Kindprozess übernimmt die
Daten und den Code vom Elternprozess und erhält vom Betriebssystem eine eigene Prozessnummer, eine sogenannte PID (engl. „Process IDentifier”). Der Kindprozess läuft als
470
39 Forks
eigenständige Instanz des Programms, unabhängig vom Elternprozess. Am Rückgabewert
von fork() erkennt man, in welchem Prozess man sich befindet. 0 kennzeichnet den Kindprozess. Im Fehlerfall liefert fork() einen Wert kleiner 0 zurück, und kein Kindprozess wird
erzeugt.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Um Prozesse forken zu können, müssen wir das Modul os in Python importieren.
Das folgende Beispiel-Skript zeigt einen Elternprozess (Parent), der sich beliebig oft forken kann, solange man als Benutzer des Skripts kein „q” bei der Eingabeaufforderung eingibt. Sowohl der Kindprozess als auch der Elternprozess machen nach dem fork mit der
if-Anweisung weiter. Im Elternprozess hat newpid einen von 0 verschiedenen Wert, sodass
die Ausführung im else-Teil der if-Anweisung fortfährt. Im Kindprozess hat newpid den
Wert 0, sodass dort die Funktion child() aufgerufen wird. Die exit-Anweisung os.exit(0) in
der child-Funktion ist notwendig, da sonst der Kindprozess in den Elternprozess zurückkehren würde, und zwar zur input-Anweisung.
import os
def child():
print('A new child ',
os._exit(0)
os.getpid( ))
def parent():
while True:
newpid = os.fork()
if newpid == 0:
child()
else:
pids = (os.getpid(), newpid)
print("parent: %d, child: %d" % pids)
if input( ) == 'q': break
parent()
Starten wir dieses Programm, erhalten wir folgende Ausgaben:
bernd@saturn:~/bodenseo/python/beispiele$ python3 fork.py
parent: 5023, child: 5024
A new child 5024
q
bernd@saturn:~/bodenseo/python/beispiele$
Das Diagramm in Bild 39.2 veranschaulicht nochmals, was genau beim Forking passiert:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
39.2 Fork in Python
Bild 39.2 Forking
Im nächsten Skript erzeugen wir drei Kindprozesse, in denen wir jeweils vier printAusgaben im Sekundenabstand absetzen:
import os, time
def counter(count):
for i in range(count):
time.sleep(1)
print('%s. count of [%s]' % (i, os.getpid()))
for i in range(3):
pid = os.fork()
if pid == 0:
counter(4)
os._exit(0)
else:
print('Another process spawned: %d' % pid)
print('Exit of parent process')
471
472
39 Forks
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Ausgabe sieht wie folgt aus:
bernd@saturn:~/bodenseo/python/beispiele$ python3 fork_counter.py
Another process spawned: 6428
Another process spawned: 6429
Another process spawned: 6430
Exit of parent process
bernd@saturn:~/bodenseo/python/beispiele$ 0. count of [6428]
0. count of [6429]
0. count of [6430]
1. count of [6428]
1. count of [6429]
1. count of [6430]
2. count of [6429]
2. count of [6428]
2. count of [6430]
3. count of [6429]
3. count of [6428]
3. count of [6430]
bernd@saturn:~/bodenseo/python/beispiele$
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Teil V
Lösungen zu den Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
40
40.1
Lösungen zu den
Aufgaben
Lösungen zu Kapitel 5
(Sequentielle Datentypen)
1. Aufgabe
Wir haben die folgenden Datentypen kennengelernt:
■
■
■
■
str:
Zeichenketten, meist als Strings bezeichnet
list:
Listen
tuple:
Tupel
bytes und bytearray:
Binärdaten
2. Aufgabe
Wenn die Variable „liste” eine Liste bezeichnet, dann spricht man das erste Element mit
liste[0] an.
Beispiel:
>>> liste = [45,87,79]
>>> liste[0]
45
>>>
476
40 Lösungen zu den Aufgaben
3. Aufgabe
Nehmen wir an, dass l eine Variable vom Typ list ist, dann können wir das letzte Element
am einfachsten mit l[-1] ansprechen.
Falls Sie l[len(l) - 1] als Lösung gefunden hatten, ist dies auch korrekt, aber weniger elegant
als obige Lösung.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Beispiel:
>>> l = ["Hallo", "Welt", "Das letzte Element"]
>>> l[-1]
'Das letzte Element'
>>> l[len(l) - 1]
'Das letzte Element'
>>>
4. Aufgabe
>>>
>>>
>>>
>>>
t = (4,7,9)
s = "Ich bin ein String"
l = [45,98,"787",[3,4]]
t2 = (4,8,[45,98])
>>> t[0]
4
>>> t[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
Der höchste Index des Tupels entspricht 2, sodass wir oben versucht hatten, auf einen nicht
existierenden Index zuzugreifen, und erhielten daher die Meldung „IndexError: tuple index
out of range”.
>>> t(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object is not callable
Zugriffe auf Tupel oder Listen benötigen eckige Klammern. Mit runden Klammern wird auf
Funktionen zugegriffen. Daher die Fehlermeldung „TypeError: ’tuple’ object is not callable”, d.h. t ist „nicht aufrufbar” (not callable), da es keine Funktion, sondern ein Tupel ist.
>>> s[4]
'b'
>>> s[4] = "x"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
40.1 Lösungen zu Kapitel 5 (Sequentielle Datentypen)
Strings sind unveränderlich.
>>> l[2][0] = "g"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Wir haben wieder versucht, einen String zu verändern.
>>> l[3][0] = "g"
>>> l
[45, 98, '787', ['g', 4]]
>>> t2[2][0] = 42
5. Aufgabe
Stellt man die Schrittweite des Slicing-Operators auf step = 2, kann man die beiden verborgenen Sätze extrahieren, wenn man einmal mit dem Index 0 und einmal mit dem Index 1
beginnt:
>>> s = 'DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre
lsbiscthh inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg
Mmaaxcihmte..'
>>> s[::2]
'Der ist der beste Lehrer, der sich nach und nach überflüssig macht.'
>>> s[1::2]
'In dem Sinne ist Hilfe zur Selbsthilfe immer unsere oberste Maxime.'
>>>
Anmerkung: Beachten Sie bitte, dass zwischen den einzelnen Wörtern des Strings s jeweils
zwei Leerzeichen stehen!
Vielleicht interessiert es Sie auch, wie wir den String erzeugt hatten. Um das Folgende zu
verstehen, müssen Sie allerdings das Kapitel 31 (Listen-Abstraktion/List Comprehension)
bereits bearbeitet haben.
>>> s = "Der ist der beste Lehrer, der sich nach und nach überflüssig
macht."
>>> t = "In dem Sinne ist Hilfe zur Selbsthilfe immer unsere oberste
Maxime."
>>> s = "".join(["".join(x) for x in zip(s,t)])
>>> s
'DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre lsbiscthh
inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg Mmaaxcihmte..'
>>>
477
478
40 Lösungen zu den Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
6. Aufgabe
>>>
3
>>>
20
>>>
3
>>>
1
>>>
2
>>>
len(x)
len(title)
len(address)
len(nested)
len(book)
40.2
Lösungen zu Kapitel 6 (Dictionaries)
1. Aufgabe
menu = {"en" : {"file":"File", "new":"New","open":"Open", "save":"Save
","save as":"Save as", "print preview":"Print Preview", "print":"
Print", "close":"Close", "exit":"Exit"},
"fr" : {"file":"Fichier", "new":"Nouveau","open":"Ouvrir", "
save":"Enregistrer","save as":"Enregistrer sous", "print preview
":"Apercu avant impressioner", "print":"Imprimer", "close":"Fermer
", "exit":"Quitter"},
"de" : {"file":"Datei", "new":"Neu","open":"Öffnen", "save":"
Speichern","save as":"Speichern unter", "print preview":"
Druckansicht", "print":"Drucken", "close":"Schließen", "exit":"
Verlassen"},
"it" : {"file":"File", "new":"Nuovo","open":"Apri", "save":"
Salva","save as":"Salva come", "print preview":"Anteprima di
stampa", "print":"Stampa", "close":"Chiudi", "exit":"Esci"}
}
language = input("Which language? ")
current_dic = menu[language]
print(current_dic)
Ein Aufruf des obigen Programms liefert beispielsweise folgende Ausgaben:
$ python3 nested_dictionaries.py
Which language? it
40.2 Lösungen zu Kapitel 6 (Dictionaries)
{'print preview': 'Anteprima di stampa', 'exit': 'Esci', 'save as': '
Salva come', 'file': 'File', 'close': 'Chiudi', 'print': 'Stampa',
'new': 'Nuovo', 'save': 'Salva', 'open': 'Apri'}
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
2. Aufgabe
board = { ("a",1)
("d",1)
("e",1)
("f",1)
("h",1)
("a",2)
("b",2)
("c",2)
("e",2)
("f",2)
("g",2)
("h",2)
("c",3)
("f",3)
("d",4)
("h",4)
("c",6)
("d",6)
("d",6)
("h",6)
("a",7)
("b",7)
("d",7)
("e",7)
("f",7)
("g",7)
("a",8)
("d",8)
("e",8)
("f",8)
("g",8)
("h",8)
}
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
("Turm", "weiss"),
("Dame", "weiss"),
("König", "weiss"),
("Läufer", "weiss"),
("Turm", "weiss"),
("Bauer", "weiss"),
("Bauer", "weiss"),
("Bauer", "weiss"),
("Bauer", "weiss"),
("Bauer", "weiss"),
("Bauer", "weiss"),
("Bauer", "weiss"),
("Springer", "weiss"),
("Springer", "weiss"),
("Bauer", "weiss"),
("Läufer", "weiss"),
("Bauer", "schwarz"),
("Bauer", "schwarz"),
("Läufer", "schwarz"),
("Bauer", "schwarz"),
("Bauer", "schwarz"),
("Bauer", "schwarz"),
("Springer", "schwarz"),
("Bauer", "schwarz"),
("Bauer", "schwarz"),
("Bauer", "schwarz"),
("Turm", "schwarz"),
("Dame", "schwarz"),
("König", "schwarz"),
("Läufer", "schwarz"),
("Springer", "schwarz"),
("Turm", "schwarz")
print(board[("g",8)])
Ruft man das Programm auf, erhält man folgende Ausgabe:
$ python3 chess.py
('Springer', 'schwarz')
479
480
40 Lösungen zu den Aufgaben
40.3
Lösungen zu Kapitel 9
(Verzweigungen)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
1. Aufgabe
age = int(input("Alter des Hundes: "))
print()
if age < 0:
print("Das stimmt wohl kaum!")
elif age == 1:
print("entspricht ca. 14 Jahre")
elif age == 2:
print("entspricht ca. 22 Jahre")
elif age > 2:
human = 22 + (age -2)*5
print("Menschenjahre", human)
###
input('press Return>')
2. Aufgabe
jahr = int(input("Jahreszahl: "))
if jahr % 4 == 0:
if jahr % 100 == 0:
if jahr % 400 == 0:
schaltjahr = True
else:
schaltjahr = False
else:
schaltjahr = True
else:
schaltjahr = False
if schaltjahr:
print(jahr, "ist ein Schaltjahr")
else:
print(jahr, "ist kein Schaltjahr")
Alternativ zu obiger Lösung kann man die Aufgabe auch wie folgt lösen. In diesem Fall
müssen wir die Variable schaltjahr auf False vorbesetzen:
40.3 Lösungen zu Kapitel 9 (Verzweigungen)
#!/usr/bin/python3
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
jahr = int(input("Jahreszahl: "))
schaltjahr = False
if jahr % 4 == 0:
schaltjahr = True
if jahr % 100 == 0:
schaltjahr = False
if jahr % 400 == 0:
schaltjahr = True
if schaltjahr:
print(jahr, "ist ein Schaltjahr")
else:
print(jahr, "ist kein Schaltjahr")
3. Aufgabe
hours
= int(input("hours: "))
minutes = int(input("minutes: "))
seconds = int(input("seconds: "))
if seconds == 59:
seconds = 0
if minutes == 59:
minutes = 0
if hours == 23:
hours = 0
else:
hours += 1
else:
minutes += 1
else:
seconds += 1
# Wandlung in Strings
hours = str(hours)
if len(hours) < 2:
hours = "0" + hours
minutes = str(minutes)
if len(minutes) < 2:
minutes = "0" + minutes
seconds = str(seconds)
481
482
40 Lösungen zu den Aufgaben
if len(seconds) < 2:
seconds = "0" + seconds
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
print("New time: " + hours + ":" + minutes + ":" + seconds)
Mithilfe einer formatierten Ausgabe kann man das Hinzufügen von Nullen bei einstelligen Werten einfacher realisieren. Die obige Lösung hat zum einen den Vorteil, dass sie
keinen Vorgriff auf noch nicht behandelte Themen macht, und zum anderen wird die ifAnweisung bei der Wandlung weiter vertieft. Allerdings möchten wir Ihnen die elegantere
Lösung nicht vorenthalten:
hours
= int(input("hours: "))
minutes = int(input("minutes: "))
seconds = int(input("seconds: "))
if seconds == 59:
seconds = 0
if minutes == 59:
minutes = 0
if hours == 23:
hours = 0
else:
hours += 1
else:
minutes += 1
else:
seconds += 1
print("New time: {0:02d}:{1:02d}:{2:02d}".format(hours,minutes,seconds
))
40.4
Lösungen zu Kapitel 10
(Schleifen)
1. Aufgabe
Haben Sie die den römischen Zahlen zugrunde liegenden Regeln erkannt?
Die Regeln:
1. Stehen gleiche Zahlen nebeneinander, werden sie addiert.
Beispiele: III = 3 oder XX = 20
2. Es dürfen höchstens drei Grundzahlen nebeneinander stehen. Ausnahme „M”.
40.4 Lösungen zu Kapitel 10 (Schleifen)
3. Steht eine Zahl mit einem kleineren Wert neben einer Zahl mit einem größeren Wert, so
wird der kleinere Wert vom größeren subtrahiert.
Beispiele: IV entspricht 5 - 1 (also 4) oder XL entspricht 50 - 10.
4. Die Grundzahlen I, X und C dürfen nur von der nächsthöheren Zahl (Zahlzeichen) subtrahiert werden.
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
roman_number = input("Bitte römische Zahl eingeben: ")
decimal = 0
roman_digits = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000
}
previous_value = 0
for char in roman_number:
if char in roman_digits:
if roman_digits[char] > previous_value:
decimal -= previous_value
else:
decimal += previous_value
previous_value = roman_digits[char]
decimal += previous_value
print("Ergebnis: " + str(decimal))
Ein Aufruf des Programms sieht so aus:
$ python3 roman2decimal.py
Bitte römische Zahl eingeben: MCDXLVIII
Ergebnis: 1448
2. Aufgabe
Es gibt natürlich viele Wege, diese Aufgabe zu lösen. Man hätte auch eine for-Schleife wählen können. Wir haben uns für eine Lösung entschieden, bei der wir eine while-Schleife
beenden, sobald die zurückgelegte Entfernung kleiner als eine Schranke eps ist:
sum = 0
old_sum = -1
eps = 0.0001
i = 1
483
484
40 Lösungen zu den Aufgaben
while sum - old_sum > eps:
old_sum = sum
sum += i
i = i / 2
print(i, sum)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Ausgabe dieses Programms liefert uns folgende Werte:
$ python3 frog.py | more
0.5 1
0.25 1.5
0.125 1.75
0.0625 1.875
0.03125 1.9375
0.015625 1.96875
0.0078125 1.984375
0.00390625 1.9921875
0.001953125 1.99609375
0.0009765625 1.998046875
0.00048828125 1.9990234375
0.000244140625 1.99951171875
0.0001220703125 1.999755859375
6.103515625e-05 1.9998779296875
3.0517578125e-05 1.99993896484375
Aus den numerischen Werten liegt es nahe zu folgern, dass der Frosch mit seiner Sprungweise nicht mehr als 2 Meter zurücklegen wird. Damit schafft er es also nicht, die 2,5 m
breite Straße zu überqueren.
Übrigens kann man auch den n-ten Summenwert der folgenden Reihe entnehmen:
1+
2n − 1
3 7 15
+ +
+ . . . n−1
5 4
8
2
3. Aufgabe
Eine Lösung sieht wie folgt aus:
koerner = 0
for i in range(64):
koerner += 2 ** i
print(koerner)
Startet man dieses Programm, erhält man die in der Aufgabenstellung angegebene Anzahl
von Weizenkörnern:
$ python3 weizenkorn.py
18446744073709551615
40.5 Lösungen zu Kapitel 11 (Dateien lesen und schreiben)
In obiger Lösung haben wir in jedem Schritt der Schleife eine Exponentiation, die natürlich
viel Rechenzeit benötigt. Wir können diese Exponentiation durch eine Summe ersetzen,
indem wir eine zusätzliche Variable „zuwachs” einführen, die die Menge der Weizenkörner
pro Schritt beinhaltet:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
koerner = 0
zuwachs = 1
for i in range(64):
koerner += zuwachs
zuwachs += zuwachs
print(koerner)
Aber wenn wir nun schon am „schneller machen” sind, dann machen wir es gleich richtig
schnell. Um diese Aufgabe zu lösen, haben wir uns ja bewusst unwissend gestellt. Für dieses Problem gibt es schließlich eine Formel. Die Anzahl der Körner auf n Feldern berechnet
sich als:
n
X
2i = 2n+1 − 1
i =0
Damit brauchen wir für die Lösung der Aufgabe nur noch die interaktive Python-Shell:
>>> 2 ** 64 -1
18446744073709551615
>>>
Für die mathematisch Interessierten: Allgemein gilt für die Summe von Potenzen mit konstanter Basis k und steigendem Exponenten:
n
X
i =0
ki =
k n+1 − 1
k −1
40.5
Lösungen zu Kapitel 11
(Dateien lesen und schreiben)
1. Aufgabe
Die Variable output ist ein leerer String, wenn wir uns in einer Zeile mit einem Vornamen
befinden. Befinden wir uns in einer Zeile mit einem Nachnamen, enthält die Variable output den zugehörigen Vornamen.
fobj_in = open("namen.txt")
fobj_out = open("namen2.txt", "w")
output = ""
485
486
40 Lösungen zu den Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
for line in fobj_in:
if output:
output += " " + line.rstrip() + "\n"
fobj_out.write(output)
output = ""
else:
output = line.rstrip()
fobj_in.close()
fobj_out.close()
2. Aufgabe
Die Variable „counter” dient dazu, die aktuelle Zeilennummer aufzunehmen. Immer, wenn
eine Zeilennummer durch drei teilbar ist, geben wir den Inhalt der vorhergehenden drei
Zeilen aus. Die einzige Ausnahme stellt die Zeilennummer 0 dar, denn in diesem Fall kann
noch nichts ausgegeben werden.
fh = open("musicians.txt")
counter = 0
new_line = ""
for line in fh:
line = line.rstrip()
if counter % 3 == 0:
if counter:
print(new_line)
_
new line = line
elif counter % 3 == 1:
new_line += " " + line
else:
new_line += " (" + line + ")"
counter += 1
fh.close()
3. Aufgabe
fh_in = open("musicians.txt")
fh_out = open("musicians2.txt", "w")
counter = 0
new_line = ""
for line in fh_in:
line = line.rstrip()
if counter % 3 == 0:
if counter:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
40.6 Lösungen zu Kapitel 16 (Listen und Tupel im Detail)
fh_out.write(new_line + "\n")
new_line = line
elif counter % 3 == 1:
new_line += " " + line
else:
new_line += " (" + line + ")"
counter += 1
fh_out.close()
fh_in.close()
40.6
Lösungen zu Kapitel 16
(Listen und Tupel im Detail)
1. Aufgabe
Die folgende Funktion „rev” kehrt die Reihenfolge einer Liste um. Die Funktion ist „destruktiv”, d.h. sie leert die Eingabeliste.
def rev(lst):
revl = []
while lst:
el = lst.pop()
revl.append(el)
return revl
liste = ["a","b","c","d"]
liste = rev(liste)
print liste
2. Aufgabe
def flatten(x):
"""flatten(sequence) -> list"""
result = []
for el in x:
#if isinstance(el,(list, tuple)):
if (type(el) == list) or (type(el) == tuple):
result.extend(flatten(el))
else:
result.append(el)
return result
487
488
40 Lösungen zu den Aufgaben
liste = [["test", ["noch tiefer", "auch"]],1,(2,(2,3,4))]
print(flatten(liste))
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Obige Funktion testen wir in der Python-Shell:
bernd@saturn:~/bodenseo/python/beispiele$ python3
Python 3.2.3 (default, Oct 19 2012, 19:53:57)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from flatten import flatten
>>> liste = [["test", ["noch tiefer", "auch"]],1,(2,(2,3,4))]
>>> flatten(liste)
['test', 'noch tiefer', 'auch', 1, 2, 2, 3, 4]
>>>
3. Aufgabe
>>> l = [3,31,33,4,41,2,1,10,22]
>>> sorted(l, key = str)
[1, 10, 2, 22, 3, 31, 33, 4, 41]
4. Aufgabe
def letter_frequency(line):
relevant_letters = "abcdefghijklmnopqrstuvwxyz"
frequency_dict = {}
for char in line.lower():
if char in relevant_letters:
if char in frequency_dict:
frequency_dict[char] += 1
else:
frequency_dict[char] = 1
f = list(frequency_dict.items())
f.sort(key = lambda x: x[1],reverse=True)
return f
txt = letter_frequency(open("ulysses.txt").read())
print(txt)
Started man dieses Programm, erhält man die Buchstabenhäufigkeiten des Romans „Ulysses” von James Joyce:
40.6 Lösungen zu Kapitel 16 (Listen und Tupel im Detail)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
bernd@saturn:~/bodenseo/python/beispiele$ python3 letter_frequency.py
[('e', 143221), ('t', 101654), ('a', 94103), ('o', 92704), ('i',
82494), ('n', 81133), ('s', 77656), ('h', 73053), ('r', 70986), ('
l', 55522), ('d', 49609), ('u', 33767), ('m', 31891), ('c', 30481)
, ('g', 28189), ('f', 27016), ('w', 26424), ('y', 24571), ('p',
22879), ('b', 21413), ('k', 12196), ('v', 9865), ('j', 2404), ('x
', 1464), ('q', 1343), ('z', 1079)]
bernd@saturn:~/bodenseo/python/beispiele$
5. Aufgabe
Wir geben zuerst die elegantere Lösung mit der lambda-Notation. Weiter unten finden Sie
eine Lösung ohne lambda:
umsaetze = [ ('John', 'Miller', 46, 18.67),
('Randy', 'Steiner', 48, 27.99),
('Tina', 'Baker', 53, 27.23),
('Andrea', 'Baker', 40, 31.75),
('Eve', 'Turner', 44, 18.99),
('Henry', 'James', 50, 23.56)]
print("Sortiert nach Verkaufserlös:")
for i in sorted(umsaetze, key=lambda x: x[2] * x[3]):
print(i)
print("\nSortiert nach Nachname und Vorname:")
for i in sorted(umsaetze, key=lambda x: x[1] + x[0]):
print(i)
Speichern wir das obige Programm unter „umsaetze.py”, erhalten wir folgende Ausgaben
beim Start:
bernd@venus:~/bodenseo/python/beispiele$ python3 umsaetze.py
Sortiert nach Verkaufserlös:
('Eve', 'Turner', 44, 18.99)
('John', 'Miller', 46, 18.67)
('Henry', 'James', 50, 23.56)
('Andrea', 'Baker', 40, 31.75)
('Randy', 'Steiner', 48, 27.99)
('Tina', 'Baker', 53, 27.23)
Sortiert nach Nachname und Vorname:
('Andrea', 'Baker', 40, 31.75)
('Tina', 'Baker', 53, 27.23)
('Henry', 'James', 50, 23.56)
('John', 'Miller', 46, 18.67)
('Randy', 'Steiner', 48, 27.99)
489
490
40 Lösungen zu den Aufgaben
('Eve', 'Turner', 44, 18.99)
bernd@venus:~/bodenseo/python/beispiele$
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Das folgende Programm stellt eine Lösung ohne die Verwendung von lambda dar. Wir müssen deshalb explizit zwei Funktionen definieren. Sie finden auch diese Lösung in unserem
Beispielverzeichnis unter dem Namen „umsaetze2.py”:
umsaetze = [ ('John', 'Miller', 46, 18.67),
('Randy', 'Steiner', 48, 27.99),
('Tina', 'Baker', 53, 27.23),
('Andrea', 'Baker', 40, 31.75),
('Eve', 'Turner', 44, 18.99),
('Henry', 'James', 50, 23.56)]
def gesamtpreis(x):
return x[2] * x[3]
def name(x):
return x[1] + x[0]
print("Sortiert nach Verkaufserlös:")
for i in sorted(umsaetze, key=gesamtpreis):
print(i)
print("\nSortiert nach Nachname und Vorname:")
for i in sorted(umsaetze, key=name):
print(i)
40.7
Lösungen zu Kapitel 14
(Funktionen)
1. Aufgabe
Die Funktion in Python:
def txt2loeffelsprache(text,
vokale = {"a", "e", "i", "o", "u", "ä", "ö", "ü
"},
diphtonge = {"ei", "au", "ie", "eu"},
fueller = "lef"):
text = text.lower()
kodiert = ""
while text:
first2 = text[:2] # ein oder zwei Zeichen
first = text[0]
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
40.7 Lösungen zu Kapitel 14 (Funktionen)
if first2 in diphtonge:
kodiert += first2 + fueller + first2
text = text[2:]
elif first in vokale:
kodiert += first + fueller + first
text = text[1:]
else:
kodiert += first
text = text[1:]
return kodiert
words = ["Löffelsprache", "Donauauen", "zweieiig", "Treueeid"]
for word in words:
print(txt2loeffelsprache(word))
print("\nUnd nun mit anderer Füllsilbe:")
for word in words:
print(txt2loeffelsprache(word, fueller="lol"))
Der Wert der Variablen first2 besteht aus den ersten beiden Buchstaben des Strings text,
außer wenn Text nur aus einem Buchstaben besteht. In letzterem Fall liefert text[:2] den
String text komplett zurück, also den einen Buchstaben.
Starten wir obiges Programm, erhalten wir folgende Ausgabe:
$ python3 loeffelsprache.py
Löleföffelefelspralefachelefe
Dolefonalefaulefualefaulefuelefen
zwelefeilefielefeilefiilefig
Trelefeulefuelefeelefeilefid
Und nun mit anderer Füllsilbe:
Lölolöffelolelspralolachelole
Dololonalolaulolualolauloluelolen
zweloleilolieloleiloliilolig
Treloleulolueloleeloleilolid
2. Aufgabe
Wir benutzen zum Test der Funktion loeffelsprache2txt die Funktion txt2loeffelsprache, d.h. wir prüfen mit word.lower() == loeffelsprache2txt(loeffel), ob sich
ein mittels txt2loeffelsprache gewandelter Text wieder zurückwandeln lässt.
def txt2loeffelsprache(text,
vokale = {"a", "e", "i", "o", "u", "ä", "ö", "ü
"},
diphtonge = {"ei", "au", "ie", "eu"},
fueller = "lef"):
491
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
492
40 Lösungen zu den Aufgaben
text = text.lower()
kodiert = ""
while text:
first2 = text[:2] # ein oder zwei Zeichen
first = text[0]
if first2 in diphtonge:
kodiert += first2 + fueller + first2
text = text[2:]
elif first in vokale:
kodiert += first + fueller + first
text = text[1:]
else:
kodiert += first
text = text[1:]
return kodiert
def loeffelsprache2txt(loeffeltext,
vokale = {"a", "e", "i", "o", "u", "ä", "ö", "ü
"},
diphtonge = {"ei", "au", "ie", "eu"},
fueller = "lef"):
txt = loeffeltext
for diphtong in list(diphtonge):
txt = txt.replace(diphtong + fueller + diphtong, diphtong)
for vokal in list(vokale):
txt = txt.replace(vokal + fueller + vokal, vokal)
return txt
words = ["Löffelsprache", "Donauauen", "zweieiig", "Treueeid"]
for word in words:
loeffel = txt2loeffelsprache(word)
print(loeffel, loeffelsprache2txt(loeffel))
if word.lower() == loeffelsprache2txt(loeffel):
print("okay")
else:
print("Problem: " + loeffel)
3. Aufgabe
Die einfachste Lösung lässt sich unter Benutzung des Teilbereichsoperators mit negativem
dritten Index erzielen:
def is_palindrome(word):
word = word.lower()
return word == word[::-1]
40.7 Lösungen zu Kapitel 14 (Funktionen)
Man kann diesen Test auch über direkte Buchstabenvergleiche durchführen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def is_palindrome(word):
word = word.lower()
for i in range(len(word)//2):
if word[i] != word[-(i+1)]:
return False
return True
4. Aufgabe
In der folgenden Funktion epalindrome vergleichen wir jeweils den Buchstaben ganz links
mit dem am weitesten rechts stehenden Buchstaben. Um diese Buchstaben zu finden,
überlesen wir zuvor in den beiden inneren while-Schleifen solange Sonderzeichen und
Leerzeichen, bis wir auf einen Buchstaben stoßen.
def is_epalindrome(phrase):
left = 0
right = -1
middle = len(phrase) // 2
phrase = phrase.lower()
while True:
while left <= middle and not phrase[left].isalpha():
left += 1
while -right <= middle and not phrase[right].isalpha():
right -= 1
if left <= middle and -right <= middle:
if phrase[left] != phrase[right]:
return False
left, right = left + 1, right - 1
else:
return True
return True
Mit integriertem Test sieht unsere Funktion wie folgt aus:
def is_epalindrome(phrase):
left = 0
right = -1
middle = len(phrase) // 2
phrase = phrase.lower()
while True:
while left <= middle and not phrase[left].isalpha():
left += 1
while -right <= middle and not phrase[right].isalpha():
right -= 1
if left <= middle and -right <= middle:
493
494
40 Lösungen zu den Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
if phrase[left] != phrase[right]:
return False
left, right = left + 1, right - 1
else:
return True
return True
words = ("Anna", "Lager", "Reittier", "Lagerregal", "x", "aka")
phrases = (
"Ein Eis esse sie nie.",
"Ein Esel lese nie.",
"Nein, kann Anna knien?",
"To be or not to be!",
"Oh, Cello voll Echo!",
"!!", ".", ",d!.,", "d,....!d,,!", ",!", "a,", ",a"
)
for phrase in phrases + words:
print(phrase, end=" ist ")
if is_epalindrome(phrase):
print("ein Palindrom!")
else:
print("kein Palindrom!")
Ausgabe:
$ python3 palindrome.py
Ein Eis esse sie nie. ist ein Palindrom!
Ein Esel lese nie. ist ein Palindrom!
Nein, kann Anna knien? ist ein Palindrom!
To be or not to be! ist kein Palindrom!
Oh, Cello voll Echo! ist ein Palindrom!
!! ist ein Palindrom!
. ist ein Palindrom!
,d!., ist ein Palindrom!
d,....!d,,! ist ein Palindrom!
,! ist ein Palindrom!
a, ist ein Palindrom!
,a ist ein Palindrom!
Anna ist ein Palindrom!
Lager ist kein Palindrom!
Reittier ist ein Palindrom!
Lagerregal ist ein Palindrom!
x ist ein Palindrom!
aka ist ein Palindrom!
40.8 Lösungen zu Kapitel 15 (Rekursive Funktionen)
40.8
Lösungen zu Kapitel 15
(Rekursive Funktionen)
1. Aufgabe
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Mathematisch können wir es wie folgt formulieren:
f(1) = 3,
f(n+1) = f(n) + 3
Die Funktion in Python:
def mult3(n):
if n == 1:
return 3
else:
return mult3(n-1) + 3
for i in range(1,10):
print(mult3(i))
2. Aufgabe
def sum_n(n):
if n== 0:
return 0
else:
return n + sum_n(n-1)
3. Aufgabe
def pascal(n):
if n == 1:
return [1]
else:
line = [1]
previous_line = pascal(n-1)
for i in range(len(previous_line)-1):
line.append(previous_line[i] + previous_line[i+1])
line += [1]
return line
print(pascal(6))
495
496
40 Lösungen zu den Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Alternativ können wir eine Lösung mit List Comprehension schreiben:
def pascal(n):
if n == 1:
return [1]
else:
p_line = pascal(n-1)
line = [ p_line[i]+p_line[i+1] for i in range(len(p_line)-1)]
line.insert(0,1)
line.append(1)
return line
print(pascal(6))
4. Aufgabe
def fib_pascal(n,fib_pos):
if n == 1:
line = [1]
fib_sum = 1 if fib_pos == 0 else 0
else:
line = [1]
(previous_line, fib_sum) = fib_pascal(n-1, fib_pos+1)
for i in range(len(previous_line)-1):
line.append(previous_line[i] + previous_line[i+1])
line += [1]
if fib_pos < len(line):
fib_sum += line[fib_pos]
return (line, fib_sum)
def fib(n):
return fib_pascal(n,0)[1]
# and now printing out the first ten Fibonacci numbers:
for i in range(1,10):
print(fib(i))
5. Aufgabe
Iterative Lösung für das Sieb des Eratosthenes für die ersten 100 Zahlen:
from math import sqrt
def sieve(n):
# returns all primes between 2 and n
primes = list(range(2,n+1))
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
40.8 Lösungen zu Kapitel 15 (Rekursive Funktionen)
max = sqrt(n)
num = 2
while num < max:
i = num
while i <= n:
i += num
if i in primes:
primes.remove(i)
for j in primes:
if j > num:
num = j
break
return primes
print(sieve(100))
In diesem Kapitel geht es jedoch um rekursive Funktionen, und in der Aufgabenstellung
haben wir auch bei der Berechnung der Primzahlen Rekursion verlangt. Um die folgende
Lösung besser verstehen zu können, empfehlen wir, unser Kapitel über Listen-Abstraktion
(List Comprehension) zurate zu ziehen:
from math import sqrt
def primes(n):
if n == 0:
return []
elif n == 1:
return []
else:
p = primes(int(sqrt(n)))
no_p = [j for i in p for j in range(i*2, n + 1, i)]
p = [x for x in range(2, n + 1) if x not in no_p]
return p
print(primes(100))
6. Aufgabe
memo = {0:0, 1:1}
def fib(n):
if not n in memo:
memo[n] = fib(n-1) + fib(n-2)
return memo[n]
def find_index(*x):
""" finds the natural number i with fib(i) = n """
497
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
498
40 Lösungen zu den Aufgaben
if len(x) == 1:
# started by user
# find index starting from 0
return find_index(x[0],0)
else:
n = fib(x[1])
m = x[0]
if n > m:
return -1
elif n == m:
return x[1]
else:
return find_index(m,x[1]+1)
7. Aufgabe
# code from the previous example with the functions fib() and
find_index()
print(" index of a |
a |
b | sum of squares | index ")
print("=====================================================")
for i in range(15):
square = fib(i)**2 + fib(i+1)**2
print( " %10d | %3d |
%3d | %14d | %5d " % (i, fib(i), fib(i+1)
, square, find_index(square)))
Die Ausgabe für das vorige Skript sieht wie folgt aus:
index of a |
a |
b | sum of squares | index
=====================================================
0 |
0 |
1 |
1 |
1
1 |
1 |
1 |
2 |
3
2 |
1 |
2 |
5 |
5
3 |
2 |
3 |
13 |
7
4 |
3 |
5 |
34 |
9
5 |
5 |
8 |
89 |
11
6 |
8 |
13 |
233 |
13
7 |
13 |
21 |
610 |
15
8 |
21 |
34 |
1597 |
17
9 |
34 |
55 |
4181 |
19
10 |
55 |
89 |
10946 |
21
11 |
89 |
144 |
28657 |
23
12 | 144 |
233 |
75025 |
25
13 | 233 |
377 |
196418 |
27
14 | 377 |
610 |
514229 |
29
40.9 Lösungen zu Kapitel 19 (Alles über Strings . . . )
40.9
Lösungen zu Kapitel 19
(Alles über Strings . . . )
1. Aufgabe
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Die Lösung besteht darin, dass man zweistufig split anwendet:
fh = open("addresses.txt", encoding="iso-8859-1")
for address in fh:
address = address.strip()
(lastname, firstname, city, phone) = address.split(';')
print(firstname + " " + lastname + ", " + city + ", " +
phone)
Die Ausgabe des Programms „split_addresses_mixed.py”:
bernd@saturn:~/bodenseo/python/beispiele$ python3
split_addresses_mixed.py
Meyer Frank, Radolfzell, 07732/43452
Rabe Peter, Konstanz, 07531/70021
Huber Ottmar, Rosenheim, 08031/7877-0
Rabe Anna, Radolfzell, 07732/2343
Lindner Oskar, Konstanz, 07531/890
List Anna, München, 089/3434544
List Anna, München, 089/3434544
Huber Franziska, Rosenheim, 08031/787878
Rabe Sarah, Konstanz, 07531/343454
bernd@saturn:~/bodenseo/python/beispiele$
2. Aufgabe
Die Lösung besteht darin, dass wir einmal ein normales split mit maxsize = 1 und ein rsplit
mit maxsize = 1 aufrufen:
fh = open("addresses.txt", encoding="iso-8859-1")
l = []
for address in fh:
address = address.strip()
t = (address.split(';',1)[0], address.rsplit(";",1)[1])
l.append(t)
print(l)
499
500
40 Lösungen zu den Aufgaben
Alternativ kann man auch ein split ohne maxsize verwenden und dann auf den ersten und
letzten Index zugreifen:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
fh = open("addresses.txt", encoding="iso-8859-1")
l = []
for address in fh:
address = address.strip()
t = address.split(';')
l.append((t[0], t[-1]))
print(l)
3. Aufgabe
>>> s = """Hat der alte Hexenmeister\nSich doch einmal wegbegeben!\r\
nUnd nun sollen seine Geister\rAuch nach meinem Willen leben.\n"""
>>> s.splitlines()
['Hat der alte Hexenmeister', 'Sich doch einmal wegbegeben!', 'Und nun
sollen seine Geister', 'Auch nach meinem Willen leben.']
>>> s = "\n".join(s.splitlines()) + "\n"
>>> s
'Hat der alte Hexenmeister\nSich doch einmal wegbegeben!\nUnd nun
sollen seine Geister\nAuch nach meinem Willen leben.\n'
>>> print(s)
Hat der alte Hexenmeister
Sich doch einmal wegbegeben!
Und nun sollen seine Geister
Auch nach meinem Willen leben.
>>>
4. Aufgabe
def findnth(str, substr, n):
num = 0
start = -1
while num < n:
start = str.find(substr, start+1)
if start == -1:
return -1
num += 1
return start
40.9 Lösungen zu Kapitel 19 (Alles über Strings . . . )
if __name__ == "__main__":
s = "abc xyz abc xyz abc xyz"
print(findnth(s, "xyz", 1))
print(findnth(s, "xyz", 3))
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Dieses Programm befindet sich in unserem Beispielverzeichnis unter „findnth.py”. Startet
man das Programm, erhält man folgende Ausgaben:
bernd@saturn:~/bodenseo/python/beispiele$ python3 findnth.py
4
20
bernd@saturn:~/bodenseo/python/beispiele$
5. Aufgabe
Für die Funktion „replacenth” benutzen wir die Funktion „findnth”, die wir im vorigen Beispiel erstellt hatten, um die Position des n-ten Vorkommens zu ermitteln. Für die eigentliche Ersetzung brauchen wir dann lediglich Slicing und die Konkatenation von Strings:
def findnth(str, substr, n):
num = 0
start = -1
while num < n:
start = str.find(substr, start+1)
if start == -1: return -1
num += 1
return start
def replacenth(source, search, replacement, n):
pos = findnth(source, search, n)
if pos == -1: return source
return source[:pos] + replacement + source[pos+len(search):]
Beispiel für die Anwendung der Funktion „replacenth”:
bernd@saturn:~/bodenseo/python/beispiele$ python3
Python 3.2.3 (default, Oct 19 2012, 19:53:57)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from replacenth import replacenth
>>> replacenth("And yes he said yes!","yes","no",2)
'And yes he said no!'
>>> replacenth("And yes he said yes!","yes","no",1)
'And no he said yes!'
>>>
501
502
40 Lösungen zu den Aufgaben
40.10
Lösungen zu Kapitel 21
(Grundlegende Aspekte)
1. Aufgabe
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Als Lösung bietet sich natürlich an, das Attribut x in eine Property zu wandeln.
class Robot(object):
def __init__(self, name):
self.name = name
def __GetName(self):
return self.__name
def __SetName(self, name):
if name == "Hugo":
self.__name = "Marvin"
else:
self.__name = name
name = property(__GetName, __SetName)
x = Robot("Marvin")
y = Robot("Hugo")
print(x.name, y.name)
2. Aufgabe
Ein Objekt der Klasse „robot” wird von den Attributen self.position, self.orientation und
self.name definiert:
self.position enthält die Position eines Roboters in einem zweidimensionalen Koordinatensystem. Die Koordinate wird in Form einer Zweierliste gespeichert.
self.orientation Ein Roboter kann in eine der vier Richtungen „north”, „east”, „south” und
„west” gerichtet sein. Das ist dann auch die Richtung, in die er sich bewegt, wenn man
die Methode move auf ihn anwendet.
self.name Alle Dinge sollten einen Namen haben, so auch unsere Robot-Objekte. Ein Name darf aber nicht mehr als 10 Zeichen enthalten, was wir in einer Property prüfen.
Enthält er mehr als 10 Zeichen, reduzieren wir den Namen auf 10 Zeichen.
40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte)
"""
Roboterklasse zur Positionsbeschreibung und Veränderung von Objekten
in einem zweidimensionalen Koordinatensystem.
"""
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class Robot(object):
def __init__(self, x=0, y=0, orientation = "north", name="marvin")
:
self.position = [x,y]
self.orientation = orientation
self.name = name
def __repr__(self):
s = "Robot(" + str(self.position[0]) + ', ' + str(self.
position[1])
s += ', "' + self.orientation + '"'
s += ', "'
+ self.name + '")'
return s
def __str__(self):
""" Stringdarstellung einer Instanz """
s = self.name + " faces " + self.orientation
s += " at position " + str(self.position)
return s
def move(self, distance):
""" Methode zum Bewegen eines Roboters in Richtung seiner
aktuellen Orientierung.
Wird ein Roboter x mit x.move(10) aufgerufen und ist dieser
Roboter östlich orientiert, also x.getPosition() == ,,east''
und ist beispielsweise [3,7] die aktuelle Position des
Roboters, dann bewegt er sich 10 Felder östlich und befindet
sich anschließend in Position [3,17].
"""
if self.orientation == "north":
self.position[1] += distance
elif self.orientation == "south":
self.position[1] -= distance
elif self.orientation == "west":
self.position[0] -= distance
elif self.orientation == "east":
self.position[0] -= distance
@property
def orientation(self):
503
504
40 Lösungen zu den Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
""" Die Property orientation enthält die aktuelle Orientierung
, d.h.
eine der Richtungen "west", "south", "east" oder "north
"."""
return self.__orientation
@orientation.setter
def orientation(self, orientation):
""" Mit der Methode newOrientation ändern wir die Orientierung
des Roboters. Mögliche Orientierungen sind "north","south","
west","east"
"""
if orientation in {"north","south","west","east"}:
self.__orientation = orientation
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
max_name_len = 10
if len(name) > max_name_len:
self.__name = name[:max_name_len]
else:
self.__name = name
if __name__ == "__main__":
x = Robot(3, 4, "north", "Marvin")
print(x)
x.move(10)
x.orientation = "west"
x.move(7)
print(x)
new_name = "Alexandria-Maria"
x.name = new_name # name ist zu lang, wird abgeschnitten
print("Hi, this is " + x.name)
print(x)
print(repr(x))
# Roboter x wird mittels repr geklont:
y = eval(repr(x))
print(y)
Ruft man obiges Modul direkt auf, erhält man folgende Ausgaben:
40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
$ python3 robot.py
Marvin faces north at position [3, 4]
Marvin faces west at position [-4, 14]
Hi, this is Alexandria
Alexandria faces west at position [-4, 14]
Robot(-4, 14, "west", "Alexandria")
Alexandria faces west at position [-4, 14]
3. Aufgabe
from datetime import date
today = date.today()
class Roboter:
def __init__(self, name, baujahr, energie=100 ):
self.name = name
self.baujahr = baujahr
self.energie = energie
def __befinden(self):
alter = today.year - self.baujahr
if alter > 20:
if self.energie < 50:
return "In Anbetracht meines Alters nicht so schlecht
!"
else:
return "Super. Vor allem in Anbetracht meines Alters!"
else:
if self.energie < 50:
return "Ausgelaugt"
else:
return "Super!"
befinden = property(__befinden)
if __name__ == "__main__":
a = Roboter("Marvin", 1979, 49)
b = Roboter("Caliban", 1993, 53)
c = Roboter("Awesom-O", 2004, 90)
d = Roboter("Clank", 2002, 30)
print(a.befinden)
print(b.befinden)
print(c.befinden)
print(d.befinden)
505
506
40 Lösungen zu den Aufgaben
Das Programm liefert folgende Ausgaben:
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
$ python3 robot_energie.py
In Anbetracht meines Alters nicht so schlecht!
Super. Vor allem in Anbetracht meines Alters!
Super!
Ausgelaugt
4. Aufgabe
"""
This module is implementing a Cave Man arithmetic "CaveInt",
the simplest numeral system representing the natural numbers.
A number n is represented by a string consisting of n tally
marks (vertical strokes). The number 0 - completely unknown
to Neanderthal Man is - is represented by an empty string
"""
class CaveInt(object):
def __init__(self,n):
""" A CaveInt can be either initialized by an integer or
by a string consisting solely of tally marks.
The number 0 and negative numbers are turned into an
empty string. A positive integer n will be turned into
a string with n tally marks.
"""
self.number = ""
if type(n) == int:
self.number = "|" * n
elif type(n) == str:
if len(n) == n.count("|"):
self.number = n
def __str__(self):
""" String representation of a CaveInt """
return self.number
def __int__(self):
""" A CaveInt is converted into an integer """
return len(self.number)
def __add__(self, other):
""" Addition of two CaveInt numbers """
40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
res = CaveInt(self.number + other.number)
return res
def __sub__(self, other):
""" Subtraction of two CaveInt numbers """
diff = int(self) - int(other)
diff = diff if diff > 0 else 0
res = CaveInt(diff)
return res
def __mul__(self,other):
""" Multiplication of two CaveInt numbers """
res = CaveInt(self.number * len(other.number))
return res
def __truediv__(self,other):
""" Division is of course integer division """
d = int(len(self.number) / len(other.number))
res = CaveInt(d)
return res
if __name__ == "__main__":
x = CaveInt("||||")
y = CaveInt(12)
print(x,y)
z = x + y
print(z)
z = x - y
print(z)
z = y - x
print(z)
z = CaveInt("")
print(z)
z = CaveInt(0)
print(z)
z = y / x
print(z)
z = x * y
print(z, int(z))
507
508
40 Lösungen zu den Aufgaben
5. Aufgabe
class Plist(list):
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __init__(self, l):
list.__init__(self, l)
def push(self, item):
self.append(item)
def splice(self, offset, length, replacement):
self[offset:offset+length] = replacement
if __name__ == "__main__":
x = Plist([33,456,8,34,99])
x.push(47)
print(x)
x.splice(2,3,["Hey", "you"])
print(x)
Startet man das Programm, erhält man folgende Ausgabe:
$ python3 standardklassen.py
[33, 456, 8, 34, 99, 47]
[33, 456, 'Hey', 'you', 47]
6. Aufgabe
class Length:
__metric = { "mm" : 0.001,
"cm" : 0.01,
"m" : 1,
"km" : 1000,
"in" : 0.0254,
"ft" : 0.3048,
"yd" : 0.9144,
"mi" : 1609.344 }
def __init__(self, value, unit = "m" ):
self.value = value
self.unit = unit
def Converse2Metres(self):
return self.value * Length.__metric[self.unit]
40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def __add__(self, other):
if type(other) == int or type(other) == float:
l = self.Converse2Metres() + other
else:
l = self.Converse2Metres() + other.Converse2Metres()
return Length(l / Length.__metric[self.unit], self.unit )
def __iadd__(self, other):
if type(other) == int or type(other) == float:
l = self.Converse2Metres() + other
else:
l = self.Converse2Metres() + other.Converse2Metres()
self.value = l / Length.__metric[self.unit]
return self
def __sub__(self, other):
if type(other) == int or type(other) == float:
l = self.Converse2Metres() - other
else:
l = self.Converse2Metres() - other.Converse2Metres()
return Length(l / Length.__metric[self.unit], self.unit )
def __isub__(self, other):
if type(other) == int or type(other) == float:
l = self.Converse2Metres() - other
else:
l = self.Converse2Metres() - other.Converse2Metres()
self.value = l / Length.__metric[self.unit]
return self
def __mul__(self, other):
if type(other) == int or type(other) == float:
l = self.Converse2Metres() * other
else:
l = self.Converse2Metres() * other.Converse2Metres()
return Length(l / Length.__metric[self.unit], self.unit )
def __imul__(self, other):
if type(other) == int or type(other) == float:
l = self.Converse2Metres() * other
else:
l = self.Converse2Metres() * other.Converse2Metres()
self.value = l / Length.__metric[self.unit]
return self
def __truediv__(self, other):
if type(other) == int or type(other) == float:
l = self.Converse2Metres() / other
509
510
40 Lösungen zu den Aufgaben
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
else:
l = self.Converse2Metres() / other.Converse2Metres()
return Length(l / Length.__metric[self.unit], self.unit )
def __itruediv__(self, other):
if type(other) == int or type(other) == float:
l = self.Converse2Metres() / other
else:
l = self.Converse2Metres() / other.Converse2Metres()
self.value = l / Length.__metric[self.unit]
return self
def __radd__(self, other):
return Length.__add__(self,other)
def __rsub__(self, other):
return Length.__sub__(self,other)
def __rmul__(self, other):
return Length.__mul__(self,other)
def __rtrudiv__(self, other):
return Length.__truediv__(self,other)
def __str__(self):
return str(self.Converse2Metres())
def __repr__(self):
return str((self.value, self.unit))
if __name__ == "__main__":
x = Length(4)
print(x)
print(repr(x))
print(x + 5.6)
print(7 + x)
x += Length(4, "yd")
print(x)
x += 4
x -= Length(4, "m")
print(x)
z = 4 + Length(8,"yd")
y = Length(4.5, "yd") + x / Length(0.77,"in")
print(repr(y))
print(y)
40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte)
7. Aufgabe
from abc import ABC, abstractmethod
from math import pi
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
class BasisFigure(ABC):
def __init__(self, size1, size2):
self.size1 = size1
self.size2 = size2
super().__init__()
@abstractmethod
def area(self):
pass
@abstractmethod
def circumference(self):
pass
class Rectangle(BasisFigure):
def area(self):
return self.size1 * self.size2
def circumference(self):
return 2 * (self.size1 + self.size2)
class Circle(BasisFigure):
def __init__(self, radius):
super().__init__(radius, None)
def area(self):
return self.size1 * pi ** 2
def circumference(self):
return 2 * pi * self.size1
x = Rectangle(2, 3)
y = Circle(3)
for obj in (x, y):
print(obj.area(), obj.circumference())
Dieses Programm liefert die folgenden Ergebnisse zurück:
6 10
29.608813203268074 18.84955592153876
511
512
40 Lösungen zu den Aufgaben
40.11
Lösungen zu Kapitel 34
(Tests und Fehler)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
1. Aufgabe
Die Formulierungen für den Doctest sind in Ordnung. Das Problem liegt in der Implementierung der Fibonacci-Funktion. Diese rekursive Lösung ist höchst ineffizient. Sie müssen
nur Geduld haben, bis der Test terminiert. Wie viele Stunden, Tage oder Wochen Sie warten
müssen, hängt von Ihrem Rechner ab ,.
40.12
Lösungen zu Kapitel 36
(Reguläre Ausdrücke)
1. Aufgabe
(1)
(2)
(3)
(4)
(5)
(6)
abc
ac
abbb
bbc
aabcd
b
(a)
(b)
(c)
(d)
(e)
(f)
(g)
ab+c?
a?b*c
b+c*
^b+c*$
a.+b?c
b{2,}c?
^a{1,2}b+.?d*
(a) matcht 1, 3, 5
(b) matcht 1, 2, 4, 5
(c) matcht 1, 3, 4, 5, 6
(d) matcht 4, 6
(e) matcht 1, 5
(f) matcht 3, 4
(g) matcht 1, 3, 5
2. Aufgabe
Die Frage lautete, auf welchen der Strings
(1)
(2)
(3)
(4)
(5)
xx
xyxyxyx
xxyxy
xyxx
xxyxyx
40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke)
der Ausdruck x(xy)*x passt.
Vielleicht hatten Sie irrtümlich gedacht, dass das Fragezeichen zum regulären Ausdruck
gehört. Es gehört natürlich zur Frage.
Antwort: Nur 2 matcht nicht, denn in allen anderen kommt der Teilausdruck „xx” vor!
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Würde das Fragezeichen zum Ausdruck gehören, würde der reguläre Ausdruck auf alle
Strings passen.
3. Aufgabe
Ein regulärer Ausdruck, der das Problem löst, lautet (wenn man annimmt, dass der ZipCode am Ende einer Zeile stehen muss, so wie es bei US-Briefen der Fall ist):
r"\b[0-9]{5}(-?[0-9]{4})?$"
Ein kleines Testprogramm finden Sie im Folgenden:
import re
destinations = ["Minneapolis, MN 55416",
"Defreestville NY 12144-7050",
"Anchorage AK 99577-0727",
"Juneau AK 99750-0077",
"Mountain View, CA 94043",
"Atlanta, GA 30309",
"Washington TX 778800305"]
erroneous = ["Minneapolis, MN 554169",
"Defreestville NY 12144-705",
"Anchorage AK 9957-0727",
"Juneau AK 99750--0077",
"Mountain View, CA 94 043",
"Atlanta, GA 3030-12345",
"Washington TX 778-800305"]
for dest in destinations + erroneous:
if re.search(r"[^-\d][0-9]{5}(-?[0-9]{4})?$",dest):
print(dest + " okay")
else:
print(dest + " not okay")
Startet man obiges Programm, erhält man folgende Ausgabe:
$ python3 us_zip_codes.py
Minneapolis, MN 55416 okay
Defreestville NY 12144-7050 okay
Anchorage AK 99577-0727 okay
Juneau AK 99750-0077 okay
513
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
514
40 Lösungen zu den Aufgaben
Mountain View, CA 94043 okay
Atlanta, GA 30309 okay
Washington TX 778800305 okay
Minneapolis, MN 554169 not okay
Defreestville NY 12144-705 not okay
Anchorage AK 9957-0727 not okay
Juneau AK 99750--0077 not okay
Mountain View, CA 94 043 not okay
Atlanta, GA 3030-12345 not okay
Washington TX 778-800305 not okay
4. Aufgabe
>>> import re
>>> x = open("ulysses.txt").read()
>>> words = set(re.findall(r"\b(\w+ship)\b", x))
>>> words
{'pumpship', 'courtship', 'battleship', 'lordship', 'marksmanship', '
pleasureship', 'chelaship', 'fellowship', 'lightship', 'fathership
', 'Lordship', 'debtorship', 'consulship', 'township', 'friendship
', 'worship', 'steamship'}
>>> len(words)
17
>>>
5. Aufgabe
>>> import re
>>> x = open("ulysses.txt").read()
>>> words = set(re.findall(r"\b((\w{2,})\2)\b", x))
>>> words
{('eeee', 'ee'), ('cancan', 'can'), ('dumdum', 'dum'), ('
schschschschschsch', 'schschsch'), ('lala', 'la'), ('ooddleooddle
', 'ooddle'), ('chinchin', 'chin'), ('chokeechokee', 'chokee'), ('
murmur', 'mur'), ('bangbang', 'bang'), ('nono', 'no'), ('
hellohello', 'hello'), ('nyumnyum', 'nyum'), ('rara', 'ra'), ('
tartar', 'tar'), ('hiphip', 'hip'), ('geegee', 'gee'), ('jamjam',
'jam'), ('poohpooh', 'pooh'), ('addleaddle', 'addle'), ('usus', '
us'), ('papa', 'pa'), ('looloo', 'loo'), ('curchycurchy', 'curchy
'), ('pullpull', 'pull'), ('yumyum', 'yum'), ('whatwhat', 'what'),
('teetee', 'tee'), ('quisquis', 'quis'), ('coocoo', 'coo'), ('
rere', 're'), ('puffpuff', 'puff')}
>>> words = { w[0] for w in words}
>>> words
40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
{'whatwhat', 'jamjam', 'yumyum', 'dumdum', 'nyumnyum', 'pullpull', '
rere', 'usus', 'quisquis', 'addleaddle', 'curchycurchy', '
hellohello', 'looloo', 'nono', 'tartar', 'schschschschschsch', '
papa', 'ooddleooddle', 'cancan', 'murmur', 'teetee', 'rara', 'eeee
', 'geegee', 'hiphip', 'bangbang', 'lala', 'chokeechokee', '
chinchin', 'coocoo', 'poohpooh', 'puffpuff'}
6. Aufgabe
import re
x = open("ulysses.txt").read().lower()
words = list(re.findall(r"\b([a-z]+)\b",x))
word_frequency = {}
for word in words:
l = len(word)
if l in word_frequency:
word_frequency[l] += 1
else:
word_frequency[l] = 1
print("Wortlänge
Häufigkeit")
word_frequency_list = list(word_frequency.items())
for (wl, freq) in sorted(word_frequency_list):
print("{0:8d}
{1:8d}".format(wl,freq))
Startet man obiges Programm, erhält man unten stehende Ausgabe:
$ python3 word_lengths.py Wortlänge
1
14930
2
43064
3
59122
4
47067
5
32513
6
23706
7
19561
8
11989
9
7901
10
4834
11
2706
12
1426
13
714
14
295
15
113
Häufigkeit
515
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
516
40 Lösungen zu den Aufgaben
16
17
18
19
20
21
22
23
24
25
26
27
28
30
33
34
36
37
39
69
53
30
16
6
7
6
1
2
1
3
2
1
1
1
1
1
2
1
1
1
Möchte man nur die Häufigkeitsverteilungen für verschiedene Wörter nach Wortlängen
berechnen, braucht man nur die Zeile
words = list(re.findall(r"\b([a-z]+)\b",x))
durch folgende Zeile zu ersetzen:
words = set(re.findall(r"\b([a-z]+)\b",x))
Man benutzt also eine Menge statt einer Liste. Dadurch werden automatisch mehrfache
Vorkommen eines Wortes eliminiert. Das Ergebnis sieht nun deutlich anders aus:
$ python3 word_lengths.py
Wortlänge
Häufigkeit
1
26
2
141
3
732
4
2038
5
3279
6
4331
7
4566
8
4273
9
3399
10
2441
11
1619
12
938
13
490
14
214
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke)
15
16
17
18
19
20
21
22
23
24
25
26
27
28
30
33
34
36
37
39
69
100
42
23
13
6
6
4
1
2
1
3
2
1
1
1
1
1
2
1
1
1
7. Aufgabe
import re
txt = open("ulysses.txt").read()
words = re.split(r"[^a-zA-Z-]+", txt)
unique_words = set(words)
ordered_words = set()
for word in words:
if len(word) <= 4:
continue
word_aufsteigend_sortiert = "".join(sorted(list(word.lower())))
if word == word_aufsteigend_sortiert:
ordered_words.add(word)
print(ordered_words)
517
518
40 Lösungen zu den Aufgaben
40.13
Lösungen zu Kapitel 30
(lambda, map, filter und reduce)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
1. Aufgabe
orders = [ ["34587","Learning Python, Mark Lutz", 4, 40.95],
["98762","Programming Python, Mark Lutz", 5, 56.80],
["77226","Head First Python, Paul Barry", 3, 32.95]]
min_order = 100
invoice_totals = list(map(lambda x: x if x[1] >= min_order else (x[0],
x[1] + 10) ,
map(lambda x: (x[0],x[2] * x[3]), orders)))
print(invoice_totals)
2. Aufgabe
from functools import reduce
orders = [ ["34587",("5464",
44.95)],
["34588",("5464",
["34588",("5464",
["34587",("8732",
39.95)] ]
4, 9.99), ("8274",18,12.99), ("9744", 9,
9, 9.99), ("9744", 9, 44.95)],
9, 9.99)],
7, 11.99), ("7733",11,18.99), ("9710", 5,
min_order = 100
invoice_totals = list(map(lambda x: [x[0]] + list(map(lambda y: y[1]*y
[2], x[1:])), orders))
invoice_totals = list(map(lambda x: [x[0]] + [reduce(lambda a,b: a + b
, x[1:])], invoice_totals))
invoice_totals = list(map(lambda x: x if x[1] >= min_order else (x[0],
x[1] + 10), invoice_totals))
print (invoice_totals)
40.14 Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension)
40.14
Lösungen zu Kapitel 31
(Listen-Abstraktion/
List Comprehension)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
1. Aufgabe
>>> n = 30
>>> [(x, y) for x in range(1,n+1) for y in range(1,n+1) if not x % y
and x != y ]
[(2, 1), (3, 1), (4, 1), (4, 2), (5, 1), (6, 1), (6, 2), (6, 3), (7,
1), (8, 1), (8, 2), (8, 4), (9, 1), (9, 3), (10, 1), (10, 2), (10,
5), (11, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 6), (13, 1)
, (14, 1), (14, 2), (14, 7), (15, 1), (15, 3), (15, 5), (16, 1),
(16, 2), (16, 4), (16, 8), (17, 1), (18, 1), (18, 2), (18, 3),
(18, 6), (18, 9), (19, 1), (20, 1), (20, 2), (20, 4), (20, 5),
(20, 10), (21, 1), (21, 3), (21, 7), (22, 1), (22, 2), (22, 11),
(23, 1), (24, 1), (24, 2), (24, 3), (24, 4), (24, 6), (24, 8),
(24, 12), (25, 1), (25, 5), (26, 1), (26, 2), (26, 13), (27, 1),
(27, 3), (27, 9), (28, 1), (28, 2), (28, 4), (28, 7), (28, 14),
(29, 1), (30, 1), (30, 2), (30, 3), (30, 5), (30, 6), (30, 10),
(30, 15)]
2. Aufgabe
Die Wahrscheinlichkeit dafür, dass die Summe der Augenzahlen 7 ist, berechnet sich aus
dem Quotienten der Anzahl aller Würfe mit Summe 7 („casts_of_sum_7”), dividiert durch
die Anzahl aller möglichen Würfe („all_casts”):
>>> all_casts = [ (x,y) for x in range(1,7) for y in range(1,7)]
>>> casts_of_sum_7 = [ x for x in all_casts if sum(x) == 7 ]
>>> casts_of_sum_7
[(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)]
>>> probability7 = len(casts_of_sum_7) / len(all_casts)
>>> probability7
0.16666666666666666
3. Aufgabe
def prob_for_sum(number):
all_casts = [ (x,y) for x in range(1,7) for y in range(1,7)]
casts_of_sum = [ x for x in all_casts if sum(x) == number ]
return len(casts_of_sum) / len(all_casts)
519
520
40 Lösungen zu den Aufgaben
if __name__ == "__main__":
for i in range(14):
print("sum: {0:2d}, prob: {1:5.2f} %".format(i,prob_for_sum(i)
100
) )
*
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Startet man obiges Programm, erhält man folgende Ausgabe:
$ python3 sum_of_dices_prob.py
sum: 0, prob: 0.00 %
sum: 1, prob: 0.00 %
sum: 2, prob: 2.78 %
sum: 3, prob: 5.56 %
sum: 4, prob: 8.33 %
sum: 5, prob: 11.11 %
sum: 6, prob: 13.89 %
sum: 7, prob: 16.67 %
sum: 8, prob: 13.89 %
sum: 9, prob: 11.11 %
sum: 10, prob: 8.33 %
sum: 11, prob: 5.56 %
sum: 12, prob: 2.78 %
sum: 13, prob: 0.00 %
40.15
Lösungen zu Kapitel 32
(Generatoren und Iteratoren)
1. Aufgabe
def fibonacci():
"""Ein Fibonacci-Zahlen-Generator"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
f = fibonacci()
counter = 0
for x in f:
print x,
counter += 1
if (counter > 10):
break
40.15 Lösungen zu Kapitel 32 (Generatoren und Iteratoren)
2. Aufgabe
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
def city_generator():
cities = ["Paris","Berlin","London","Vienna"]
for city in cities:
yield city
def round_robin(g):
while True:
for i in g():
yield i
if __name__ == "__main__":
for i in round_robin(city_generator):
print(i)
Dies Ausgabe dieses Programms sieht wie folgt aus:
Paris
Berlin
London
Vienna
Paris
Berlin
London
Vienna
Paris
Berlin
London
Vienna
Paris
...
3. Aufgabe
def city_generator():
cities = ["Paris","Berlin","London","Vienna"]
for city in cities:
yield city
def pendulum(g):
while True:
l = []
for i in g():
l.append(i)
yield i
521
522
40 Lösungen zu den Aufgaben
while l:
yield l.pop()
if __name__ == "__main__":
for i in pendulum(city_generator):
print(i)
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Starten wir dieses Programm, erhalten wir die folgende Ausgabe:
Paris
Berlin
London
Vienna
Vienna
London
Berlin
Paris
Paris
Berlin
London
Vienna
Vienna
London
Berlin
Paris
...
4. Aufgabe
def numbers():
for i in range(1,10):
yield i
def pair_sum(g):
it = g()
previous = next(it)
while True:
try:
x = next(it)
yield x + previous
previous = x
except StopIteration:
it = g()
if __name__ == "__main__":
for i in pair_sum(numbers):
print(i, end=", ")
40.15 Lösungen zu Kapitel 32 (Generatoren und Iteratoren)
Die Ausgabe sieht wie folgt aus:
3, 5, 7, 9, 11, 13, 15, 17, 10, 3, 5, 7, 9, 11, 13, 15, 17, 10, 3, 5,
7, 9, 11, ...
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
5. Aufgabe
import random
def random_ones_and_zeros():
p = 0.5
while True:
x = random.random()
message = yield 1 if x < p else 0
if message != None:
p = message
x = random_ones_and_zeros()
next(x) # wir sind nicht am return-Wert interessiert
for p in [0.2, 0.8]:
x.send(p)
print("\nprobabiliy: " + str(p))
for i in range(15):
print(next(x), end=" ")
Die Ausgabe sieht wie folgt aus:
probabiliy:
0 0 1 0 0 0
probabiliy:
1 1 1 1 1 0
0.2
0 0 0 0 0 0 0 0 0
0.8
1 0 0 1 1 0 1 1 1
523
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Stichwortverzeichnis
∧ bei regulären Ausdrücken 394
. Match eines beliebigen Zeichens 392
$ bei regulären Ausdrücken 394
% Modulo-Operator 23
16-Bit-Unicode-Zeichen → Escape-Zeichen
32-Bit-Unicode-Zeichen → Escape-Zeichen
A
Abfragemethode → Getter
Abgeleitete Klasse → Unterklasse
abort 441
__abs__ 236
abspath 455
Abstrakte Klassen 297
Abstrakte Methoden 297
access 441
__add__ 233, 235, 506
– Beispiel 236
all 350
Allgemeine Klasse → Basisklasse
Anagramm
– als Beispiel einer Permutation 328
__and__ 235
Änderungsmethode → Setter
Anführungszeichen → Escape-Zeichen
Angestelltenklasse 252→ Personklasse
Ankerzeichen 396
Anweisungsblöcke 67
Anzahl der Elemente einer Liste 40
Anzahl der Elemente eines Tupels 40
Anzahl der Zeichen eines Strings 40
append 143, 145
Archivdatei erzeugen 466
Arität 126
ASCII-Zeichen hexadezimal →
Escape-Zeichen
ASCII-Zeichen oktal → Escape-Zeichen
Asimovsche Gesetze 228
assertAlmostEqual 378
assertCountEqual 378
AssertEqual 377
assertEqual 378
assertFalse 379
assertGreater 379
assertGreaterEqual 379
assertIn 379
AssertionError 376
assertIs 379
assertIsInstance 379
assertIsNone 379
assertIsNot 379
assertItemsEqual 379
assertLess 379
assertLessEqual 379
assertListEqual 379
assertNotRegexpMatches 379
assertTrue 379
assertTupleEqual 379
assoziative Arrays 43
assoziatives Feld 43
atime 457
Attribute 205
– dynamische Erzeugung 207
– private 217
– protected 217
– public 217
AttributeError 258
Aufspalten von Zeichenketten 176
Augmented Assignment → erweiterte
Zuweisungen
augmented assignment 145
Ausgabe
– formatieren 93
– in Datei umleiten 95
– in Fehlerkanal umleiten 95
Auskellern 143
Ausnahme
– StopIteration 324
526
Stichwortverzeichnis
Ausnahmebehandlung 193
– except 193
– finally 198
– try 193
Automatentheorie 387
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
B
Barry 365
basename 455
Bash 426
Bash-Befehle ausführen 453
Basisklasse 251
bedingte Anweisungen 70
Bibliothek 158
Bierdeckelarithmetik 302
Bierdeckelnotation 302
binäre Operatoren 235
Binärsystem 302
Binärzahlen 25
Blöcke 67
Boehm 365
Boolesche Werte 26
Bourne-Shell 426
break 81
Bruch
– kürzen 243
– Kürzungszahl 243
– Repräsentierung in Python 242
– vollständig gekürzte Form 243
Bruchklasse 241
Bruchrechnen 242
bztar-Datei → Archivdatei erzeugen
C
C3 superclass linearization 273
Calendar-Klasse 264
CalendarClock-Klasse 264
__call__ 355
callable 355
Call-by-Reference 123
Call-by-Value 123
Caret-Zeichen 393
Cast 30
cast-Operator 66
Casting 30
Catullus 88
CaveInt 506→ Neanderthal-Arithmetik
center 187
chdir 441
chmod 441
Chomsky-Grammatik 133
chown 442
chroot 442
clear → Dictionary
CLI 425
Clock-Klasse 264
close 432
Closure 359
Codeblock 75, 417
commonprefix 456
complex 27, 236
continue 81
copy 464→ Dictionary
copy2 464
copy-Modul 111
copyfile 464
copyfileobj 464
copymode 464
copystat 464
copytree 464
count 147, 183
ctime 457
D
data hiding → Geheimnisprinzip
Datei 87
– lesen 87
– öffnen 87
– schreiben 89
– zum Schreiben öffnen 89
Dateibaum
– rekursiv durchwandern 454
Dateibearbeitung mit os 429
Dateideskriptor 432, 433
Dateigröße 459
Daten konservieren 381
Daten sichern mit Pickle 381
Datenabstraktion 212
Datenkapselung 204, 212
Datenpersistenz 381
Datentypen 21
Deadly Diamond of Death 272
Decorator → Dekorateur
deepcopy 111
Definition einer Variablen 22
Dekorateur 341
– Anwendungsfälle 350
– classmethod 261
– Einführung 341
– Funktionsdekorateur 341
– Klassendekorateur 341
– Memoisation 358
– mit Parametern 352
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Stichwortverzeichnis
– zur Überprüfung von Funktionsargumenten
350
Dekoration
– mit Klasse 357
__del__ 229
deleter 220
Destruktor 211
Dezimalpunkt 26
Dezimalsystem 302
Diamond-Problem 272
Dictionary 43, 49
– aus Listen erzeugen 54
– clear 48
– copy 49
– flache Kopie 49, 112
– get 47
– items 50
– keys 50
– nested Dictionaries 48
– pop 50
– popitem 50
– setdefault 51
– tiefe Kopie 49, 112
– update 51
– verschachtelte Dictionaries 48
Differenz 27
dirname 456
Division
– ganzzahlige 23
__doc__ 115
– bei Dekorateuren 354
Docstring 115
– property 220
doctest 370
Donald Michie → Memoisation
dup 434
Dynamische Attribute 227
Dynamische Erzeugung von Klassen 281
dynamische Typdeklaration 29
E
Eigenschaften 205
Eingabe 65
Eingabeaufforderung
– robust 194
Eingabeprompt 7, 65
Einkellern 143
Einrückungen 67
Einschubmethode → Hook-Methode
Elternklasse → Basisklasse
endliche Automaten 387, 394
__eq__ 226, 235
erben → Vererbung
errare humanum est 365
erweiterte Zuweisungen 23, 235
Escape-Zeichen
– 16-Bit-Unicode-Zeichen 35
– 32-Bit-Unicode-Zeichen 35
– Anführungszeichen 35
– ASCII-Zeichen hexadezimal 35
– ASCII-Zeichen oktal 35
– Hochkomma 35
– Horizontaler Tabulator 35
– Rückschritt 35
– Seitenumbruch 35
– Unicode-Zeichen 35
– Vertikaler Tabulator 35
– Zeilenumbruch 35
escape-Zeichen 35
Euklidischer Algorithmus 243
eval 66
except 193
execl 440
execle 440
execlp 439
execlpe 440
execv 438
execve 438
execvp 436
execvpe 437
exists 456
expandvars 457
explizite Typumwandlung 30
extend 144
extsep 443
F
Fabrikfunktion 345
f-Strings 104
Fakultätsfunktion 133
– iterative Lösung 135
– rekursive Implementierung 134
False 26
Fehler
– Semantik 366
– Syntax 365
Fehlerarten 365
fib → Fibonacci-Modul
fiblist → Fibonacci-Modul
Fibonacci 136
Fibonacci-Folge → Fibonacci-Zahlen
– rekursive Berechnung 142
Fibonacci-Modul 367
– fib-Funktion 367
527
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
528
Stichwortverzeichnis
– fiblist-Funktion 367
Fibonacci-Zahlen
– formale mathematische Definition 136
– Generator 339, 520
– Modul zur Berechnung der Fibonacci-Zahlen
367
– rekursive Berechnung mit Dekorateuren 358
– rekursive Funktion 135
filter 307
finally 198
find 147, 183
Finite State Machine 394
firstn-Generator 327
flache Kopie 49, 112
__float__ 236
__floordiv__ 235
flüchtige Daten 381
Flussdiagramm 68
for-Schleife 80
– optionaler else-Teil 80
– StopIteration 324
– Unterschied zur while-Schleife 81
– Vergleich zu C 80
fork 443, 469
Forking 469
forkpty 443
formale Sprachen 387
format-Methode 97
Formatstring 95
Fraction-Klasse 250
fractions-Modul 250
fromkeys → Dictionary
Frosch
– Aufgabe mit Summenbildung 84
– Straßenüberquerung 84
frozensets 59
fully qualified 158
functools 313
Funktionen 113
– Attribute 207
– Fabrikfunktionen 345
– globale Variablen 120
– lokale Variablen 120
– Parameterübergabe 122
– Rückgabewerte 118
– statische Variablen 207
– überladen 253
– Überschreiben 253
– variadische 126
– verschachtelte Funktionen 342
– Zählen der Aufrufe 207
Funktionen als Parameter 343
Funktionsabschluss 359
Funktionsdekorateur → Dekorateur
G
Ganze Zahlen 24
ganzzahlige Division 23, 27
Gaußsche Summenformel 77
__ge__ 235
Geheimnisprinzip 212
Generator
– Allgemein 323
– CLU 323
– Endlosschleife in 326
– firstn 327
– Icon 323
– isclice zur Ausgabe von unendlichen
Generatoren 327
– pair_sum 339
– pendulum 339
– Permutationen 328
– round_robin 339
– yield 325
– zur Erzeugung von Variationen 329
Generator-Ausdrücke 331
Generator-Comprehension 319
Generatoren
– return 332
– send 332
Generatoren-Abstraktion 319
get → Dictionary
get_archive_formats 465
getatime 457
getattr 207
getcwd 443, 462
getcwdb 443
getegid 443
getenv 427
getenvb 428
geteuid 443
get_exec_path 443
getgid 443
getgroups 443
getloadavg 444
getlogin 444
getmtime 458
getpgid 444
getpgrp 444
getpid 444
getppid 444
getresgid 444
getresuid 444
getsize ⇒ os.path
Stichwortverzeichnis
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Getter 214
getuid 444
get_unpack_formats 465
ggT 243
größter gemeinsamer Teiler 243
Gruppierungen 401
__gt__ 235
GUI 425
gztar-Datei → Archivdatei erzeugen
H
hasattr 207
Hash 43
__hex__ 236
Hexadezimalzahlen 25
Hochkomma → Escape-Zeichen
Hook-Methode 377
Hooks → Hook-Methode
Horizontaler Tabulator → Escape-Zeichen
I
__iadd__ 235
__iand__ 235
id 22, 107
Identitätsfunktion 22, 107
__idiv__ 235
if-Anweisung 70
__ifloordiv__ 235
ignore_patterns 466
__ilshift__ 235
__imod__ 235
implizite Typumwandlung 30
import-Anweisung 158
__imul__ 235
in
– Dictionaries 46
index 147, 183
index-Funktionen 434
Inheritance → Vererbung
__init__ 233
__init__-Methode 209
__init__.py 164
input 46, 65
insert 148
Instanz 203, 205
Instanzattribute 205, 207, 227
Instanzvariablen 209
int 24, 236
Integer 24
interaktive Shell
– Befehlsstack 11
– Starten unter Linux 7
– Starten unter Windows 9
– Unterstrich 11
__invert__ 236
__ior__ 235
__ipow__ 235
__irshift__ 235
isabs 459
isalnum 188
isalpha 188
isdigit 188
isdir 459
isfile 459
isinstance 31
islice 327
– als Ersatz für firstn 327
islink 460
islower 188
ismount 460
is_palindrome 131
is_prime 350
isspace 188
istitle 188
__isub__ 235
isupper 188
items → Dictionary
itemgetter 154
__iter__ 324
Iteration
– for-Schleife 323
– über Dictionary 83
– über String 83
Iteratoren 323
– for-Schleife 323
itertools 327
– islice 327
__ixor__ 235
J
join 183, 460
Junit 375
K
kanonischer Pfad 462
Keller 143
Kellerspeicher 143, 261
keys → Dictionary
KeyError 45
kill 444
killpg 445
Kindklasse → Unterklasse
Klasse
– Instanz 205
529
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
530
Stichwortverzeichnis
– Kopf 204
– Körper 204
– minimale Klasse in Python 204
klassen
– abstrakte 297
Klassenattribute 206, 227
Klassendekorateur 357→ Dekorateur
Klassenmethoden 231, 259
Klassenobjekt 206
Kombination 329
Kommandozeilenparameter 125
Kompilierung von regulären Ausdrücken 407
Komplexe Zahlen 27
Komponententest 367
Konstruktor 209
Kontrollstrukturen 70
Kopieren
– von Unterlisten 110
– von verschachtelten Listen 107
Koroutinen 332
Kundenklasse → Personklasse
Kürzen 243
L
lambda 307
Länge von dictionaries 52
__le__ 235
len 52
len-Funktion 40
Leonardo von Pisa → Fibonacci-Zahlen
Lesbia 88
lexists 460
Liber Abaci 136
Lieferant → Personklasse
linesep 445
link 445
Linux 426
– Python unter 7
list
– append 143
– augmented assignment 145
– extend 144
– find 147
– index 147
– Packing 149
– pop 143
– sort 151
– Unpacking 149
List Comprehension 315
– Kreuzprodukt 317
– pythagoreisches Tripel 316
– Sieb des Eratosthenes 318
– Syntax 316
– Vergleich mit konventionellem Code 316
– Wandlung Celsius in Fahrenheit 316
– Weitere Beispiele 316
– zugrundeliegende Idee 317
list:count 147
list:insert 148
list:remove 147
list-Klasse 203
listdir 445
list_iterator 324
Listen-Abstraktion 315
– Kreuzprodukt 317
– pythagoreisches Tripel 316
– Sieb des Eratosthenes 318
– Syntax 316
– Vergleich mit konventionellem Code 316
– Wandlung Celsius in Fahrenheit 316
– Weitere Beispiele 316
– zugrundeliegende Idee 317
ljust 187
Löffelsprache 130
__long__ 236
lower 186
lseek 433
__lshift__ 235
lstat 445
__lt__ 235
M
Magische Methoden 233
– __add__ 233
– binäre Operatoren 235
– Erweiterte Zuweisungen 235
– __floordiv__ 235
– __init__ 209, 233
– __mod__ 235
– __mul__ 235
– __new__ 233
– __pow__ 235
– __repr__ 222, 233
– __str__ 222
– __sub__ 235
– __truediv__ 235
– unäre Operatoren 236
– Vergleichsoperatoren 235
Magische Operatoren
– Beispielklasse Length 236
__main__ 205, 367
major 445
make_archive 466
makedev 446
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Stichwortverzeichnis
makedirs 447
Manager-Funktion 288
map 307, 310
Mapping 43
Marvin 504
Match eines beliebigen Zeichens 392
Match-Objekte 401
Matching-Problem 390
– Over Matching 390
– Under Matching 390
math-Modul 158
maxsplit
– split 176
Mehrfachschnittstellenvererbung 263
Mehrfachvererbung 263
– Beispiel CalendarClock 264
Mehrfachzuweisung 36
Memoisation 139, 358
– mit functools.lru_cache 360
– mit Klasse 359
Memoization → Memoisation
Memoize 359
memoize-Funktion 359
Mengen 57
– add 59
– clear 59
– copy 60
– difference 60
– difference_update 61
– isdisjoint 62
– issubset 63
– issuperset 63
– Obermenge 63
– pop 64
– remove 62
– Teilmenge 63
Mengen-Abstraktion 318
Mengenlehre 57
Metaklassen 285
Method Resolution Order 273, 276
Methoden 204, 208
– abstrakte 297
– __init__ 209
– Overloading → Überladen
– Overwriting → Überschreiben
– Überladen 257
– überlagern 253
– Überschreiben 257
– Unterschiede zur Funktion 208
minor 445
mkdir 446
mkfifo 447
__mod__ 235
modulare Programmierung 157
modulares Design 157
Modularisierung 157
Module 157
– dir() 161
– Dokumentation 162
– dynamisch geladene C-Module 159
– eigene Module schreiben 161
– Inhalt 161
– lokal 157
– math 158
– Modularten 159
– Namensraum 158
– re 389
– Suchpfad 160
__module__
– bei Dekorateuren 354
Modulo-Division 27
Modulo-Operator 23
Modultest 367
– unter Benutzung von __name__ 367
Monty Python 44
move 467
MRO 273, 276
mtime 457
__mul__ 235, 506
mypy 418
N
__name__ 205, 367
– bei Dekorateuren 354
Namensraum
– umbenennen 159
__ne__ 235
Neanderthal-Arithmetik 302
Nebeneffekte 124
__neg__ 236
Nested Dictionaries 48
__new__ 233
next 324
nice 447
Noam Chomsky 133
None 114
normcase 461
normpath 461
NotImplemented 247
O
Oberklasse 251
object 251
Objekt 203
531
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
532
Stichwortverzeichnis
Objektorientierte Programmiersprache 201
Objektorientierte Programmierung 201
__oct__ 236
Oktalzahlen 25
OOP 201
open 430
open() 87
openpty 432
operator-Modul 154
– itemgetter 154
Operatoren überladen
– binäre Operatoren 235
– erweiterte Zuweisungen 235
– unäre Operatoren 236
– Vergleichsoperatoren 235
Operatorüberladung 233
__or__ 235
os
– abort 441
– access 441
– chdir 441
– chmod 441
– chown 442
– chroot 442
– close 432
– dup 434
– execl 440
– execle 440
– execlp 439
– execlpe 440
– execv 438
– execve 438
– execvp 436
– execvpe 437
– extsep 443
– fork 443
– forkpty 443
– getcwd 443
– getcwdb 443
– getegid 443
– getenv 427
– getenvb 428
– geteuid 443
– get_exec_path 443
– getgid 443
– getgroups 443
– getloadavg 444
– getlogin 444
– getpgid 444
– getpgrp 444
– getpid 444
– getppid 444
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
–
getresgid 444
getresuid 444
getuid 444
kill 444
killpg 445
linesep 445
link 445
listdir 445
lseek 433
lstat 445
major 445
makedev 446
makedirs 447
minor 445
mkdir 446
mkfifo 447
nice 447
open 430
openpty 432
popen 447
putenv 428
read 432
readlink 449
remove 449
removedirs 449
rename 451
renames 451
rmdir 451
sep 456
setegid 451
seteuid 451
setgid 451
setgroups 444
setpgid 451
setpgrp 451
setregid 451
setresgid 451
setresuid 452
setreuid 452
setsid 452
setuid 451
stat 452
stat_float_times 452
strerror 452
supports_bytes_environ 428
symlink 453
sysconf 453
system 453
times 453
umask 453
uname 454
unlink 454
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Stichwortverzeichnis
– unsetenv 428
– urandom 454
– utime 454
– wait 454
– wait3 454
– waitpid 454
– walk 454
– write 429
os-Modul 426, 441
os.path 455
os.python
– abspath 455
– basename 455
– commonprefix 456
– dirname 456
– exists 456
– expandvars 457
– getatime 457
– getmtime 458
– getsize 459
– isabs 459
– isdir 459
– isfile 459
– islink 460
– ismount 460
– join 460
– lexists 460
– normcase 461
– normpath 461
– realpath 461
– relpath 462
– samefile 462
– split 462
– splitdrive 463
– splitext 463
Over Matching 390
Overloading 253→ Überladen
Overwriting 253→ Überschreiben
P
Packing 149
Paket 164
Paketkonzept 164
Palindrome 131
Parameter 115
– beliebige Anzahl 126
– Defaultwert 115
– optional 115
– Schlüsselwort 118
– Standardwert 115
Parameterliste 114
Parameterübergabe 122
partion 183
Pascalsches Dreieck 141
– Fibonacci-Zahlen 141
peek 143
Permutationen 328
– mit Wiederholungen 328
– ohne Wiederholungen 328
Permutations-Generator 328
Persistente Daten 381
Personenklasse 252
Personklasse → Vererbung
Pfadname
– absolut 459
– relativ 459
pickle-Modul 381
Platzhalter 96
Polymorphismus 255
Polynom-Fabrikfunktion 345
pop 143, 261→ Dictionary
popen 447
popitem → Dictionary
__pos__ 236
Potenzieren 27
__pow__ 235
print 93
– Anweisung 93
– Ausgabe in Fehlerkanal 95
– end 93
– file 95
– Funktion 93
– sep 93
– sys.stderr 95
– Umlenkung in Datei 95
printf 95
private Attribute 217
Produkt 27
Programmablaufplan 68
Programmiersprache
– Unterschied zu Skriptsprache 17
Properties 215
– Definition mit Dekorateuren 221
property
– deleter 220
– mit Dekorateuren 221
protected Attribute 217
public Attribute 217
push 143, 261
putenv 428
Q
Quantoren 399
Quotient 27
533
534
Stichwortverzeichnis
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
R
__radd__ 238
random-Methode
– sample 330
raw-Strings 35
read 432
readlink 449
re-Modul 389
realpath 461
reduce 313
Referenzen 21
Regeln zur Interpretation von römischen
Zahlen 482
register_archive_format 467
register_unpack_format 467
reguläre Ausdrücke 387
– Alternativen 407
– Anfang eines Strings matchen 394
– beliebiges Zeichen matchen 392
– compile 407
– Ende eines Strings matchen 394
– Kompilierung 407
– optionale Teile 398
– Qunatoren 399
– Teilstringsuche 389
– Überlappungen 389
– vordefinierte Zeichenklassen 397
– Wiederholungen von Teilausdrücken 399
– Zeichenauswahl 393
reguläre Auswahl
– Caret-Zeichen, Zeichenklasse 393
reguläre Mengen 387
reguläre Sprachen 387
Rekursion 133
– Beispiel aus der natürlichen Sprache 133
rekursiv → Rekursion
rekursive Funktion 133, 134
relpath 462
remove 147, 449
removedirs 449
rename 451
renames 451
replace 186
__repr__ 222, 233
Restdivision 27
return 114
rfind 183
rindex 183
rjust 187
rmdir 451
rmtree 467
Robot-Klasse 502
Roboter → Roboterklasse
Robotergesetze 228
Roboterklasse 204, 301
römische Zahlen 83
Rossum, Guido van 307
round_robin 339
__rshift__ 235
rsplit 179
– Folge von Trennzeichen 181
rstrip 187
Rückgabewerte 118
Rückschritt → Escape-Zeichen
Rückwärtsreferenzen 401
S
samefile 462
sample 330
Schachbrett mit Weizenkörnern 85
Schaltjahrberechnung 74, 266
Schaltjahre 74
Schleife
– break 81
– continue 81
Schleifen 70, 75
– Endekriterium 75
– for-Schleife 80
– kopfgesteuert 79
– Schleifendurchlauf 75
– Schleifenkopf 75
– Schleifenkörper 75
– Schleifenzähler 75
– while-Schleife 76
Schleifenzähler 75
Schlüssel
– Dictionary 43
– zulässige Typen 47
Schlüsselwortparameter 118
Seiteneffekte 124
Seitenumbruch → Escape-Zeichen
Sekunden
– Additon zu Uhrzeiten 74
semantische Fehler 366
sep 456
Sequentielle Datentypen 33
Sequenz 33
set 57
set comprehension 318
setdefault → Dictionary
setegid 451
seteuid 451
setgid 451
setgroups 444
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
Stichwortverzeichnis
setpgid 451
setpgrp 451
setregid 451
setresgid 451
setresuid 452
setreuid 452
sets
– add 59
– clear 59
– copy 60
– difference 60
– difference_update 61
– discard 61
– intersection 62
– isdisjoint 62
– issubset 63
– issuperset 63
– Operationen auf sets 59
– pop 64
– remove 62
setsid 452
Setter 214
setuid 451
setUp-Methode 377
Shell 425
– Bash 426
– Bourne 426
– C-Shell 426
– CLI 425
– GUI 425
– Herkunft und Bedeutung des Begriffes 425
Shell-Skripte ausführen 453
shelve-Modul 383
Shihram 85
shutil
– copy 464
– copy2 464
– copyfile 464
– copyfileobj 464
– copymode 464
– copystat 464
– copytree 464
– get_archive_formats 465
– get_unpack_formats 465
– ignore_patterns 466
– make_archive 466
– move 467
– register_archive_format 467
– register_unpack_format 467
– rmtree 467
– unpack_archive 468
– unregister_archive 468
– unregister_unpack_format 468
shutil-Modul 463
Sieb des Eratosthenes 318
– Rekursive Berechnung 141
– Rekursive Funktion mit Mengen-Abstraktion
319
silly_merge 420
Simula 67 201
singleton-Klasse 292
Skriptsprache
– Unterschied zu Programmiersprache 17
Slicing 38
Slots 279
sort 151
– eigene Sortierfunktionen 152
– reverse 152
– Umkehrung der Sortierreihenfolge 152
sorted 151
spezialisierte Klasse → Unterklasse
splice 303
split 176, 462
– Folge von Trennzeichen 181
– maxsplit 176
splitdrive 463
splitext 463
splitlines 182
Sprachfamilie 387
sprintf 95
Stack 143
– Stapelspeicher 143
Standardausnahmebehandlung 195
Standardbibliothek 158
Standardklassen als Basisklassen 261
Stapelspeicher 143, 261
stat 452
stat_float_times 452
staticmethod 229
Statische Attribute 227
Statische Methoden 229
statische Typ-Deklaration 29
Stelligkeit 126
Stephen Cole Kleene 387
StopIteration 324
str 26, 222, 242
strerror 452
Strings
– escape-Zeichen 35
– raw 35
– Suchen und Ersetzen 186
String-Tests 188
Stringinterpolation 95
Stringliterale 104
535
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
536
Stichwortverzeichnis
Stringmethoden
– Alles in Großbuchstaben 186
– Alles in Kleinbuchstaben 186
– capitalize 186
– center 187
– count 183
– find 183
– index 183
– isalnum 188
– isalpha 188
– isdigit 188
– islower 188
– isspace 188
– istitle 188
– isupper 188
– ljust 187
– lower 186
– replace 186
– rfind 183
– rindex 183
– rjust 187
– rstrip 187
– String-Tests 188
– strip 187
– title 186
– upper 186
– zfill 187
Stringmodulo 95
Strings 26
– formatieren 93
strip 187
Strukturierungselement 113
__sub__ 235, 506
Subklasse → Unterklasse
Suchen und Ersetzen 186
Suchen von Teilstrings 183
sum 76
Summe 27
Summe von n Zahlen 76
– Berechnung mit while-Schleife 76
SUnit 375
super 273
Superklasse → Basisklasse
supports_bytes_environ 428
symlink 453, 460
syntaktische Fehler 365
Syntax 365
– Fehler 365
Syntaxfehler 365
sysconf 453
system 453
Systemprogrammierung 425
T
tar-Datei → Archivdatei erzeugen
TDD → test-driven development
tearDown-Methode 377
Teilbereichsoperator 38
Tests 365
test first development 374
test-driven development 374
TestCase 377
– Methoden 377
– setUp-Methode 377
– tearDown-Methode 377
testCase 375
Testgesteuerte Entwicklung 374
Testgetriebene Entwicklung 373, 374
Testmethoden 377
Textverarbeitung 175
Theoretische Informatik 387
tiefe Kopie 49, 112
times 453
Transiente Daten 381
True 26
__truediv__ 235, 506
try 193
Tupel 36
– entpacken 36
– leere 149
– mit einem Element 149
– Packing 149
– Unpacking 149
tuple 36
Typ-Alias 421
Typ-Anmerkungen 417
– Any 422
– Callables 422
– Dict 422
– List 422
– Listen 420
– Variablen 419
Typ-Variablenanmerkungen 419
type 31, 203
type annotations 417
type conversion 30
type hints 417
type-Klasse 281
TypeError 369
– unhashable type 48
typing-Modul 421
Typumwandlung 30
– explizit 30
– implizit 30
Typverletzung 29
Stichwortverzeichnis
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
U
Überladen 253, 257
überlagern 253
Überlappungen 389
Überschreiben 253, 257
umask 453
Umgebungsvariablen 427
uname 454
unäre Operatoren 236
Unärsystem 302
Unary System → Unärsystem
Under Matching 390
unhashable type 48
Unicode-Zeichen → Escape-Zeichen
unittest 375→ Modultest
Unix 426
unlink 454
unpack_archive 468
Unpacking 149
unregister_archive_format 468
unregister_unpack_format 468
unsetenv 428
Unterklasse 251
Unterstrich
– Bedeutung in der interaktiven Shell 11
update → Dictionary
upper 186
urandom 454
utime 454
V
ValueError 194
Variable Anzahl von Parametern 126
Variablen 21
– Referenzen auf Objekte 21
Variablennamen 23
– Binnenversalien 24
– CamelCase 24
– gültige 23
– Konventionen 24
variadische Funktion 126
Variation 329
Vererbung 251
– Beispiel Angestelltenklasse, die von Person
erbt 251
– Beispiel Kundenklasse, die von Person erbt
251
– Beispiel Lieferantenklasse, die von Person
erbt 251
– Beispiel Personenklasse 251
Vererbungsbaum 263
Vergleichsoperatoren 72, 235
verschachtelte Dictionaries 48
Verschachtelte Funktionen 342
Vertikaler Tabulator → Escape-Zeichen
Verzeichnis löschen 451
Verzweigungen 70
Vollständige Induktion 133
W
wait 454
wait3 454
waitpid 454
walk 454
Weizenkornaufgabe 85
while-Schleife 76
– optionaler else-Teil 78
with-Anweisung 90
wraps-Dekorateur 355
write 429
X
__xor__ 235
Y
yield 325
– im Vergleich zur return-Anweisung 325
Z
Zahlenratespiel 78
Zeichenauswahl 393
Zeichenkette 26
Zeilenumbruch → Escape-Zeichen
Zeitrechnung 74
zfill 187
zip-Datei → Archivdatei erzeugen
zip-Funktion 52
zip-Klasse 52
Zugriffsmethoden 214
Zustandsautomat 394
Zustandsmaschine 394
Zuweisung 22
537
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
EINFÜHRUNG
IN PYTHON 3 //
Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021
For personal use only.
■ Besonders geeignet für Programmieranfänger,
aber auch für Umsteiger von anderen Sprachen
wie C, C++, Java oder Perl
■ Eine systematische Einführung in die PythonProgrammierung, die auf den Erfahrungen aus
Hunderten von Schulungen des Autors aufbaut
■ Lehrbuch und Nachschlagewerk
■ Praxisnahe Übungen mit ausführlich dokumentierten Musterlösungen zu jedem Kapitel
Die wesentlichen Begriffe und Techniken der
Programmierung und die zugrunde liegenden Ideen
werden im vorliegenden Buch anschaulich erklärt.
Zu den Problemstellungen werden typische
Beispiele zur Verdeutlichung verwendet, die sich
leicht auf andere Anwendungsfälle übertragen
lassen. Ebenso dienen auch die Übungsaufgaben
mit ausführlich dokumentierten Musterlösungen
nicht nur dazu, den Stoff zu vertiefen, sondern vor
allem auch exemplarische Vorgehensweisen zu
demonstrieren, die in vielen anderen Bereichen
verwendet werden können.
Als idealer Einstieg für Programmieranfänger, aber
auch für Umsteiger behandelt dieses Buch alle
grundlegenden Sprachelemente von Python 3.
Aber auch für Python-Kenner bietet das Buch viele
weiterführende Themen wie Systemprogrammierung, Threads, Forks, Ausnahmebehandlungen
und Modultests.
In dieser dritten Auflage wurde der Objektorientierung ein eigener Teil mit hundert Seiten gewidmet.
In anschaulicher und leicht verständlicher Weise
wird der pythonische Stil demonstriert, der sich
deutlich von C++ und Java unterscheidet. Außerdem wurden nun auch eigene Kapitel zu Slots,
dynamische Erzeugung von Klassen, Metaklassen
und Abstrakten Klassen aufgenommen.
Der Diplom-Informatiker Bernd
KLEIN genießt internationales
Ansehen als Python-Dozent.
Bisher hat er über 200 PythonKurse in Deutschland, Schweiz,
Österreich, Niederlande, Luxemburg und Kanada auf allen Niveaus
durchgeführt. Er ist Gründer und
Inhaber des international agierenden Schulungsanbieters Bodenseo. Besondere Anerkennung
findet er wegen seiner PythonWebseiten www.python-kurs.eu
und www.python-course.eu.
Seit 2016 ist er Lehrbeauftragter
der Universität Freiburg.
AUS DEM INHALT //
■ Datentypen
■ Verzweigungen
■ Schleifen
■ Formatierte Ausgabe und
Strings formatieren
■ Flaches und tiefes Kopieren
■ Funktionen
■ Vererbung
■ Slots
■ Klassen
■ lambda, map, filter und
reduce
■ Listen-Abstraktion
■ Generatoren und Iteratoren
■ Dekorateure
■ Tests und Fehler
■ Daten konservieren
■ Reguläre Ausdrücke
■ Typ-Anmerkungen
■ Systemprogrammierung
■ Forks
Zusätzlich wird auf die Typ-Anmerkungen von
Python 3.5 und 3.6 eingegangen.
€ 25,00 [D] | € 25,70 [A]
ISBN 978-3-446-45208-4
www.hanser-fachbuch.de/computer
Download