Einführung in Python 3 - In Einer Woche Programmieren Lernen

February 25, 2018 | Author: nyphur | Category: Software Development, Areas Of Computer Science, Software Engineering, Computing, Technology
Share Embed Donate


Short Description

Erste Schritte mit Python // Datentypen: Listen, Tupel, Mengen und Dictionaries // Verzweigungen // Schleifen mit while ...

Description

Bernd KLEIN

3

EINFÜHRUNG IN

PYTHON IN EINER WOCHE PROGRAMMIEREN LERNEN

Im Internet: Musterlösungen zu den Übungen

Klein

Einführung in Python 3

CHV Newsletterhinweis Computer Bleiben Sie auf dem Laufenden! Der Hanser Computerbuch-Newsletter informiert Sie regelmäßig über neue Bücher und Termine aus den verschiedenen Bereichen der IT. Profitieren Sie auch von Gewinnspielen und exklusiven Leseproben. Gleich anmelden unter www.hanser-fachbuch.de/newsletter

 

Bernd Klein

Einführung in Python 3 In einer Woche programmieren lernen

 

Der Autor: Bernd Klein, [email protected]

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, Waren­ ­­be­zeich­­nungen 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 National­biblio­ grafie; 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. © 2013 Carl Hanser Verlag München, www.hanser-fachbuch.de Lektorat: Brigitte Bauer-Schiewek Herstellung: Irene Weilhart Fachlektorat: Stefan Günther, Hanau 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-43547-6 e-book-ISBN: 978-3-446-43717-3

Inhalt

Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XVII Danksagung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XIX 1

Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

1.1

Programmieren lernen in einer Woche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

1.2

Aufbau des Buches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2

1.3

Zielgruppe des Buches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3

1.4

Programmieren lernen „interaktiv” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4

1.5

Download der Beispiele und Hilfe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4

1.6

Anregungen und Kritik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5

Einführung in Python 3 – Für Ein- und Umsteiger . . . . . . . . . . . . . . . . . . . . . .

7

2

Kommandos und Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9

2.1

Erste Schritte mit Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9

2.1.1

Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9

2.1.2

Teil I

Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10

Herkunft und Bedeutung des Begriffes interaktive Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11

2.2.1

Erste Schritte in der interaktiven Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11

2.3

Verlassen der Python-Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

13

2.4

Benutzung von Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

13

2.5

Mehrzeilige Anweisungen in der interaktiven Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

14

2.6

Programme schreiben oder schnell mal der Welt “Hallo” sagen . . . . . . . . . . . . . . . . . . .

14

3

Bytecode und Maschinencode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

3.1

Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

3.2

Unterschied zwischen Programmier- und Skriptsprachen . . . . . . . . . . . . . . . . . . . . . . . . .

17

3.3

Interpreter- oder Compilersprache. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

2.2

VI

Inhalt

4

Datentypen und Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4.1

Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

21

4.2

Statische und dynamische Typdeklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

22

4.3

Typumwandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

24

4.4

Datentyp ermitteln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

25

5

Sequentielle Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

5.1

Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

27

5.1.1

Zeichenketten oder Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

27

5.1.2

Listen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

5.1.3

Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

5.1.4

Sequenz von Binärdaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

5.2

Indizierung von sequentiellen Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

5.3

Slicing oder Ausschneiden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

30

5.4

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

31

6

Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

6.1

Dictionaries und assoziative Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

33

6.2

Definition und Benutzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

34

6.3

Fehlerfreie Zugriffe auf Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

36

6.4

Zulässige Typen für Schlüssel und Werte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

37

6.5

Verschachtelte Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

37

6.6

Methoden auf Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

38

6.7

Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

41

6.8

Zip. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

42

6.9

Dictionaries aus Listen erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

43

6.10

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

44

7

Mengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

7.1

Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

47

7.2

Mengen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

47

7.2.1

Sets erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

48

7.2.2

Mengen von unveränderlichen Elementen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

48

7.3

Frozensets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

49

7.4

Operationen auf „set”-Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

49

7.4.1

add(element). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

49

7.4.2

clear() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

50

7.4.3

copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

50

7.4.4

difference() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

50

Inhalt

7.4.5

difference_update() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

51

7.4.6

discard(el) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

51

7.4.7

remove(el) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

52

7.4.8

intersection(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

52

7.4.9

isdisjoint() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

52

7.4.10 issubset() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

7.4.11 issuperset() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

7.4.12 pop() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

8

Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

8.1

Verzweigungen im Allgemeinen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

55

8.2

Bedingte Anweisungen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

55

8.3

Beispiel Hundejahre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

56

8.4

Wahr oder falsch: Bedingungen in Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

57

8.5

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

58

9

Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

9.1

Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

59

9.2

while-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

60

9.3

die Alternative im Erfolgsfall: else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

61

9.4

For-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

62

9.5

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

65

10

Dateien lesen und schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

10.1

Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

69

10.2

Text aus einer Datei lesen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

69

10.3

Schreiben in eine Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

71

10.4

In einem Rutsch lesen: readlines und read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

71

10.5

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

72

11

Listen und Tupel im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

11.1

Stapelspeicher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

75

11.2

Stapelverarbeitung in Python: pop und append. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

76

11.3

extend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77

11.4

Entfernen eines Wertes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77

11.5

Prüfen, ob ein Element in Liste enthalten ist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

78

11.6

Finden der Position eines Elementes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

78

11.7

Einfügen von Elementen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

79

11.8

Besonderheiten bei Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

79

VII

VIII

Inhalt

11.9

11.8.1 Leere Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

80

11.8.2 1-Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

80

11.8.3 Mehrfachzuweisungen, Packing und Unpacking . . . . . . . . . . . . . . . . . . . . . . . . . . .

81

Die veränderliche Unveränderliche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

81

11.10 Sortieren von Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

82

11.10.1 „sort” und „sorted” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

82

11.10.2 Umkehrung der Sortierreihenfolge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

83

11.10.3 Eigene Sortierfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

83

11.11 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

86

12

Modularisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

12.1

Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

89

12.1.1 Namensräume von Modulen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

90

12.1.2 Namensräume umbenennen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

91

12.1.3 Modularten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

91

12.1.4 Suchpfad für Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

92

12.1.5 Inhalt eines Modules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

93

12.1.6 Eigene Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

93

12.1.7 Dokumentation für eigene Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

94

12.2

Pakete. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

95

13

Flaches und tiefes Kopieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

13.1

Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

97

13.2

Kopieren einer Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

99

13.3

Kopie mit Teilbereichsoperator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

13.4

Kopieren mit deepcopy aus dem Modul copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

13.5

Deepcopy für Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

14

Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

14.1

Allgemein . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

14.2

Funktionen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104

14.3

Optionale Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105

14.4

Docstring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

14.5

Schlüsselwortparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

14.6

Rückgabewerte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

14.7

Mehrere Rückgabewerte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

14.8

Lokale und globale Variablen in Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108

14.9

Beliebige Anzahl von Parametern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

14.10 Parameterübergabe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

Inhalt

14.11 Nebeneffekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 14.12 Kommandozeilenparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 14.13 Variable Anzahl von Parametern. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 14.14 * in Funktionsaufrufen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 14.15 Beliebige Schlüsselwortparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 14.16 Doppeltes Sternchen im Funktionsaufruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116

15

Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

15.1

Definition und Herkunft des Begriffs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

15.2

Definition der Rekursion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

15.3

rekursive Funktionen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

15.4

Die Tücken der Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119

15.5

Fibonacci-Folge in Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

15.6

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124

16

Globale und lokale Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

16.1

Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

16.2

Globale und lokale Variablen in Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

17

Alles über Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

17.1

... fast alles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

17.2

Aufspalten von Zeichenketten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 17.2.1 split . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 17.2.2 Standardverhalten und „maxsplit” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 17.2.3 rsplit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 17.2.4 Folge von Trennzeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 17.2.5 splitlines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 17.2.6 partition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

17.3

Zusammenfügen von Stringlisten mit join . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

17.4

Suchen von Teilstrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 17.4.1 „in” oder „not in” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 17.4.2 s.find(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 17.4.3 s.rfind(substring[, start[, end]]). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 17.4.4 s.index(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 17.4.5 s.rindex(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 17.4.6 s.count(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142

17.5

Suchen und Ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142

17.6

Nur noch Kleinbuchstaben oder Großbuchstaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

17.7

capitalize und title . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

IX

X

Inhalt

17.8

Stripping Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

17.9

Strings ausrichten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

17.10 String-Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 17.11 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

18

Ausnahmebehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

18.1

Die optionale else-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150

18.2

Fehlerinformationen über sys.exc_info . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

18.3

Abfangen mehrerer Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

18.4

except mit mehrfachen Ausnahmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152

18.5

Exceptions generieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152

18.6

Finalisierungsaktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152

19

Objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155

19.1

Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155

19.2

Die Kuchenklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

19.3

Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

19.4

Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

19.5

Kapselung von Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157

19.6

Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158

19.7

Klassen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158

19.8

Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159

19.9

Konstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

19.10 Destruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 19.11 Lauffähige Version der Kontoklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 19.12 „Öffentlicher Schönheitsfehler” oder Public-Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 19.13 Datenkapselung und die Benutzung von Public- Protected- und PrivateAttributen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 19.14 Statische Member . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 19.14.1 __del__ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 19.15 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 19.16 Dynamische und statische Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 19.17 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 19.17.1 Oberbegriffe und Oberklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 19.17.2 Vererbung in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 19.18 Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 19.18.1 Theorie. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 19.18.2 Diamand-Problem oder „deadly diamond of death” . . . . . . . . . . . . . . . . . . . . . . . 176 19.18.3 Beispiel: CalendarClock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

Inhalt

19.19 Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 19.20 Operator-Überladung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 19.21 Standardklassen als Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 19.22 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189

Teil II

Weiterführende Themen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193

20

Tests und Fehler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195

20.1

Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195

20.2

Modultests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197

20.3

Modultests unter Benutzung von __name__ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197

20.4

doctest-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200

20.5

Testgetriebene Entwicklung oder „Im Anfang war der Test” . . . . . . . . . . . . . . . . . . . . . . . . 202

20.6

unittest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204

20.7

Methoden der Klasse TestCase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206

20.8

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209

21

Systemprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211

21.1

Systemprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211

21.2

Häufig falsch verstanden: Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211

21.3

os-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 21.3.1 Vorbemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 21.3.2 Umgebungsvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 21.3.3 Dateiverarbeitung auf niedrigerer Ebene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 21.3.4 Die exec-„Familie” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 21.3.5 Weitere Funktionen im Überblick. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 21.3.6 os.path - Arbeiten mit Pfaden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240

21.4

shutil-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249

22

Forks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255

22.1

Fork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255

22.2

Fork in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255

23

Das Modul „pickle” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259

23.1

Daten „einpökeln” mit pickle.dump . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259

23.2

pickle.load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260

XI

XII

Inhalt

24

Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261

24.1

Ursprünge und Verbreitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261

24.2

Stringvergleiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261

24.3

Überlappungen und Teilstrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263

24.4

Das re-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263

24.5

Matching-Problem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264

24.6

Syntax der regulären Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266 24.6.1 Beliebiges Zeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266

24.7

Zeichenauswahl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266

24.8

Endliche Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267

24.9

Vordefinierte Zeichenklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268

24.10 Anfang und Ende eines Strings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 24.11 Optionale Teile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 24.12 Quantoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 24.13 Gruppierungen und Rückwärtsreferenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274 24.13.1 Match-Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274 24.14 Umfangreiche Übung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276 24.15 Alles finden mit findall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 24.16 Alternativen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 24.17 Kompilierung von regulären Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280 24.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck . . . . . . . . . . . . . . . . . . . . . . . 282 24.18.1 split-Methode der String-Klasse. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 24.18.2 split-Methode des re-Moduls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283 24.18.3 Wörter filtern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 24.19 Suchen und Ersetzen mit sub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 24.20 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286

25

lambda, map, filter und reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289

25.1

lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289

25.2

map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292

25.3

Filtern von sequentiellen Datentypen mittels „filter” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294

25.4

reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294

25.5

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296

26

Listen-Abstraktion/List Comprehension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297

26.1

Die Alternative zu Lambda und Co. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297

26.2

Einführung in die Listen-Abstraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297

26.3

Syntax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298

26.4

Weitere Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299

Inhalt

26.5

Die zugrunde liegende Idee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299

26.6

Anspruchsvolleres Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300

26.7

Mengen-Abstraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301

26.8

Rekursive Funktion zur Berechnung der Primzahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301

26.9

Generator Comprehension/ Generatoren-Abstraktion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302

26.10 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303

27

Generatoren und Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305

27.1

Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305

27.2

Iteration in for-Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305

27.3

Generatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307

27.4

Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 27.4.1 Permutationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 27.4.2 Variationen und Kombinationen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310

27.5

Generatoren zähmen mit firstn und islice. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312

27.6

send-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312

27.7

Generator-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313

27.8

Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314

28

Memoisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317

28.1

Bedeutung und Herkunft des Begriffes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317

28.2

Memoisation mit Dekorateur-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318

28.3

Memoize in einer Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319

28.4

Dekorateure in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319

28.5

Überprüfung von Argumenten durch Dekorateure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321

29

NumPy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323

29.1

Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323

29.2

Arrays in NumPy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325

29.3

Arrays flach machen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327

29.4

Arrays umdimensionieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328

29.5

Arrays konkatenieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330

29.6

Array, neue Dimension hinzufügen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332

29.7

Array mit Nullen und Einsen initialisieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332

29.8

Matrizenarithmetik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333

29.9

Vektoraddition und Vektorsubtraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334

29.10 Matrix-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336 29.10.1 Matrix-Produkt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 29.10.2 Eine einfache praktische Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338

XIII

XIV

Inhalt

29.11 Inverse Matrix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339 29.12 Kreuzprodukt / Vektorprodukt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340 29.13 Lineare Gleichungssysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340 29.14 Polynome . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 29.15 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343

Teil III

Umfassende Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345

30

Bruchklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347

30.1

Brüche à la 1001 Nacht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347

30.2

Zurück in die Gegenwart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348

30.3

Rechenregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351 30.3.1 Multiplikation von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351 30.3.2 Division von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352 30.3.3 Addition von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 30.3.4 Subtraktion von Brüchen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 30.3.5 Die Bruchklasse im Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354

31

Mastermind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357

31.1

Die Ursprünge des Spiels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357

31.2

Die Spielregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 31.2.1 „Bulls and Cows” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 31.2.2 Mastermind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358

31.3

Kombinatorikmodul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359

31.4

Mastermind in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360

32

Textklassifikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365

32.1

Einführung in die Textklassifikation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365

32.2

Textklassifikation: Aufgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367

32.3

Naive-Bayes-Klassifikator. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367 32.3.1 Definition. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367 32.3.2 Bayes-Theorem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367

32.4

Formale Herleitung der Naive-Bayes-Klassifikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368

32.5

Textklassifikation in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370 32.5.1 BagOfWords-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370 32.5.2 Document-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371 32.5.3 DocumentClass-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373 32.5.4 Pool-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374

Inhalt

33

Lösungen zu den Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379

33.1

Lösungen zu Kapitel 5 (Sequentielle Datentypen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379

33.2

Lösungen zu Kapitel 6 (Dictionaries) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381

33.3

Lösungen zu Kapitel 8 (Verzweigungen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383

33.4

Lösungen zu Kapitel 9 (Schleifen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385

33.5

Lösungen zu Kapitel 10 (Dateien lesen und schreiben) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388

33.6

Lösungen zu Kapitel 11 (Listen und Tupel im Detail) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390

33.7

Lösungen zu Kapitel 15 (Rekursive Funktionen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393

33.8

Lösungen zu Kapitel 17 (Alles über Strings . . . ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397

33.9

Lösungen zu Kapitel 19 (Objektorientierte Programmierung) . . . . . . . . . . . . . . . . . . . . . 400

33.10 Lösungen zu Kapitel 20 (Tests und Fehler) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405 33.11 Lösungen zu Kapitel 24 (Reguläre Ausdrücke) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405 33.12 Lösungen zu Kapitel 25 (lambda, map, filter und reduce) . . . . . . . . . . . . . . . . . . . . . . . . . . 410 33.13 Lösungen zu Kapitel 26 (Listen-Abstraktion/List Comprehension) . . . . . . . . . . . . . . . 411 33.14 Lösungen zu Kapitel 27 (Generatoren und Iteratoren) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412 33.15 Lösungen zu Kapitel 29 (NumPy) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415

Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417

XV

Vorwort

Bedingt durch den traumhaften Anstieg der Bedeutung von Python in der Wissenschaft und Wirtschaft in den letzten Jahren gibt es auch ein wachsendes Interesse an geeigneten Python-Büchern. Gerade für Programmieranfänger besteht die Schwierigkeit darin, ein geeignetes Buch zu finden. Der unüberlegte Griff ins Bücherregal führt schnell zum Kauf eines Buches, was zwar viele Vorzüge haben mag, aber für Anfängerinnen und Anfänger völlig ungeeignet ist. Dies ist besonders schade, da es sich bei Python ja um eine einfache Programmiersprache handelt. Das war bereits beim anfänglichen Design der Sprache eines der wesentlichen Ziele ihres „Erfinders” Guido van Rossum, als er Python Anfang der 1990er Jahre am Zentrum für Mathematik und Informatik (Centrum voor Wiskunde en Informatica) in Amsterdam entwarf. Python ist einfach, weil es mit erstaunlich wenigen Schlüsselwörtern auskommt und seine Syntax, also der Aufbau der Sprache, auf Übersichtlichkeit optimiert ist. Auch im Bereich objektorientierte Programmierung ist Python sehr konsequent, in manchen Dingen sogar konsequenter als Java. So sind in Python alles Objekte, d.h. es gibt keine primitiven Typen. Selbst Integer und Float-Zahlen sind in Python als Klassen realisiert. Ein weiterer Unterschied zu Java besteht darin, dass in Python Mehrfachvererbung möglich ist. Auch Programme in anderen Sprachen lassen sich einfach als Module in Python einbetten. So kann man beispielsweise zeitkritische Algorithmen in C programmieren und sie dann in Python einbinden. Zusammenfassend kann man sagen, dass es sich bei Python um eine Programmiersprache handelt, die sich bestens zum Einstieg in die Programmierung eignet, aber auch die optimale Lösung für Spezialisten aus zahlreichen Problemfelder ist. In diesem Sinne finden Sie in diesem Buch den idealen Einstieg. Brigitte Bauer-Schiewek, Lektorin

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 hatte. Außerdem danke ich den zahlreichen Teilnehmern an meinen Python-Kursen, die mir geholfen haben, meine didaktischen und fachlichen Kenntnisse kontinuierlich zu verbessern. Ebenso möchte ich den Besuchern meiner Online-Tutorials unter www.pythonkurs.eu und www.python-course.eu danken, vor allem denjenigen, die sich mit konstruktiven Anmerkungen bei mir gemeldet haben. Allen voran Stefan Günther, der mir viele kleine und große Ungenauigkeiten übermittelte und auch dieses Buch nochmals kritisch gelesen hat. Zuletzt danke ich auch ganz herzlich dem Hanser Verlag, der dieses Buch ermöglicht hat. Vor allem danke ich Frau Brigitte Bauer-Schiewek, Programmplanung Computerbuch, und Frau Sarah Merz, Lektorat und Online-Marketing, für die kontinuierliche ausgezeichnete Unterstützung. Für die technische Unterstützung bei LaTex-Problemen danke ich Herrn Uwe Hartmann und Herrn Stephan Korell. Herrn Jürgen Dubau danke ich fürs Lektorat. Bernd Klein, Singen

1 1.1

Einleitung

Programmieren lernen in einer Woche

Kann man in einer Woche programmieren lernen, so wie es der Titel des Buches verspricht? Genauer gesagt: Kann man in einer Woche mit Python programmieren lernen? Was meinen wir überhaupt mit einer Woche? „Acht Tage” – und gemeint sind sieben – oder denken wir an eine Arbeitswoche mit fünf Tagen? Wie viel Stunden sollte man dann pro Tag an dem Buch arbeiten? Jeweils ein voller Arbeitstag oder ein paar Minuten oder Stunden zwischendurch? Die Liste der Fragen könnten wir noch beliebig fortsetzen. Formulieren wir die Hauptfrage einfach um: Kann man in wenigen Tagen programmieren lernen? Das ist möglich, definitiv! 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 anderen Programmiersprachen wie beispielsweise C, C++, Java, Perl und nicht zu vergessen Shell-Programmierung besucht. Manche haben auch schon vorher mehr oder weniger viel Erfahrung in Python gesammelt. Aber eines war in allen Gruppen gleich: Wir haben es immer geschafft, dass unsere Teilnehmerinnen und Teilnehmer programmieren gelernt haben, und vor allen Dingen – was uns am wichtigsten ist – konnten wir immer die Begeisterung für die Sprache Python entfachen. Nun mögen Sie fragen, was bedeutet es, dass Sie „programmieren gelernt haben”? Können Sie nun selbständig und problemlos auch komplexeste Probleme lösen? Lassen Sie es mich mit natürlichen Sprachen vergleichen. Man kann in wenigen Stunden soviel Englisch oder eine andere Sprache lernen, dass man bis zehn oder zwanzig zählen kann, dass man Leute begrüßen kann, dass man sich bedanken, entschuldigen oder nach dem Weg fragen kann usw., aber auch nach einem Jahr wird man selten in der Lage sein, Shakespeare oder James Joyce im englischen Original zu lesen. Will man jedoch überhaupt Shakespeare oder Joyce im Original lesen oder genügt es einem schon, wenn man einfache Konversationen mit Muttersprachlern führen, Nachrichten verstehen und Zeitungsartikel lesen kann? So ähnlich verhält es sich auch mit Programmiersprachen und der Programmierung im Allgemeinen. Was hat man sich zum Ziel gesetzt? Will man am Ende komplexe Aufgaben wie Routenplaner, Webcrawler, Suchalgorithmen oder Ähnliches programmieren können oder möchte man vielmehr kleine oder mittlere praktische Probleme lösen?

2

1 Einleitung

Apropos „am Ende”: Wie auch beim Sprachen lernen gibt es kein Ende. Programmieren lernen ist eine lebenslange Angelegenheit. Man kann sich immer wieder (selbst als anerkannter Experte) an kleinen neu gefundenen Details erfreuen.

1.2

Aufbau des Buches

Dieses Buch besteht aus vier Teilen: Teil I:

In diesem Teil behandeln wir die Grundlagen der Sprache. Dies ist der eigentliche Stoff, den man durcharbeiten sollte, um Python zu beherrschen. Dieser Teil wird auch in unseren fünftägigen Kursen komplett behandelt. An die Bearbeitung dieses Teiles denken wir auch, wenn wir von „Programmieren lernen in einer Woche” sprechen.

Teil II:

Im nächsten Teil behandeln wir weiterführende Themen. Dies sind zum einen weitere Sprachkonstrukte, die unmittelbar zum Sprachumfang von Python gehören und um wichtige Module und Programmierkonzepte. Während es im ersten Teil prinzipiell keine Gebiete gibt, die man weglassen kann, wenn man Python lernen will, so sieht es in diesem Teil anders aus. Die Kapitel sind sehr wichtig für Python und die Benutzung von Python, aber sie sind von unterschiedlicher Wichtigkeit für verschiedene Anwender und Anwendungszwecke. So ist beispielsweise das Kapitel zur Systemprogrammierung von besonderer Wichtigkeit für Systemprogrammierer, die ihre Systemprogramme zukünftig unter Python und nicht mehr mit Shell-Skripting schreiben wollen. NumPy ist insbesondere für Mathematiker und Ingenieure von besonderem Interesse, die gerne numerische Probleme effizient mit Python bearbeiten wollen, also beispielsweise „numerisches Lösen von Gleichungssystemen”, „Matrizenmultiplikation” oder „Nullstellen von Polynomen”. Die Listen-Abstraktionen, ebenso wie die lambda-, map-, filter- und reduceFunktionen, bieten eine faszinierende Möglichkeit, die Programmierung auf ein abstrakteres Level zu bringen. Dadurch kann man komplexere Probleme mit geringerem Programmieraufwand lösen. Dennoch kann man die gleichen Programme auch ohne diese Techniken schreiben. Aber das einführende Kapitel über Testverfahren und Debugging ist von allgemeinem Interesse. 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 betreibt. Man braucht sie auch, wenn man aus Log- oder Parameterdateien bestimmte Informationen herausfiltern will.

Teil III: In diesem Teil wenden wir das im 1. Teil gelernte Wissen in umfassenden Beispielen an. Dabei verwenden wir viele kleine Beispiele und Übungsaufgaben, die wir bereits im 1. Teil behandelt haben. Sie werden sehen, wie

1.3 Zielgruppe des Buches

man Funktionen, wenn man sie gut konzipiert hat, in anderen Programmen wiederverwenden kann. Teil IV: 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 Kapitel 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.3

Zielgruppe des Buches

Beim Schreiben eines Buches hat man ein Gegenüber vor sich, eine Person, die das Geschriebene liest, 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 zu erlernen. Natürlich hatten wir beim Schreiben aber nicht nur eine Person im Blickfeld, sondern eine ganze Leserschar. Da sind zum einen die totalen Programmieranfänger, die Sachverhalte erklärt haben wollen, die erfahrene Programmierer anderer Sprachen vielleicht als „trivial” oder „selbstverständlich” bezeichnen würden. Aber hier ist ein Buch wohl dem Präsenzunterricht, also wenn Lehrer und Lernende am gleichen Ort zusammen sind, deutlich überlegen: Ist einem der Stoff eines Abschnittes oder sogar Kapitels bereits vertraut, kann man es einfach überspringen, bevor man sich zu langweilen beginnt. Das Gleiche können dann auch die totalen Anfänger tun, wenn wir auf Ähnlichkeiten, aber auch generelle Unterschiede in der Vorgehensweise von Python im Vergleich zu anderen Sprachen eingehen. Damit können wir also im ersten Teil des Buches sowohl totale Programmieranfänger als auch erfahrenere Programmierer anderer Sprachen zufrieden stellen. 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 mit 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. Experten, die alles wissen oder zu wissen glauben, können dieses Buch als Nachschlagewerk benutzen, wenn sie mal etwas vergessen haben sollten oder sich in einer Sache mal doch nicht so ganz sicher sind. 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. Insbesondere im zweiten Teil befinden sich viele Themen, die auch Anwendern von Python noch unbekannt sein könnten. 1

als Übersetzung des englischen Fachbegriffs „user”

3

4

1 Einleitung

Wenn Programmieranfänger oder auch Programmierumsteiger von anderen Programmiersprachen erfolgreich den ersten Teil des Buches bearbeitet haben, sollten sie in der Lage sein, auch den zweiten Teil zu verstehen. Vielleicht mit Ausnahme des Kapitels über die Systemprogrammierung, weil dieses Kapitel Vorkenntnisse über Betriebssysteme, vor allem Linux und Unix, voraussetzt.

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 hinzuzunehmen oder dass man den Stoff nochmals in anderen – möglicherweise auch einfacheren – Worten erklären sollte. Beim Schreiben eines 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 nun hilft, diese Klippen zu umgehen, führt bei anderen 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 halt, 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 auf 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 fest gebissen haben. Wenn Sie das Glück haben, in einer Umgebung zu arbeiten, in der es andere Python-Programmierer gibt, haben Sie es natürlich gut. Aber viele Leserinnen und Leser haben nicht diesen Vorteil. Dann könnte sich ein Besuch unserer Webseite besonders lohnen: http://www.python-kurs.eu/buch/ Dort finden Sie ein Korrekturverzeichnis, zusätzliche bzw. aktualisierte Übungen und sonstige Hilfestellungen. Außerdem finden Sie hier auch ein Forum, in dem Sie Ihr Problem schildern können, sodass andere Ihnen hel-

1.6 Anregungen und Kritik

fen können. Ansonsten ist natürlich Google, wie in so vielen anderen Situationen auch, ein williger Helfer. 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: [email protected]. Natürlich gilt dies auch, wenn Sie Anregungen zum Buch geben wollen. Leider können wir jedoch – so gerne wir es auch tun würden – keine individuellen Hilfen zu speziellen Problemen geben. Dazu soll unser Forum dienen.

1.6

Anregungen und Kritik

Trotz großer Sorgfalt können wir sicherlich nicht verhindern, dass sich der eine oder andere Fehler eingeschlichen hat. Wenn Sie also Kritik, Anmerkungen oder auch Wünsche haben, senden Sie uns einfach eine E-Mail an [email protected]. Wir werden versuchen, dies alles in kommenden Auflagen zu berücksichtigen. Die jeweils aktuellsten Ergänzungen und weitere Informationen können Sie unter http://www.python-kurs.eu/buch/ finden. Bernd Klein im Mai 2013

5

TEIL I Einführung in Python 3 – Für Ein- und Umsteiger

,

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. $ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57)

10

2 Kommandos und Programme

[GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>

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. $ python Python 2.7.3 (default, Sep 26 2012, 21:53:58) [GCC 4.7.2] 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. Wir haben die Version 3.2.2 von ActiveState installiert.

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.2” oder „Python 3.3”, unter dem sich die Untereinträge „IDLE (Python GUI)” und „Python (command line)” bzw. „Python Interactive Shell” befinden. Um die nachfolgenden Beispiele und Übungen nachvollziehen zu können, starten Sie bitte die Python-Kommandozeile, also „Python (command line)” bzw. „Python Interactive Shell”. Sie erhalten ein schwarzes Terminal-Fenster. Alternativ kann man auch mit IDLE arbeiten. Dabei handelt es sich um eine Mini-Entwicklungsumgebung, auf die wir hier jedoch nicht eingehen wollen.

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” (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. In Englisch 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 werden 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 dem blinkendem Cursor hinter dem Prompt „»>” auf Ihre Eingabe wartet.

11

12

2 Kommandos und Programme

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: >>> 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: >>> _ 85 >>>

Der Unterstrich kann im Prinzip wie eine normale Variable benutzt werden: >>> _ * 3 255 >>>

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' >>>

2.3 Verlassen der Python-Shell

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 unten („↑”), also zu älteren Befehlen, und nach oben („↓”), 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.2 Emergency Exit

Alle Einstellungen wie beispielsweise Variablen oder eingegebene Funktionsdefinitionen gehen beim Verlassen der Python-Shell verloren und sind beim nächsten Start nicht mehr vorhanden.

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

13

14

2 Kommandos und Programme

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

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 Zeichenkette2 „Hallo Welt” oder auf Englisch „Hello World” verwendet. Wir wollen dieser Tradition folgen.

2

BILD 2.3 Hallo Welt

Eine Folge von Zeichen wird als „Zeichenkette” oder meistens als „String” bezeichnet.

2.6 Programme schreiben oder schnell mal der Welt “Hallo” sagen

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 Programm-Code ist in C++ nötig, um ein Programm zu schreiben, was nach dem Start nur den Text „Hello World” in einer Zeile ausgibt: #include using namespace std; int main() { cout >> import beispiel Hallo ich komme von beispiel.py >>>

In Ihrem Verzeichnis finden Sie anschließend ein Unterverzeichnis „__pycache__ „beispiel. cpython-32.pyc”

19

4 4.1

Datentypen und Variablen

Einführung

Haben Sie bereits in Sprachen wie C und C++ programmiert? Deswegen glauben Sie, dass Sie bereits genug über Datentypen und Variablen wissen? Vorsicht ist geboten! Sie wissen sicherlich viel, das stimmt, aber wahrscheinlich nicht genug, wenn es um Python geht. Deshalb lohnt es sich auf jeden Fall, hier weiterzulesen. Es gibt gravierende Unterschiede in der Art, wie Python und C Variablen behandeln. 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 und C++.

BILD 4.1 Variablen als Container

Möchte man Listen oder assoziative Arrays in C oder C++ verwenden, dann muss man sie sich selbst mühsam von Grund auf inklusive Zugriffs- und Suchfunktionen programmieren oder schwer verständliche Libraries verwenden. Dieses Kapitel ist also sowohl für Anfänger als auch für fortgeschrittene Programmierer zu empfehlen. Eine Variable im allgemeinsten Sinne ist einfach ein Behälter (Container) zur Aufbewahrung von bestimmten Werten, also z.B. Strings oder Zahlen. Wie der Name sagt, sind Variablen „variabel”, d.h. sie können ihren Wert ändern. Man kann im Verlauf des Programms auf diese Variablen oder genauer: auf den Wert ihres Inhalts zugreifen, oder ihnen einen neuen Wert zuweisen. Im Folgenden kreieren wir eine Variable i, indem wir ihr einfach den Wert 42 unter Verwendung eines Gleichheitszeichens zuweisen: >>> i = 42

Man bezeichnet dies als Zuweisung oder auch als Definition einer Variablen.

22

4 Datentypen und Variablen

Anmerkung: Obige Anweisung darf man nicht als mathematisches Gleichheitszeichen sehen, sondern als „der Variablen i wird der Wert 42 zugewiesen”, d.h. der Inhalt von i ist nach der Zuweisung 42. Man kann die Anweisung also wie folgt „lesen”: „i soll sein 42”. Wir können nun diese Variable zum Beispiel benutzen, indem wir ihren Wert mit print ausgeben lassen: >>> print("Wert von i: ", i) Wert von i: 42 >>>

Wir können Sie aber auch auf der rechten Seite einer Zuweisung in einem Rechenausdruck verwenden, dessen Ergebnis dann einer Variablen zugewiesen wird: >>> x = i / 3.0 + 5.8 >>> print(x) 19.8 >>>

Man kann eine Variable auch gleichzeitig auf der rechten und der linken Seite einer Zuweisung benutzen. Im Folgenden erhöhen wir den Wert der Variablen i um 1: >>> i = i + 1 >>> print(i) 43 >>>

4.2

Statische und dynamische Typdeklaration

In Programmiersprachen unterscheidet man zwischen verschiedenen Datentypen. So bezeichnet man Zahlen wie -2, -1, 0, 1, 2 als ganze Zahlen, Ganzzahlen oder meistens mit dem englischen Wort Integer. Zahlen wie 0.0676 oder 13.897 werden als Fließkommazahlen oder Float-Zahlen bezeichnet.1 Wer Erfahrungen mit C, C++, Java oder ähnlichen Sprachen hat, hat dort gelernt, dass man, bevor man eine Variable verwenden darf, ihr einen Typ zuordnen muss. Der Datentyp einer Variablen muss im Programm festgelegt werden und zwar bevor die Variable benutzt oder definiert wird. Dies sind dann beispielsweise in einem C-Programm so aus: int i, j; float x; x = i / 3.0 +5.8; 1

Man beachte, dass in Programmiersprachen als Dezimaltrennzeichen immer ein Punkt anstelle eines Kommas verwendet wird.

4.2 Statische und dynamische Typdeklaration

Während des Programmlaufes können sich dann die Werte für i, j und x ändern, aber ihre Typen sind für die Dauer des Programmlaufes fest auf int im Falle von i und j und float für die Variable x festgelegt. Dies bezeichnet man als „statische Typdeklaration”, da bereits der Compiler den Typ festlegt und während des Programmablaufes keine Änderungen des Typs mehr vorgenommen werden können. In Python sieht dies anders aus. 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 folgenden Anweisung: >>> i = 42

Damit hat man automatisch eine Variable vom Typ Integer deklariert und definiert. Man kann dann im weiteren Verlauf des Programmes der Variablen i auch beliebige Werte von 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) >>> i = "Hallo" >>> type(i) >>> i = [3,9,17] >>> type(i) >>>

Während sich bei statischen Typdeklarationen, also bei Sprachen wie C und C++, nur der Wert, aber nicht Typ einer Variablen während eines Laufes ändert, kann sich bei dynamischer Typdeklaration, also in Python, sowohl der Wert als auch der Typ einer Variablen ändern. Aber Python achtet auf Typverletzungen. Man spricht von einer Typverletzung2 , wenn Datentypen beispielsweise aufgrund fehlender Zuweisungskompatibilität nicht regelgemäß verwendet werden. In Python kann es nur bei Ausdrücken zu Problemen kommen, da man

2

engl. type conflict

23

24

4 Datentypen und Variablen

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: >>> x = "Ich bin ein String" >>> y = 42 >>> z = x + y Traceback (most recent call last): File "", line 1, in TypeError: Can't convert 'int' object to str implicitly >>>

Allerdings kann man Integer und Floats – 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) >>> type(y) >>> type(z) >>>

4.3

Typumwandlung

In der Programmierung bezeichnet man die Umwandlung eines Datentyps in einen anderen als Typumwandlung3 . 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 Bei-

3

engl. type conversion oder cast

4.4 Datentyp ermitteln

spiel 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 "", line 1, in TypeError: Can't convert 'int' object to str implicitly >>>

Der Text 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 Floatwerte in einem Ausdruck gemischt hatten. Dort wurde der Integer-Wert implizit in einen Float-Wert gewandelt.

4.4

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) >>> x = 4 >>> type(x) >>> x = 4.5 >>> type(x) >>> x = "Ein String" >>> type(x) >>>

Als Alternative zu der 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

25

26

4 Datentypen und Variablen

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

Allerdings bietet isinstance hierfür eine bequemere Methode. Statt eines Typs gibt man als zweites Argument eine Liste von Typen an: >>> x = 4.8 >>> isinstance(x,(int, float)) True >>> x = (89,123,898) >>> isinstance(x,(list,tuple)) True >>> isinstance(x,(int, float)) False >>>

5 5.1

Sequentielle Datentypen

Übersicht

Auch Perl kennt die Datentypen String und List, 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 Tupeln gemeinsam: Es handelt sich jeweils um Datentypen, deren Elemente sequentiell angeordnet sind. Bei Strings handelt es sich dabei um gleichartige Objekte, d.h. Zeichen, und bei Listen oder Tupeln handelt es sich um beliebige Element. In Python gibt es deshalb gleichnamige Methoden oder Zugriffsmethoden für sequentielle Datentypen, wenn eine solche Operation für einen Typ erlaubt ist. Beispielsweise kann man mit obj[0] auf das erste Element eines sequentiellen Datentyps zugreifen, egal ob es sich um einen String, eine Liste oder einen Tupel handelt. Aber man kann obj[0] nicht verändern, falls „obj” ein String ist.

BILD 5.1 Fotos aus dem Film „The Kiss” von 1896

Python stellt die folgenden sequentiellen Datentypen zur Verfügung:

5.1.1

Zeichenketten oder Strings

Strings bestehen aus einer Folge – also Sequenz – von beliebigen Zeichen. Um einen String in Python zu generieren, werden diese Zeichen in einfache oder doppelte Hochkommata gestellt, also z.B. ’Ich bin ein String’ und “Ich bin auch ein String”.

28

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. 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.

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.

5.1.4

Sequenz von Binärdaten

Man unterscheidet unveränderliche (bytes) und veränderliche (bytearray) Binärdaten. Auf diesen Datentyp werden wir in einem späteren Kapitel eingehen.

5.2

Indizierung

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:

BILD 5.2 Indizierung eines Strings

5.2 Indizierung von sequentiellen Datentypen

>>> text = "Hallo Kaa" >>> print(text[0]) H >>> print(text[6]) K >>>

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 und so weiter. >>> text = "Es geht auch von hinten!" >>> text[-1] '!' >>> text[-2] 'n' >>>

Dies funktioniert bei allen Objekten von sequentiellen Datentypen, also auch bei Listen und Tupeln: >>> 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!" Traceback (most recent call last): File "", line 1, in TypeError: 'tuple' object does not support item assignment >>>

Im obigen Beispiel konnten wir sehen, dass wir nur Listen, aber keine Tupel ändern können.

29

30

5 Sequentielle Datentypen

5.3

Slicing oder Ausschneiden

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. In der Überschrift zu diesem Kapitel benutzen wir sowohl den Begriff „Slicing” als auch „Ausschneiden”, da wir uns nicht für die deutsche Übersetzung eindeutig entscheiden wollten. Der Begriff „Slicing” wird in der deutschen Python-Literatur allzu häufig benutzt. Deshalb werden wir die beiden Begriffe im Folgenden synonym benutzen. Wie bei der Indizierung benutzt der Slicing-Operator eckige Klammer, aber nun werden statt einem Wert mindestens zwei Werte erwartet: Anfangswert und Endwert. Man versteht dies am besten an einem Beispiel: >>> txt = "Hello World" >>> txt[1:5] 'ello' >>> txt[0:5] 'Hello' >>> txt = "Hello World" >>> 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: >>> colours = ['red', 'green', 'blue'] >>> colours[1:3] ['green', 'blue'] >>> colours[2:] ['blue'] >>> colours[:2] ['red', 'green'] >>> >>> colours[-1] 'blue'

5.4 Aufgaben

Der Slicing-Operator funktioniert auch mit drei Argumenten. Das dritte Argument gibt dann an, das wievielte Argument jedes Mal genommen werden soll, d.h. s[begin, end, step]. Ausgegeben werden dann die folgenden Elemente von s: s[begin], s[begin + 1 * step], ... s[begin + i * step], solange (begin + i * step) < end ist. txt[::3] gibt jeden dritten Buchstaben eines Strings aus. Beispiel: >>> txt = "Python ist ganz toll" >>> txt[2:15:3] 'tnsgz' >>> txt[::3] 'Ph ta l'

5.4

Aufgaben

1. Aufgabe: Welche sequentiellen Datentypen haben Sie kennengelernt? Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 379

2. Aufgabe: Wie erhält man das erste Element einer Liste? Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 379

3. Aufgabe: Wie kann man am einfachsten das letzte Zeichen in einem String ausgeben? Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 379

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"

31

32

5 Sequentielle Datentypen

>>> >>> >>> >>>

l[2][0] = "g" l[3][0] = "g" l t2[2][0] = 42

Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 380

5. Aufgabe: Welche zwei Sätze verbergen sich hinter dem folgenden scheinbaren Buchstabensalat: ’DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre lsbiscthh inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg Mmaaxcihmte..’ Hinweis: Benutzen Sie Slicing! Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 381

6 6.1

Dictionaries

Dictionaries und assoziative Felder

In den vorigen Kapiteln BIS HIER 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 werBILD 6.1 Dictionary den in verschiedenen Programmiersprachen mit verschiedenen 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-Objekt-Paaren. Zu einem bestimmten Schlüssel gehört immer ein Objekt. Man kann also die Schlüssel auf die Objekte „abbilden”, daher der Kategorienname 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.

34

6 Dictionaries

6.2

Definition und Benutzung

Wir beginnen mit dem wohl einfachsten Fall eines Dictionarys, dem leeren: >>> empty = {} >>> empty {}

Zu Ehren des Schutzheiligen 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 ein ganzes Directory 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 Directories entspricht. In unserer Definition stand als erstes „ham”, während unsere Ausgabe mit „egg” beginnt. Dies ist in Ordnung, da auf dem Directory 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 Wertes 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

In obigem Dictionary können wir nur über die englischen Schlüssel zugreifen. 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, von Englisch nach Französisch zu übersetzen, obwohl wir gar kein Englisch-Französisch-Wö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 "", line 1, in KeyError: 'brown' >>>

Wir erzeugen also einen Fehler, einen KeyError, wenn wir auf einen nicht existierenden Schlüssel zuzugreifen versuchen. Um solche Fehler zu vermeiden, werden häufig bedingte Anweisungen verwendet. Auf diese werden wir erst in Kapitel 8 (NumPy) eingehen. Dennoch möchten wir Ihnen das typische Vorgehen hier nicht vorenthalten. Anfänger, die noch nie programmiert haben, können die folgenden paar Abschnitte getrost übergehen, damit sie keine Verständnisprobleme haben.

1

Nicht ohne Programmierung jedenfalls

35

36

6 Dictionaries

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] = "braun" 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 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'}

6.4 Zulässige Typen für Schlüssel und Werte

6.4

Zulässige Typen für Schlüssel und Werte

In einem Dictionary können beliebige Typen als Werte verwendet werden. Bei den Schlüsseln gilt jedoch die Einschränkung, dass nur Instanzen unveränderlicher (immutable) Datentypen verwendet werden können, also z.B. keine Listen und keine Dictionaries. Ansonsten erhält man eine Fehlermeldung: < >>> dic = { [1,2,3]:"abc"} Traceback (most recent call last): File "", line 1, in 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 >>>

37

38

6 Dictionaries

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 Dictionaries. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow ":"gelb"} >>> en_de.clear() >>> en_de {} >>>

copy(...) D.copy() erzeugt eine flache Kopie von D.2 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'} >>>

Auf die besondere Problematik zwischen flacher und tiefer Kopie gehen wir in 13 (Flaches und tiefes Kopieren) ein. 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 2

Auf die Unterschiede zwischen flacher und tiefer Kopie gehen wir in Kapitel 13 (Flaches und tiefes Kopieren) ein.

6.6 Methoden auf Dictionaries

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'} >>>

get(...) D.get(k[,d]) liefert D[k] zurück, falls k in D ist, ansonsten d. Der Default-Wert ist None. 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' >>>

items(...) D.items() liefert ein Mengen-ähnliches Objekt vom Type „dict_item” zurück, was einer View der Schlüssel-Werte-Paare entspricht. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow ":"gelb"} >>> x = en_de.items() >>> type(x) >>> 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) >>> x dict_keys(['blue', 'green', 'yellow', 'red']) >>>

39

40

6 Dictionaries

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 "", line 1, in 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. 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 "", line 1, in 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")

6.7 Operatoren

'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'} >>> 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 >>>

41

42

6 Dictionaries

6.8

Zip

Diese sehr nützliche Funktion gehört eigentlich nicht in die Dictionary-Klasse. Es ist eine Funktion, die Sequenzen als Ein- und Ausgaben hat. Dennoch behandeln wir sie an dieser Stelle, weil wir sie im folgenden Abschnitt benötigen werden. Mit Zip kann man eine Matrix transponieren. >>> z1 = [11, 12, 13] >>> z2 = [21, 22, 23] >>> z3 = [31, 32, 33] >>> >>> T = zip(z1, z2, z3) >>> T >>> list(T) [(11, 21, 31), (12, 22, 32), (13, 23, 33)] >>> >>> M = zip(*T) >>> list(M) [] >>> T = zip(z1, z2, z3) >>> M = zip(*T) >>> list(M) [(11, 12, 13), (21, 22, 23), (31, 32, 33)] >>>

Im obigen Beispiel hatten wir zip mit dem Argument *T aufgerufen. Der SternchenOperator kann auf ein Tupel, eine Liste oder ganz allgemein auf einen sequentiellen Datentyp bzw. Iterator angewendet werden, um diesen zu „entpacken”.3 Im obigen Beispiel wird zip mit den drei Argumenten (11, 21, 31), (12, 22, 32) und (13, 23, 33) aufgerufen. Hätte man T statt *T geschrieben, wäre zip mit einer Liste bestehend aus den vorigen Tupeln aufgerufen worden, d.h. mit [(11, 21, 31), (12, 22, 32), (13, 23, 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))

3

Auf diesen Mechanismus gehen wir in Kapitel Funktionen, Seite 115 näher ein.

6.9 Dictionaries aus Listen erzeugen

[(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 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. Deshalb 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' >>>

43

44

6 Dictionaries

6.10

Aufgaben

1. Aufgabe:

BILD 6.2 Datei-Menü 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 Print Preview – Druckansicht – Apercu avant impressioner – Anteprima di stampa Print – Drucken – Imprimer – Stampa Close – Schließen – Fermer – Chiudi Exit – Verlassen – Quitter – Esci Lösung: Lösungen zu Kapitel 6 (Dictionaries), Seite 381

6.10 Aufgaben

2. Aufgabe:

BILD 6.3 Schachbrett Überlegen Sie sich eine Darstellung des Schachbretts als Dictionary. Die Schlüssel dieses Dictionaries sollen die Felder des Schachbrettes 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 382

45

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 1977, 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änderli-

48

7 Mengen

chen 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.

7.2.1

Sets erzeugen

Will man eine Menge erzeugen, so ist dies in Python3 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) >>>

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:

7.3 Frozensets

>>> cities = set((("Python","Perl"), ("Paris", "Berlin", "London"))) >>> cities = set((["Python","Perl"], ["Paris", "Berlin", "London"])) Traceback (most recent call last): File "", line 1, in TypeError: unhashable type: 'list'

7.3

Frozensets

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 "", line 1, in 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 "", line 1, in TypeError: unhashable type: 'list' >>>

49

50

7 Mengen

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.

7.4.2

clear()

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"}

7.4 Operationen auf „set”-Objekten

>>> x.difference(y) set(['a', 'e', 'd']) >>> x.difference(y).difference(z) {'a', 'e'} >>>

Statt die Methode difference zu benutzen, hätten wir auch den Operator „-” benutzen können: >>> 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'} >>>

51

52

7 Mengen

7.4.7

remove(el)

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 "", line 1, in 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.intersection(y) {'c', 'e', 'd'} >>> >>> 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()

x.issubset(y) liefert True zurück, falls x eine Untermenge von y ist. „>> x = {"a","b","c","d","e"} >>> y = {"c","d"} >>> x.issubset(y) False >>> y.issubset(x) True >>> >>> x > y True >>> y < 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. >>> x = {"a","b","c","d","e"} >>> y = {"c","d"} >>> x.issuperset(y) True >>> >>> x > y True >>>

7.4.12

pop()

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'

53

8 8.1

Verzweigungen

Übersicht

Bisher sind wir in der Lage, Programme zu schreiben, in denen eine Anweisung auf die andere folgt und diese auch in dieser Reihenfolge ausgeführt werden. Aber nur wenige Probleme lassen sich durch einen linearen Programmablauf kontrollieren. Man möchte beispielsweise einen bestimmten Teil des Programms nur dann ausführen, wenn bestimmte Bedingungen zutreffen, oder ein anderer Teil soll gegebenenfalls mehrmals ausgeführt werden. Dazu bietet jede Programmiersprache Kontrollstrukturen, die sich in zwei Kategorien unterscheiden lassen: Verzweigungen und Schleifen.

BILD 8.1 Verzweigungen

In diesem Kapitel beschäftigen wir uns mit bedingten Anweisungen, wie Verzweigungen auch genannt werden. Dies sind Codeteile, die unter einer bedingten Bedingung ausgeführt werden. Ist die Bedingung 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, die sich aus Eingaben und Berechnungen ergeben, reagieren kann.

8.2

Bedingte Anweisungen in Python

Die einfachste Form einer if-Anweisung sieht in Python wie folgt aus: if bedingung: anweisungen

56

8 Verzweigungen

Der eingerückte Codeblock wird nur ausgeführt, wenn die Bedingung „bedingung” wahr, also True ist. Ganz allgemein sieht es so aus: if bedingung: anweisungen elif bedingung: anweisungen ... elif bedingung: anweisungen else: anweisungen

Wir wollen uns im folgenden Beispiel-Skript mit der einfachen if-Anweisung beschäftigen, also ohne „elif” und „else”. person = input("Nationalität? ") if person == "französisch": print("Préférez-vous parler français?") if person == "italienisch": print("Preferisci parlare italiano?")

Obiges kleines Python-Skript hat zwei Nachteile. Wenn jemand als Nationalität „französisch” angegeben hat, wird wie zu erwarten die print-Anweisung des ersten if ausgeführt. Anschließend wird aber in der nächsten if-Anweisung geprüft, ob „person” möglicherweise „italienisch” ist, was in diesem Fall unmöglich ist. Dieses Problem kann man mit einem „elif” lösen. Der Ausdruck hinter dem „elif” wird nur geprüft, wenn der Ausdruck beim letzten „elif” oder „if” „False” war. Zum anderen gibt es aber auch noch andere Nationalitäten. Wir haben keine Anweisungen für diesen Fall. Wir können als Default Englisch einstellen. Dazu benötigen wir noch den else-Zweig der if-Anweisung. Der Block unter dem „else” wird immer ausgeführt, wenn die Ausdrücke hinter dem „if” und den „elif”s nicht zutrafen (True). person = input("Nationalität? ") if person == "französisch": print("Préférez-vous parler français?") elif person == "italienisch": print("Preferisci parlare italiano?") else: print("You are neither Italian nor French,") print("so we have to speak English.")

8.3

Beispiel: Hundejahre

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 Menschen-

8.4 Wahr oder falsch: Bedingungen in Verzweigungen

jahre 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 Menschens.



Ab dann entspricht ein Hundejahr jeweils 5 Menschenjahren.

Das folgende kleine Beispielprogramm verlangt die Eingabe für das Alter des Hundes und berechnet nach obiger Regel das Alter in Menschenjahren: age = 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 ### raw_input('press Return>')

8.4

Wahr oder falsch

„Was nicht verboten ist, ist erlaubt”: So funktioniert unser Gesetz. 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 boolsche Wert False,



leere Zeichenketten,



leere Listen, leere Tupel,



leere Dictionaries



sowie der spezielle Wert None.

Alle anderen Werte betrachtet Python als „wahr”, also True.

57

58

8 Verzweigungen

8.5

Aufgaben

1. 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 8 (Verzweigungen), Seite 383

2. Aufgabe: Auch bei der zweiten 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 Integerwerte ein. Das Programm soll die neue Uhrzeit in der obigen Form ausgeben, also hh:mm:ss. Lösung: Lösungen zu Kapitel 8 (Verzweigungen), Seite 384

9 9.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 dem 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 BILD 9.1 Karussell der Rumpf 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.

60

9 Schleifen

9.2

while-Schleife

Das folgende Skript, das wir in der interaktiven Shell direkt eintippen können, gibt die Zahlen von 1 bis 10 unter Benutzung einer while-Schleife aus: >>> i = 1 >>> while i >>

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: #!/usr/bin/env python3 n = 100 sum = 0 i = 1 while i 0: if guess > to_be_guessed: print("Number too large") else: print("Number too small") else: print("Sorry, that you're giving up!") break

1

Es gibt auch eine else-Schleife in C und C++. Diese wird aber nur selten verwendet.

61

62

9 Schleifen

else: print("Congratulation. You made it!")

Die Ausgabe einer Spielsitzung könnte beispielsweise so aussehen: $ python3 number_game.py New number: 12 Number too small New number: 15 Number too small New number: 18 Number too large New number: 17 Number too small Congratulation. You made it! $

9.4

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 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.

9.4 For-Schleife

for Variable in Sequenz: Anweisung_1 Anweisung_2 ... Anweisung_n else: Else-Anweisung_1 Else-Anweisung_2 ... Else-Anweisung_m

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. 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. Dieser Block wird 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: edibles = ["ham", "spam","eggs","nuts"] for food in edibles: if food == "spam": print("No more spam please!") break print("Great, delicious " + food) else: print("I am so glad: No spam!") print("Finally, I finished stuffing myself")

Wenn wir obiges Beispiel unter for.py speichern und aufrufen, erhalten wir folgende Ausgaben:

63

64

9 Schleifen

$ python for.py Great, delicious ham No more spam please! Finally, I finished stuffing myself $

Wenn wir „spam” aus der Liste der essbaren Dinge entfernen, erhalten wir folgende Ausgabe: $ python for.py Great, delicious ham Great, delicious eggs Great, delicious nuts I am so glad: No spam! Finally, I finished stuffing myself $

Vielleicht ist unsere Abscheu vor dem Dosenfutter „spam” nicht so groß, dass wir sofort aufhören zu essen. In diesem Fall kommt die continue-Anweisung ins Spiel. Im folgenden kleinen Skript benutzen wir continue, um mit dem nächsten Artikel der essbaren Artikel weiterzumachen. „continue” schützt uns davor, „spam” essen zu müssen: edibles = ["ham", "spam", "eggs","nuts"] for food in edibles: if food == "spam": print("No more spam please!") continue print("Great, delicious " + food) # here can be the code for enjoying our food :-) else: print("I am so glad: No spam!") print("Finally, I finished stuffing myself")

Die Ausgabe sieht dann wie folgt aus: $ python for.py Great, delicious ham No more spam please! Great, delicious eggs Great, delicious nuts I am so glad: No spam! Finally, I finished stuffing myself $

9.5 Aufgaben

9.5

Aufgaben

Für die erste Aufgabe benötigen wir die römischen Zahlen. Für diejenigen, die sich nicht so ganz sicher sind mit römischen Zahlen, geben wir hier die Zuordnungen in der folgenden Tabelle. TABELLE 9.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

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

65

66

9 Schleifen

1. Aufgabe: Schreiben Sie ein Python-Programm, das eine beliebige römische Zahl in eine „gewöhnliche” Dezimalzahl umrechnet. Lösung: 33.4 (1. Aufgabe), Seite 385

2. Aufgabe:

BILD 9.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 überqueren, 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: n 1 X 1 1 1 1 = 1+ + + + ... i 2 4 8 16 i =1

Versuchen Sie, mittels eines Python-Programmes herauszubekommen, ob der Frosch es auf die andere Straßenseite schafft. Lösung: 33.4 (2. Aufgabe), Seite 386

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

9.5 Aufgaben

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 mit Hilfe eines Python-Programmes ohne eine Formel zu Hilfe zu nehmen, wieviele Weizenkörner angehäuft werden müssen. Lösung: 33.4 (3. Aufgabe), Seite 387

2

Die Weizenkornlegende wurde wörtlich von Wikipedia entnommen

67

10 10.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 10.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.

10.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

70

10 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)

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 Defaultwert: fobj = open("ad_lesbiam.txt")

Sehr häufig bearbeitet man eine Datei zeilenweise, d.h. man liest eine Datei Zeile für Zeil. 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.”

10.3 Schreiben in eine Datei

10.3

Schreiben in eine Datei

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.

10.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:

71

72

10 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,

10.5

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: 33.5 (1. Aufgabe), Seite 388

2. Aufgabe: Schreiben Sie ein Programm, das jeweils drei Zeilen aus der folgenden Datei zu jeweils einer zusammenfasst und diese als Standardausgabe mittels print() ausgibt.

10.5 Aufgaben

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: 33.5 (2. Aufgabe), Seite 389

3. Aufgabe: Ändern Sie das vorige Programm so, dass es nun die Ausgabe in eine Ausgabedatei schreibt. Lösung: 33.5 (3. Aufgabe), Seite 389

73

11 11.1

Listen und Tupel im Detail

Stapelspeicher

In den bisherigen Kapiteln haben wir bereits 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 zwischendurch ein neues Buch, kommt es oben drauf und ist damit das nächste Buch, was gelesen werden „muss”.

BILD 11.1 Alles Strings

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 diese 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”.

englisch: stack oder „rechts”, je nach Sichtweise

76

11 Listen und Tupel im Detail



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 ist, dann entspricht liste[-1] einem liste.peek(), wenn es peek bei Python gäbe.

11.2 ■





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']

BILD 11.2 pop und append

11.3 extend

11.3

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]]

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("Ich bin ein String") >>> liste ['I', 'c', 'h', ' ', 'b', 'i', 'n', ' ', 'e', 'i', 'n', ' ', 'S', 't', 'r ', 'i', 'n', 'g'] >>>

11.4

Entfernen eines Wertes

Mit der Methode „remove” kann man einen bestimmten Wert ohne Kenntnis des Indexes aus einer Liste entfernen. s.remove(x)

77

78

11 Listen und Tupel im Detail

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” ist, 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 "", line 1, in ValueError: list.remove(x): x not in list >>>

11.5

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

11.6

Positionsbestimmung

Mit der Methode index kann man die Position eines Elements innerhalb einer Liste ermitteln. s.index(x[, i[, j]]) 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.

11.7 Einfügen von Elementen

>>> 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 "", line 1, in ValueError: 'black' is not in list >>>

11.7

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'] >>>

Die Methode „append” lässt sich sehr einfach mit „insert” realisieren: >>> abc = ["a","b","c"] >>> abc.insert(len(abc),"d") >>> abc ['a', 'b', 'c', 'd'] >>>

11.8

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:

79

80

11 Listen und Tupel im Detail

>>> t = (1,1,2,3,5,8,13) >>> t[0] = 42 Traceback (most recent call last): File "", line 1, in TypeError: 'tuple' object does not support item assignment >>>

11.8.1

Leere Tupel

Leere Tupel definiert man mit leeren runden Klammern: >>> t = () >>> type(t) >>>

11.8.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, also zum Beispiel: ((a + b) ∗ c) Aber auch dies ist ein gültiger Ausdruck: (c) Damit versteht man auch, dass „t = (5)” eine Integerzahl und nicht einen Einer-Tupel mit der Zahl 5 definiert: >>> t = (5) >>> type(t) >>>

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) >>>

11.9 Die veränderliche Unveränderliche

11.8.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. Die folgende Anweisung bezeichnet man als Tupel-Packing, da man die Zahlen 47 und 11 in ein Tupel packt: >>> t = "Philip", "Glass" >>> type(t) >>>

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

11.9

Die veränderliche Unveränderliche

Wir haben festgestellt, dass sich Tupel nicht verändern können! Oder können sie es vielleicht doch? Folgender Code zeigt scheinbar, dass es doch geht: >>> 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 nicht wirklich um die Erde dreht.

81

82

11 Listen und Tupel im Detail

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 "", line 1, in TypeError: 'tuple' object does not support item assignment >>>

Nach der Lektüre unseres Kapitels 13 (Flaches und tiefes Kopieren) sollte das oben Gesagte völlig klar sein.

11.10 11.10.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 BILD 11.3 Alles Strings oder besser gesagt den speziellen Wert „None”. Ein beliebter Fehler besteht darin, 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: >>> liste = [32,7,12,20,8] >>> liste = liste.sort() >>> print(liste) None >>>

11.10 Sortieren von Listen

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] >>>

11.10.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] >>>

11.10.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 Funktionsreferenz, die als Argument den gleichen Typ wie die Listenelemente erwartet. Die Arbeitsweise kann man sich mathematisch wie folgt vorstellen: Die zu sortierende Liste sieht wie folgt aus:

BILD 11.4 sort mit eigener Sortierabbildung

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: [ f (x 1 ), f (x 2 ), f (x 3 ), . . . f (x n )]

83

84

11 Listen und Tupel im Detail

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 "", line 1, in 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 Elementen 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. Sehr elegant kann man diese Aufgabe mit der lambda-Notation lösen, die wir aber erst in Kapitel lambda, map, filter und reduce, Seite 289 behandeln: >>> phone = [("John", "Miller", "4567"), ("Tina", "Baker", "4289"), (" Randy", "Steiner", "4103")] >>> sorted(phone, key=lambda x:x[0])

11.10 Sortieren von Listen

[('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 Elementes 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' >>>

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))

85

86

11 Listen und Tupel im Detail

[('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')] >>>

11.11

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 11 (Listen und Tupel im Detail), Seite 390

2. Aufgabe: Schreiben Sie mit Hilfe 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 11 (Listen und Tupel im Detail), Seite 390

3. Aufgabe: Sortieren Sie eine Liste von Zahlen in alphabetischer Reihenfolge, d.h. das gilt: 1 < 10 < 111 < 19 < 2

Lösung: Lösungen zu Kapitel 11 (Listen und Tupel im Detail), Seite 391

11.11 Aufgaben

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 11 (Listen und Tupel im Detail), Seite 391

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 Tupeln 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 11 (Listen und Tupel im Detail), Seite 391

87

12 12.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 12.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 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 Quelltextes 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

90

12 Modularisierung



lokale Module nur für ein Programm verfügbar

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. 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()): >>> 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 der Übersichtlichkeit willen an den Anfang stellen.

12.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 "", line 1, in NameError: name 'sin' is not defined

12.1 Module

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

12.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)

12.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

91

92

12 Modularisierung

Um eine Liste dieser Module zu erhalten: >>> 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')

12.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.3.

sys.path enthält die Verzeichnisse, in denen Module gesucht werden: >>> import sys >>> for dir in sys.path: ... print(dir) ... /usr/lib/python3.2 /usr/lib/python3.2/plat-linux2 /usr/lib/python3.2/lib-dynload /usr/local/lib/python3.2/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 "", line 1, in AttributeError: 'module' object has no attribute '__file__'

Wir 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.

12.1 Module

12.1.5

Inhalt eines Modules

Mit der built-in-Funktion dir() kann man sich die in einem Modul definierten Namen ausgeben lassen. >>> dir(math) ['__doc__', '__name__', '__package__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees ', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'hypot', 'isfinite', 'isinf', ' isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'modf', '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.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 math >>> a = 42 >>> b = 4.5 >>> l = [2344,"Hallo"] >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'a', 'b', 'l', ' math'] >>>

12.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 def fiblist(n): fib = [0,1] for i in range(1,n): fib += [fib[-1]+fib[-2]] return fib

93

94

12 Modularisierung

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' >>>

12.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: """ 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

12.2 Pakete

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

12.2

Pakete

Beim Verfassen sprachlicher Dokumente steht natürlich der Inhalt selbst im Vordergrund, aber man wird wohl kaum diesen Inhalt an seine Leserinnen und Leser bringen können, wenn dieser Inhalt nicht gegliedert 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 zu verstehen und zu warten. Im vorigen Kapitel BILD 12.2 Beispielpaket in der Übersicht 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. 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. 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

95

96

12 Modularisierung

kann leer sein oder Initialisierungscode enthalten, der beim Einbinden des Pakets einmalig ausgeführt wird. Pakete können wiederum Pakete enthalten. Im Folgenden zeigen wir nun anhand eines einfachen Beispiels, wie man ein Paket erzeugt. Unser Paket soll SimplePackage heißen. Dazu erzeugen wir einen Ordner1 mit dem Namen SimplePackage. In diesem Ordner legen wir eine leere Datei mit dem Namen __init__.py an2 . 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 SimplePackage import a,b >>> a.f1() Hello, f1 from module 'a' calling >>> b.foo() Hello, foo from module 'b' calling

Versucht man, das Paket SimplePackage direkt, also mittels „import SimplePackage” zu importieren, erhält man eine Fehlermeldung. Dies liegt daran, dass die Module in einem Paket nicht automatisch importiert werden: >>> import SimplePackage >>> SimplePackage.a.f1() Traceback (most recent call last): File "", line 1, in AttributeError: 'module' object has no attribute 'a'

Jetzt kann man das Initialisierungsverhalten von __init__.py ideal einsetzen. Wir fügen in diese bisher leere Datei die Zeile „from SimplePackage import a, b” ein und können nun obigen Versuch wiederholen. >>> import SimplePackage >>> SimplePackage.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.

1 2

In Linux oder Unix können wir dies mit dem Befehl „mkdir SimplePackage” tun. In Linux kann man dies mit einem touch-Kommando am einfachsten tum: „touch __init.py__”.

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() benutzt, 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, der eindeutig und konstant für dieses Objekt ist, solange es im Programmablauf existiert.

BILD 13.1 Tiefsee

Betrachten wir folgenden Code: >>> x = 3 >>> y = x

Es stellt sich die Frage, was bei der Zuweisung „y = x” 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:

98

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” bzw. teilen sich das gleiche Objekt: $ 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. >>> x = 3 >>> y = x >>> id(x) == id(y) True >>> print(id(x), id(y)) 137220832 137220832

Was passiert nun, wenn wir einer der beiden Variablen einen neuen Wert zuweisen, also beispielsweise die Zahl 4 der Variablen y: >>> y = 4 >>> id(x) == id(y) False >>> print(x,y) 3 4 >>>

Bildlich sieht die Situation nun also wie folgt aus:

Aber auch wenn das obige Verhalten ungewöhnlich im Vergleich zu anderen Programmiersprachen wie C, C++, Perl etc. ist, so entsprechen die Resultate der Zuweisungen dennoch unseren Erwartungen. Kritisch wird es jedoch, wenn wir veränderliche (mutable) Objekte wie Listen und Dictionaries kopieren wollen. Python legt nur dann echte Kopien an, wenn es unbedingt muss, d.h. dass es der Anwender, also der Programmierer, explizit verlangt. Ansonsten verhält sich Python ebenso wie im obigen einfachen Beispiel: Es gibt nur Zeiger auf das gleiche Objekt. Im Folgenden wollen wir dies im Detail darstellen und auf einige Probleme eingehen, die beim nicht „korrekten” Kopieren von veränderlichen Objekten entstehen können, also z.B. beim Kopieren von Listen und Dictionaries.

13.2 Kopieren einer Liste

13.2

Kopieren einer Liste

>>> colours1 = ["red", "green"] >>> colours2 = colours1 >>> print(colours1) ['red', 'green'] >>> print(colours2) ['red', 'green'] >>> print(id(colours1),id(colours2)) 3072482700 3072482700 >>> colours2 = ["rouge", "vert"] >>> print(colours1) ['red', 'green'] >>> print(colours2) ['rouge', 'vert'] >>> print(id(colours1),id(colours2)) 3072482700 3072470444

Im obigen kleinen Code-Beispiel legen wir als Erstes eine flache colours1 an. Mit „flach” (Englisch in diesem Zusammenhang: shallow) meinen wir, dass die Liste nicht verschachtelt ist, also keine weiteren Unterlisten enthält. Anschließen weisen die Liste colours1 einer Variablen colours2 BILD 13.2 Kopieren von Listen 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. Wie im obigen 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. >>> colours1 = ["red", "green"] >>> colours2 = colours1 >>> print(id(colours1),id(colours2)) 153810028 153810028 >>> colours2[1] = "blue" >>> print(id(colours1),id(colours2)) 153810028 153810028 >>> print(colours1) ['red', 'blue'] >>> print(colours2) ['red', 'blue'] >>>

Im obigen Beispiel sind wir der Frage nachgegangen, was passiert, wenn wir einer Variablen nicht ein neues Elements zuordnen, sondern nur ein einzelnes Element der Liste ändern.

BILD 13.3 Kopieren von Listen

99

100

13 Flaches und tiefes Kopieren

Um dies zu testen, weisen wir dem zweiten Element von colours2, also dem Element mit dem Index 1, einen neuen Wert zu. Viele wird es nun erstaunen, dass auch colours1 damit verändert wurde, obwohl man doch eine Kopie von colours1 gemacht zu haben glaubte. Wenn wir uns die Speicherorte mittels der Funktion id() anschauen, sehen wir, dass beide Variablen weiterhin auf das selbe Listenobjekt zeigen.

13.3

Kopie mit Teilbereichsoperator

Mit dem Teilbereichsoperator (slicing) kann man flache Listenstrukturen komplett kopieren, ohne dass es zu Nebeneffekten kommt, wie man im folgenden Beispiel sehen kann: >>> liste1 = ['a','b','c','d'] >>> liste2 = liste1[:] >>> liste2[1] = 'x' >>> print(liste2) ['a', 'x', 'c', 'd'] >>> 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[:] >>> 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][0]. 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 [:] mitkopiert.

BILD 13.5 Kopieren von Listen

13.4 Kopieren mit deepcopy aus dem Modul copy

13.4

Kopieren mit deepcopy aus dem Modul copy

Abhilfe für das eben beschriebene Problem schafft das Modul „copy”. Dieses Modul stellt die Methode „deepcopy” zur Verfügung, die das komplette Kopieren einer nicht flachen Listenstruktur erlaubt. Das folgende Skript kopiert unser obiges Beispiel nun mit Hilfe dieser Methode:

BILD 13.6 Kopieren mit Deepcopy

>>> from copy import deepcopy >>> lst1 = ['a','b',['ab','ba']] >>> lst2 = deepcopy(lst1) >>> lst2[2][1] = "d" >>> lst2[0] = "c"; >>> print(lst2) ['c', 'b', ['ab', 'd']] >>> print(lst1) ['a', 'b', ['ab', 'ba']]

Da es sich nun um eine vollständige Kopie ohne gemeinsame Referenzen handelt, wirken sich Änderungen „lst2” nicht auf „lst1 aus. Das Gleiche gilt natürlich auch umgekehrt, d.h. Änderungen an „lst1” wirken sich nicht auf „lst2” aus.

13.5

Deepcopy für Dictionaries

deepcopy aus dem Modul copy funktioniert für beliebige Python-Objekte, also beispielsweise auch für Dictionaries: >>> from copy import deepcopy >>> d = {"a":"1. Buchstabe", "b":"2. Buchstabe"} >>> d2 = deepcopy(d) >>> d2 {'a': '1. Buchstabe', 'b': '2. Buchstabe'} >>> d {'a': '1. Buchstabe', 'b': '2. Buchstabe'} >>> d2["a"] = "Erster Buchstabe" >>> d2 {'a': 'Erster Buchstabe', 'b': '2. Buchstabe'} >>> d {'a': '1. Buchstabe', 'b': '2. Buchstabe'}

Achtung: Die Methode copy der Klasse dict erzeugt nur flache Kopien eines Dictionarys, wie wir im Folgenden sehen können:

101

102

13 Flaches und tiefes Kopieren

>>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"} >>> de_fr = {"rot" : "rouge", "grün" : "vert", "blau" : "bleu"} >>> d = {"en_de" : en_de, "de_fr" : de_fr } >>> d2 = d.copy() >>> d {'en_de': {'blue': 'blau', 'green': 'grün', 'red': 'rot'}, 'de_fr': {'grü n': 'vert', 'blau': 'bleu', 'rot': 'rouge'}} >>> d2 {'en_de': {'blue': 'blau', 'green': 'grün', 'red': 'rot'}, 'de_fr': {'grü n': 'vert', 'blau': 'bleu', 'rot': 'rouge'}} >>>

Nun erzeugen wir mit deepcopy eine Kopie, und das Problem ist behoben: >>> from copy import deepcopy >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"} >>> de_fr = {"rot" : "rouge", "grün" : "vert", "blau" : "bleu"} >>> d = {"en_de" : en_de, "de_fr" : de_fr } >>> >>> d2 = deepcopy(d) >>> >>> d["en_de"]["brown"] = "braun" >>> >>> d {'en_de': {'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'red': 'rot '}, 'de_fr': {'grün': 'vert', 'blau': 'bleu', 'rot': 'rouge'}} >>> d2 {'en_de': {'blue': 'blau', 'green': 'grün', 'red': 'rot'}, 'de_fr': {'grü n': 'vert', 'blau': 'bleu', 'rot': 'rouge'}} >>>

14 14.1

Funktionen

Allgemein

Das Konzept einer Funktion gehört wohl zu den bedeutendsten der Mathematik. Auch in Programmiersprachen werden Funktionen 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. Dann müsste man den Code entBILD 14.1 Funktionen sprechend anpassen. Spätere Änderungen müssten dann an allen kopierten Stellen nachgezogen werden, was natürlich sehr fehlerträchtig wäre. Die Benutzung von Funktionen erhöht die Verständlichkeit und Qualität eines Programms oder Skripts. Außerdem senkt die Verwendung von Funktionen auch die Kosten für die Software-Entwicklung und die Wartung der Software. Funktionen kennt man unter unterschiedlichen Bezeichnungen in verschiedenen Programmiersprachen. So kennt man sie auch als Subroutinen, Routinen, Prozeduren, Methoden und Unterprogramme.

104

14 Funktionen

14.2

Funktionen in Python

Funktionen werden mit dem Schlüsselwort def eingeleitet def funktions-name(Parameterliste): Anweisung(en)

Die Parameterliste besteht aus einem oder mehr Bezeichnern, die durch Kommata getrennt sind. Die Parameter der Definition werden beim Aufruf der Funktion als Argumente bezeichnet. Meistens werden die beiden Begriffe jedoch fälschlicherweise wie Synonyme verwendet. Parameter können obligatorisch und optional sein. Die optionalen Parameter (0 oder mehr) folgen den obligatorischen. Der Funktionskörper (function body), also die Anweisungen, die bei Aufruf der Funktion ausgeführt werden, wird in Python durch eine eine homogene Einrückung markiert. Also ebenso wie alle anderen Blöcke in Python. 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 kein Ausdruck dem return folgt, wird der spezielle Wert None zurückgeliefert. Programmablauf: 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 Optionale Parameter

Im nebenstehenden Diagramm zeigen wir, was passiert, wenn man obiges 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. Ein weiteres Beispiel: def fahrenheit(T_in_celsius): """ returns the temperature in degrees Fahrenheit """ return (T_in_celsius * 9 / 5) + 32 for t in (22.6, 25.8, 27.3, 29.8): print(t, ": ", fahrenheit(t))

Die Ausgabe des obigen Skripts mit der Funktion fahrenheit: 22.6 25.8 27.3 29.8

: : : :

72.68 78.44 81.14 85.64

14.3

Optionale Parameter

Funktionen können auch optionale Parameter haben. Man nennt sie auch Default-Parameter. Dies sind Parameter, die beim Aufruf nicht angegeben werden müssen. In diesem Fall werden dann Default-Werte für diese Parameter eingesetzt. Wir zeigen dies an einem Beispiel. Das folgende kleine Skript, das nicht sehr nützlich ist, begrüßt eine Person mit Namen. Falls beim Aufruf allerdings kein Name übergeben wird, druckt sie nur „Hello everybody!”: def Hello(name="everybody"): """ Greets a person """ print("Hello " + name + "!") Hello("Peter") Hello()

Die Ausgabe sieht wie folgt aus: Hello Peter! Hello everybody!

105

106

14 Funktionen

14.4

Docstring

Die erste Anweisung eines Funktionsrumpfs ist normalerweise eine Zeichenkette, die als Funktionsname.__doc__ abfragbar ist. Diese Anweisung wird Docstring genannt. Beispiel: def Hello(name="everybody"): """ Greets a person """ print("Hello " + name + "!") print("The docstring of the function Hello: " + Hello.__doc__)

Die Ausgabe: The docstring of the function Hello:

14.5

Greets a person

Schlüsselwortparameter

Dabei handelt es sich um eine alternative Möglichkeit, eine Funktion aufzurufen. Die Funktionsdefinition selbst ändert sich nicht. Ein Beispiel: def sumsub(a, b, c=0, d=0): return a - b + c - d print(sumsub(12,4)) print(sumsub(42,15,d=10))

Als Schlüsselwortparameter dürfen nur solche verwendet werden, die nicht bereits als Positionsargumente verwendet wurden. Wir können den Vorteil im Beispiel sehen. Wenn wir keine Schlüsselwortparameter hätten, müssten wir im zweiten Funktionsaufruf alle Argumente angeben, obwohl c nur den Default-Wert haben muss: print(sumsub(42,15,0,10))

14.6

Rückgabewerte

In unseren früheren Beispielen haben wir return-Anweisungen in der Funktion sumsub, aber nicht in Hello verwendet. Man sieht also, dass es nicht zwingend notwendig ist, eine return-Anweisung in einer Funktion zu haben. Aber was wird zurückgeliefert, wenn wir keine explizite Rückgabeanweisung haben? Wir wollen es uns an einem Beispiel anschauen:

14.7 Mehrere Rückgabewerte

def no_return(x,y): c = x + y res = no_return(4,5) print(res)

Wenn wir das obige kleine Skript starten, wird None gedruckt, d.h. der spezielle Wert None wird von der Funktion no_return zurückgeliefert. Das zeigt, dass eine Funktion, die ohne return-Anweisung endet, None zurückliefert. Dieser Wert wird aber auch zurückgeliefert, wenn eine return-Anweisung ohne nachfolgenden Ausdruck eine Funktion beendet, wie wir im folgenden Beispiel sehen: def empty_return(x,y): c = x + y return res = empty_return(4,5) print(res)

Ansonsten wird der Wert des Ausdrucks hinter dem return zurückgeliefert. Im nächsten Beispiel wird 9 zurückgeliefert: def return_sum(x,y): c = x + y return c res = return_sum(4,5) print(res)

14.7

Mehrere Rückgabewerte

Eine Funktion kann genau einen Wert zurückliefern oder wir sollten besser sagen: genau ein Objekt. Ein Objekt kann beispielsweise ein numerischer Wert wie eine ganze Zahl (Integer) oder eine Nachkommazahl (float) sein, aber auch ein komplexes Objekt wie etwa eine Liste oder ein Dictionary. Wenn wir beispielsweise drei Integers zurückliefern wollen, haben wir die Möglichkeit, diese in ein Tupel oder eine Liste zu packen und dieses Listenoder Tupel-Objekt als return-Wert zu übergeben. Das bedeutet, dass wir indirekt auf eine sehr einfache Art und Weise beliebig viele Werte zurückliefern können. 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): """ returns the largest fibonacci number smaller than x and the lowest

107

108

14 Funktionen

fibonacci number higher than x""" if x < 0: return -1 (old,new, lub) = (0,1,0) while True: if new < x: lub = new (old,new) = (new,old+new) else: return (lub, new) while True: x = int(input("Your number: ")) if x >> x = 9 >>> id(x)

111

112

14 Funktionen

9251936 >>> ref_demo(x) x= 9 id= 9251936 x= 42 id= 9252992 >>> id(x) 9251936 >>>

14.11

Nebeneffekte

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. 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 Nebeneffekte auftreten, mit denen man nicht gerechnet hat, und dadurch Fehler verursacht werden. Dies kann auch in Python passieren, z.B . dann, wenn Listen oder Dictionaries als Parameter übergeben werden. Im folgenden Beispiel wird die Liste fib, die als aktueller Parameter an die Funktion s() übergeben wird, mit der Liste [47,11] verkettet. >>> 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]

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 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: >>> def s(liste): ... liste += [47,11] ... print(liste) ... >>> fib = [0,1,1,2,3,5,8]

14.12 Kommandozeilenparameter

>>> [0, >>> [0,

s(fib[:]) 1, 1, 2, 3, 5, 8, 47, 11] fib 1, 1, 2, 3, 5, 8]

14.12

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 eachArg in sys.argv: print(eachArg)

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 kurs fuer anfaenger

14.13

Variable Anzahl von Parametern

Wir führen nun Funktionen ein, die mit einer beliebigen Anzahl von Argumenten aufgerufen werden können. Diejenigen mit Programmiererfahrungen in C und C++ kennen das Konzept als varargs. Es folgen einige Definitionen, die für das Verständnis der weiteren Beispiele dieses Kapitels nicht wichtig sind: Der Begriff Stelligkeit oder Arität bezeichnet in der Informatik die Para-

113

114

14 Funktionen

meteranzahl 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. Ein Sternchen „*” wird in Python benutzt, um eine variable Anzahl von Parametern zu kennzeichnen. Dazu wird das Sternchen unmittelbar vor einen Variablennamen gestellt. >>> def varpafu(*x): print(x) ... >>> varpafu() () >>> varpafu(34,"Do you like Python?", "Of course") (34, 'Do you like Python?', 'Of course') >>>

Aus dem vorigen Beispiel lernen wir, dass die Argumente, die bei einem Funktionsaufruf von varpafu übergeben werden, in einem Tupel gesammelt werden. Auf dieses Tupel kann als „normale” Variable im Rumpf (body) der Funktion zugegriffen werden. Ruft man die Funktion ohne jegliche Argumente auf, so ist x ein leeres Tupel. Manchmal ist es notwendig, dass man eine feste Anzahl von Positionsparametern benötigt, 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 eine Funktion, die das arithmetische Mittel aus einer variablen Anzahl von Werten berechnet. Lösung: def arithmetic_mean(x, *l): """ The function calculates the arithmetic mean of a non-empty arbitrary number of numbers """ sum = x for i in l: sum += i return sum / (1.0 + len(l))

14.14 * in Funktionsaufrufen

Man mag sich fragen, warum wir sowohl einen Positionsparameter „x” und einen Parameter für eine variable Anzahl von Werten „*l” in unserer Funktionsdefinition benutzt haben. Die Idee besteht darin, dass wir erzwingen wollten, dass unsere Funktion immer mit einer nichtleeren Anzahl von Argumenten aufgerufen wird. Dies ist notwendig, um eine Division durch 0 zu vermeiden, was einen Fehler verursachen würde. In der folgenden interaktiven Python-Sitzung lernen wir, wie wir diese Funktion benutzen können. Dazu nehmen wir an, dass die Funktion arithmetic_mean in einer Datei mit dem Namen statistics.py gespeichert worden ist. >>> from statistics import arithmetic_mean >>> arithmetic_mean(4,7,9) 6.666666666666667 >>> arithmetic_mean(4,7,9,45,-3.7,99) 26.71666666666667

Das funktioniert gut, aber die Sache hat einen Haken. Was ist, wenn jemand die Funktion mit einer Liste statt mit einer variablen Zahl von Zahlen aufrufen will? Im Folgenden sehen wir, dass dann ein Fehler verursacht wird: >>> l = [4,7,9,45,-3.7,99] >>> arithmetic_mean(l) Traceback (most recent call last): File "", line 1, in File "statistics.py", line 8, in arithmetic_mean return sum / (1.0 + len(l)) TypeError: unsupported operand type(s) for /: 'list' and 'float'

Die Lösung besteht in der Benutzung eines weiteren Sternchens: >>> arithmetic_mean(*l) 26.71666666666667 >>>

14.14

* in Funktionsaufrufen

Ein Stern kann auch in einem Funktionsaufruf erscheinen, wie wir in der vorigen Übung 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)

115

116

14 Funktionen

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) >>>

Zusätzlich, dass dieser Aufruf weniger komfortabel ist, ist die vorige Aufrufsart im allgemeinen Fall nicht anwendbar, d.h. wenn Listen „unbekannter” Längen verwendet werden sollen. „Unbekannt” bedeutet, dass die Länge erst während der Laufzeit bekannt ist und nicht, während man das Skript schreibt.

14.15

Beliebige Schlüsselwortparameter

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="Germnan",en="English",fr="French") {'fr': 'French', 'de': 'Germnan', 'en': 'English'} >>>

14.16

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 in Kombination mit *: >>> t = (47,11) >>> d = {'x':'extract','y':'yes'} >>> f(*t, **d) (47, 11, 'extract', 'yes') >>>

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 mit Hilfe 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

118

15 Rekursive Funktionen

15.2

Definition der Rekursion

Die Rekursion ist eine Programmiertechnik oder ein Programmierkonzept, indem 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 faculty(n): if n == 1: return 1 else: return n * faculty(n-1)

Wenn wir nachvollziehen wollen, wie die Funktion funktioniert, können wir zwei print()Funktionen ergänzen: def faculty(n):

15.4 Die Tücken der Rekursion

print("faculty has been called with n = " + str(n)) if n == 1: return 1 else: res = n * faculty(n-1) print("intermediate result for ", n, " * faculty(" ,n-1, "): ", res) return res print(faculty(5))

Obiges Python-Skript liefert folgende Ausgaben: faculty has been called faculty has been called faculty has been called faculty has been called faculty has been called intermediate result for intermediate result for intermediate result for intermediate result for 120

with n = 5 with n = 4 with n = 3 with n = 2 with n = 1 2 * faculty( 3 * faculty( 4 * faculty( 5 * faculty(

1 2 3 4

): ): ): ):

2 6 24 120

Die Fakultätsfunktion lässt sich natürlich auch iterativ schreiben: def iterative_faculty(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 Fibonacci-Zahlen auch als Fibonacci-Folge bezeichnet.

BILD 15.2 Fibonacci-Rechtecke und goldener Schnitt

119

120

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. 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 wieder: 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: 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 fib(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, n=11, n=12,

fib: fib: 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, 0.000198, 0.000287,

fibi: fibi: 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, 0.000007, 0.000007,

percent: percent: 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 27.70 41.52

121

122

15 Rekursive Funktionen

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:

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():

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.

15.5 Fibonacci-Folge in Python

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 haben einen besonderen Vorteil 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, n=12, n=13, n=14, n=15, n=16, n=17, n=18, n=19, n=20, n=21,

fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: 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, 0.000004, 0.000004, 0.000004, 0.000004, 0.000003, 0.000004, 0.000004, 0.000004, 0.000003, 0.000004,

fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: 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, 0.000007, 0.000007, 0.000008, 0.000008, 0.000008, 0.000009, 0.000009, 0.000009, 0.000010, 0.000009,

percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: 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 0.59 0.57 0.52 0.50 0.39 0.45 0.45 0.45 0.29 0.45

123

124

15 Rekursive Funktionen

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:

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,

15.6

fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi:

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:

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

Aufgaben

1. Aufgabe: Schreibe 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 393

2. Aufgabe: Schreibe eine rekursive Python-Funktion, welche die Summe der ersten n ganzen Zahlen zurückliefert. (Hinweis: Diese Funktion sieht ähnlich wie die Fakultätsfunktion aus!) Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 393

3. Aufgabe: Schreiben Sie eine Funktion, die das Pascalsche Dreieck implementiert: 1 1 1 1 1 1

3

4 5

1 2

1 3

1 6 4 1 10 10 5 1

Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 393

15.6 Aufgaben

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 3 *1 1 4 4 1 *6 1 10 10 5 1 *5 6 15 20 15 6 1 *1 1

2

3

Schreibe ein rekursives Programm, d.h. rekursive Funktion, die die Fibonacci-Zahlen aus einem Pascalschen Dreieck heraus berechnet. Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 394

5. Aufgabe: Schreibe 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. 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) 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 394

125

126

15 Rekursive Funktionen

6. Aufgabe: Schreibe 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, also wenn n kein Element der Folge ist, -1 zurückliefert. Für eine Fibonacci-Zahl n gilt also: fib(find_index(n)) == n Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 395

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. Benutze 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 396

16 16.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 16.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 was immer man mit dieser Variable innerhalb der Funktion anstellt, 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.

128

16 Globale und lokale Variablen

16.2

Globale und lokale Variablen in Funktionen

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 Variablen s benutzt. Dieser Wert kann natürlich nicht verändert werden, wie wir weiter unten in diesem Kapitel 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!"

16.2 Globale und lokale Variablen in Funktionen

print(s) s = "I love Paris!" f() print(s)

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 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. Deswegen betrachtete Python s als eine lokale Variable innerhalb des Rumpfs. Da nun 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)

129

130

16 Globale und lokale Variablen

Wenn man dieses Skript startet, erhält man folgende Ausgabe mit Fehlermeldung: $ python3 global_lokal5.py I am globally not known Traceback (most recent call last): File "global_lokal5.py", line 6, in 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

17 17.1

Alles über Strings . . .

... fast alles

In den vorigen Kapitel 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 an die Suchmaschinen alleine denkt, sieht man, dass heutzutage ein Großteil der Probleme auf Texten also Strings basieren.

BILD 17.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', 'endswith', 'expandtabs ', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', '

132

17 Alles über Strings . . .

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

17.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)

17.2.1

BILD 17.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

17.2 Aufspalten von Zeichenketten

>>> 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, Tabs (’\n’) oder anderen sogenannten Whitespaces, also nicht druckbaren Zeichen bestehen, zu einer Trennstelle zusammengefasst: >>> mammon = "The god \t \t chief temple is in the >>> mammon.split() ['The', 'god', 'of', 'the', chief', 'temple', 'is', York.'] >>>

of the world's \t leading religion. \n\r The holy city of New York." "world's", 'leading', 'religion.', 'The', ' 'in', 'the', 'holy', 'city', 'of', 'New', '

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.

133

134

17 Alles über Strings . . .

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. Die Wert von „maxsplit” bestimmt, wie oft ein String aufgespaltet 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'] >>>

17.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 "", line 1, in TypeError: split() takes no keyword arguments >>>

17.2 Aufspalten von Zeichenketten

Den 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 "", 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 zu der korrekten Lösung. Will man einen Parameter nicht angeben und möchte haben, dass der Default-Wert genommen wird, 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'] >>>

135

136

17 Alles über Strings . . .

17.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. >>> s.split(",") ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white'] >>> s.rsplit(",") ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white'] >>>

Ganz anders sieht es aus, wenn wir mit „maxsplit” arbeiten. Zur Demonstration des Unterschieds wiederholen wir das vorletzte Beispiel mit rsplit: >>> s.rsplit(",") ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white'] >>> >>> s = "red,green,blue,yellow,pink,brown,black,white" >>> for i in range(1,8): ... x = s.rsplit(",",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'] >>>

Es bringt einen großen Zeitvorteil, wenn man konsequent „maxsize” benutzt, 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([0]) return res def splitter_maxsplit(): fh = open("ulysses.txt") res = []

17.2 Aufspalten von Zeichenketten

for line in fh: x = line.split(None, 1) if len(x): res.append([0]) return res

for i in range(5): t1 = time.time() splitter_total() t1 = time.time() - t1 t2 = time.time() splitter_maxsplit() t2 = time.time() - t2 print(t1, t2, t1/t2)

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 bernd@saturn:~/bodenseo/python/beispiele$

17.2.4

Folge von Trennzeichen

Wir hatten gezeigt, dass mehrere Leerzeichen, Tabs und so weiter 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.

137

138

17 Alles über Strings . . .

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. So wie beispielsweise in den folgenden Adresszeilen. In der zweiten Zeile fehlt der Vorname, in der vierten Zeile fehlt 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; Anna;List;München;089/3434544 Anna;List;München;089/3434544 Franziska;Huber;Rosenheim;08031/787878 Sarah;Rabe;Konstanz;07531/343454

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$

17.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. Zeilenenden können auch von unterschied5

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 überlicherweise .csv.

17.3 Zusammenfügen von Stringlisten mit join

lichen Betriebssystemen in einem String gemischt sein, und splitlines erkennt sie als Zeilenende. Im folgenden Beispiel wird „\r\n” korrekterweise als Windows-Zzeilenende erkannt, und „\n\r” hinter line2 werden als Unix- und als Max-Zeilenende 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']

17.2.6

partition

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!')

17.3

join

join ist eine String-Methode. Allgemeine Syntax: 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 den die Methode „join” angewendet wird. Beispiele: >>> x = ["Python","Perl","Java"] >>> "-".join(x) 'Python-Perl-Java' >>> x = "48772" >>> ".".join(x) '4.8.7.7.2'

139

140

17 Alles über Strings . . .

17.4

Suchen von Teilstrings

Für die Suche von Teilstrings gibt es verschiedene Möglichkeiten in Python:

17.4.1

„in” oder „not in”

„in” ist ideal, um die reine Existenz eines Substrings zu testen, wenn man nicht daran interessiert 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!" ' >>> "corner" in rhyme True >>> "corner" not in rhyme False >>>

17.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!" ' >>> pos = rhyme.find("orner") >>> pos 13 >>> pos = rhyme.find("Jill") >>> pos -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: >>> s = "annxbny" >>> s.find("x") 3 >>> s.find("x",2) 3

17.4 Suchen von Teilstrings

>>> s.find("x",2,3) -1 >>>

17.4.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!" ' >>> pos = rhyme.rfind("orner") >>> pos 29 >>>

Gefunden wird das „orner” in dem Wort „corner”. Der Name „Jill” kommt nicht in unserem String vor, deshalb liefert „find” ein -1 zurück.

17.4.4

s.index(substring[, start[, end]])

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!" ' >>> pos = rhyme.index("orner") >>> pos 13 >>> rhyme.index("orner") == rhyme.find("orner") True >>> rhyme.index("Jill") Traceback (most recent call last): File "", line 1, in ValueError: substring not found >>>

17.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!" '

141

142

17 Alles über Strings . . .

>>> rhyme.rindex("orner") 29 >>> rhyme.rindex("Jill") Traceback (most recent call last): File "", line 1, in ValueError: substring not found >>>

17.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 >>>

17.5

Suchen und Ersetzen

Eine häufige Aufgabe, wenn man mit Texten arbeitet, besteht 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, wieviele Vorkommen von old durch new ersetzt werden sollen. Beispiel: >>> ch = "Drei Chinesen mit dem Kontrabass" >>> ch.replace("e","a") 'Drai Chinasan mit dam Kontrabass'

17.6 Nur noch Kleinbuchstaben oder Großbuchstaben

>>> 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' >>>

17.6

Kleinbuchstaben und Großbuchstaben

Man kann einen String mit Hilfe der Stringmethode „upper” komplett in Großbuchstaben wandeln. Ebenso kann man einen String mit Hilfe 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' >>>

17.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. „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' >>> ch.capitalize() 'Drei chinesen mit dem kontrabass' >>>

143

144

17 Alles über Strings . . .

17.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 String stehen. Um diese Zeichen loszuwerden, gibt es die Stringmethoden „strip”, „lstrip” und „rstrip”. Allgemeine Syntax: ■





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

Beispiele: >>> 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' >>>

17.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. 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)

17.10 String-Tests

' Hallo ' >>> s.ljust(15) 'Hallo ' >>> s.rjust(15) ' Hallo' >>> >>> z = "123.99" >>> z.zfill(10) '0000123.99' >>>

17.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 ("mp3", "Hallo", "hello", "343", "767.43"): ... print("%6s : %s" % (word, word.isalpha())) ... mp3 : False Hallo : True hello : True 343 : False 767.43 : False >>>



s.isdigit() True, wenn alle Zeichen in s Ziffern sind. >>> for word in ("mp3", "Hallo", "hello", "343", "767.43"): ... print("%6s : %s" % (word, word.isdigit())) ... mp3 : False Hallo : False hello : False

145

146

17 Alles über Strings . . .

343 : True 767.43 : False >>> ■

s.islower() True, wenn alle Buchstaben in s Kleinbuchstaben sind. >>> for word in ("mp3", "Hallo", "hello", "343", "767.43"): ... print("%6s : %s" % (word, word.islower())) ... mp3 : True Hallo : False hello : True 343 : False 767.43 : False >>>



s.isupper() True, wenn alle Buchstaben in s Großbuchstaben sind. >>> for word in ("mp3", "Hallo", "hello", "343", "767.43", "HALLO", " MP3"): ... print("%6s : %s" % (word, word.isupper())) ... mp3 : False Hallo : False hello : False 343 : False 767.43 : False HALLO : True MP3 : True >>>



s.isspace() True, wenn alle Zeichen in s Whitespaces sind. >>> for word in ("mp3", "Hallo", "hello", "343", "767.43", " \t\n", " "): ... print("%6s : %s" % (word, word.isspace())) ... mp3 : False Hallo : False hello : False 343 : False 767.43 : False : True : True >>>



s.istitle() True, wenn alle Wörter in s groß geschrieben sind.

17.11 Aufgaben

>>> for word in ("mp3", "Hallo", "hello", "343", "767.43", "HALLO", " MP3"): ... print("%6s : %s" % (word, word.istitle())) ... mp3 : False Hallo : True hello : False 343 : False 767.43 : False HALLO : False MP3 : False >>>

17.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 17 (Alles über Strings . . . ), Seite 397

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

147

148

17 Alles über Strings . . .

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: [('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 17 (Alles über Strings . . . ), Seite 397

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 17 (Alles über Strings . . . ), Seite 398

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 17 (Alles über Strings . . . ), Seite 398

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 17 (Alles über Strings . . . ), Seite 399

18

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 != 9: z = x / y

Eleganter geht es mit den in Python vorhandenen Ausnahmebehandlungen.

BILD 18.1 Ausnahmebehandlung

Sie sind Thread-sicher und können leicht bis in die höchste Programmebene weitergegeben oder an einer beliebigen anderen Ebene der Funktionsaufruffolge behandelt werden. Der Python-Ansatz legt den Einsatz von Ausnahmen nahe, wann immer eine Fehlerbedingung entstehen könnte. Die Ausnahmebehandlung (Exception Handling) ist ein Mittel, mit Fehlern umzugehen. Damit ist es möglich, Syntaxfehler zur Laufzeit abzufangen und zu behandeln. Das folgende Skript implementiert unter Verwendung einer Ausnahmebehandlung eine robuste Eingabeaufforderung, die solange wartet, bis eine Zahl eingegeben wurde: while True: try: zahl = raw_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.

150

18 Ausnahmebehandlung



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 1.9 0.5263157894736842 0 hat kein inverses Element 4.5 0.2222222222222222 >>>

18.1

Die optionale else-Klausel

Die try ... except-Anweisung hat eine optionale else-Klausel, die 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

Starten wir obiges Programm, erhalten wir folgende Ausgaben: bernd@saturn:~/bodenseo/python/beispiele$ python3 try_else.py Dateiname: ulisses.txt ulisses.txt lässt sich nicht öffnen Dateiname: ulysses.txt ulysses.txt hat 33025 Zeilen bernd@saturn:~/bodenseo/python/beispiele$

18.2 Fehlerinformationen über sys.exc_info

18.2

Fehlerinformationen über sys.exc_info

Die genauen Fehlerinformationen kann man sich mit der Methode exc_info des sysModuls anzeigen lassen: 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: Value: invalid literal for int() with base 10: 'Hallo' traceback: Traceback (most recent call last): File "exception_sys_exc_info.py", line 4, in i = int("Hallo") ValueError: invalid literal for int() with base 10: 'Hallo' bernd@saturn:~/bodenseo/python/beispiele$

18.3

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

151

152

18 Ausnahmebehandlung

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

18.4

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: 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

18.5

Exceptions generieren

Man kann auch selbst Exceptions generieren: >>> raise SyntaxError("Sorry, mein Fehler!") Traceback (most recent call last): File "", line 1, in SyntaxError: Sorry, mein Fehler! >>>

18.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.

18.6 Finalisierungsaktion

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 inverse = 1.0 / x ZeroDivisionError: float division by zero bernd@saturn:~/bodenseo/python/beispiele$

153

19 19.1

Objektorientierte Programmierung

Einführung

Auch wenn Python ohne Wenn und Aber eine objektorientierte Programmiersprache ist, haben wir es bisher mit voller Absicht in den vorhergehenden Kapiteln unseres Tutorials vermieden, auf die objektorientierte Programmierung (OOP) einzugehen. Wir haben OOP ausgelassen, weil wir davon überzeugt sind, dass es einfacher ist und mehr Spaß macht, wenn man Python zu lernen beginnt, ohne dass man über die Details der OOP Bescheid wissen muss.

BILD 19.1 Früchte

Aber auch wenn wir die objektorientierte Programmierung vermieden haben, so war sie dennoch in unseren Übungen und Beispielen präsent. Wir haben Objekte und Methoden von Klassen benutzt, ohne eigentlich von ihrer Existenz zu wissen. In diesem Kapitel geben wir nun eine ordentliche 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 mächtige und effiziente Programme ohne 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. Das Grundkonzept der objektorientierten Programmierung besteht darin, Daten und deren Funktionen (Methoden), – d.h. Funktionen, die auf diese Daten angewendet werden können –, in einem Objekt zusammenzufassen und nach außen zu kapseln, sodass Methoden fremder Objekte diese Daten nicht manipulieren können. Objekte werden über Klassen definiert. Eine Klasse ist eine formale Beschreibung, wie ein Objekt beschaffen ist, d.h. welche Attribute und welche Methoden sie hat. Eine Klasse darf nicht mit einem Objekt verwechselt werden. Statt Objekt spricht man auch von einer Instanz einer Klasse.

156

19 Objektorientierte Programmierung

19.2

Die Kuchenklasse

Bei Einführungen in die objektorientierte Programmierung wird häufig und gerne auf Beispiele aus dem Alltag zurückgegriffen. Dabei handelt es sich meistens um Beispiele, die zwar helfen, objektorientierte Konzepte zu verdeutlichen, aber diese Beispiele lassen sich dann nicht in Programmiercode wandeln. So auch in diesem Beispiel einer „Kuchenklasse”. Betrachten wir das Rezept eines Erdbeerku- BILD 19.2 Kuchenklasse chens. Ein solches Rezept kann man prinzipiell als eine Klasse ansehen. Das heißt, das Rezept bestimmt, wie eine Instanz der Klasse beschaffen sein muss. Backt jemand einen Kuchen nach diesem Objekt, dann schafft er eine Instanz oder ein Objekt dieser Klasse. Es gibt dann verschiedene Methoden, diesen Kuchen zu verarbeiten oder zu verändern. Ein nette Methode stellt übrigens in diesem Beispiel „aufessen” dar. Ein Erdbeerkuchen gehört in eine übergeordnete Klasse „Kuchen”, die ihre Eigenschaften, z.B. dass ein Kuchen sich als Nachtisch nutzen lässt, an Unterklassen wie Erdbeerkuchen, Rührkuchen, Torten und so weiter vererbt.

19.3

Objekte

Der zentrale Begriff in der objektorientierten Programmierung ist der des Objekts. Ein Objekt bezeichnet in der OOP die Abbildung eines realen Gegenstandes mit seinen Eigenschaften und Verhaltensweisen (Methoden) in ein Programm. Anders ausgedrückt: Ein Objekt kann immer durch zwei Dinge beschrieben werden: ■

was es tun kann oder was wir in einem Programm mit ihm tun können,



was wir über es wissen.

Objekte sind Instanzen oder Exemplare einer Klasse. Die Begriffe Objekt und Instanz werden meist synonym gebraucht und bezeichnen den gleichen „Gegenstand”. Objekte oder Instanzen werden mittels Konstruktoren erzeugt. Konstruktoren sind spezielle Methoden zur Erzeugung von Instanzen einer Klasse. Zum Entfernen oder Löschen von Instanzen gibt es die Destruktor-Methode.

19.4

Klasse

Eine Klasse ist ein abstrakter Oberbegriff für die Beschreibung der gemeinsamen Struktur und des gemeinsamen Verhaltens von realen Objekten (Klassifizierung). Reale Objekte werden auf die für die Software wichtigen Merkmale abstrahiert. Die Klasse dient als Bauplan zur Abbildung von realen Objekten in Software-Objekte, die sogenannten Instanzen.

19.5 Kapselung von Daten

BILD 19.3 Kontenklasse

Die Klasse fasst hierfür notwendige Eigenschaften (Attribute) und zur Manipulation der Eigenschaften notwendige Methoden zusammen. Klassen stehen häufig in Beziehung zueinander. Man hat beispielsweise eine Oberklasse (Kuchen), und aus dieser leitet sich eine andere Klasse ab (Erdbeerkuchen). Diese abgeleitete Klasse erbt bestimmte Eigenschaften und Methoden der Oberklasse.

19.5

Kapselung von Daten

Ein weiterer wesentlicher Vorteil der OOP besteht in der Kapselung von Daten. Der Zugriff auf Eigenschaften darf nur über Zugriffsmethoden erfolgen. Diese Methoden können Plausibilitätstests enthalten, und sie (oder „nur” sie) besitzen „Informationen” über die eigentliche Implementierung. So kann z.B. eine Methode zum Setzen des Geburtsdatums prüfen, ob das Datum korrekt ist und sich innerhalb eines bestimmten Rahmens bewegt, z.B. Girokonto für Kinder unter 14 nicht möglich oder Kunden über 100 Jahre unwahrscheinlich.

BILD 19.4 Datenkapselung

157

158

19 Objektorientierte Programmierung

19.6

Vererbung

In unserem Beispiel erkennt man leicht, dass eine Klasse „Konto” einer realen Bank nicht genügen kann. Es gibt verschiedene Arten von Konten: Girokonto, Sparkonto, usw. Aber allen verschiedenen Konten sind bestimmte Eigenschaften und Methoden gemeinsam. Beispielsweise wird jedes Konto eine Kontonummer, einen Kontoinhaber und einen Kontostand aufweisen. Gemeinsame Methoden: Einzahlen und Auszahlen Es gibt also so etwas wie ein Grundkonto, von dem alle anderen Konten „erben”. Die Vererbung dient also dazu, unter Zugrundelegung von existierenden Klassen neue zu schaffen. Eine neue Klasse kann dabei sowohl als eine Erweiterung als auch als eine Einschränkung der ursprünglichen Klasse entstehen.

BILD 19.5 Vererbung

19.7

Klassen in Python

Eine Klasse besteht aus zwei Teilen: dem Kopf und einem Körper. Der Kopf besteht meist nur aus einer Zeile: das Schlüsselwort class, gefolgt von einem Leerzeichen, einem beliebigen Namen, einer Liste von Oberklassen in Klammern und als letztes Zeichen ein Doppelpunkt. Hat man keine spezielle Oberklasse, wie es in unseren anfänglichen Beispielen immer der Fall sein wird, dann gibt man als Oberklasse die Standard-Basisklasse object an. Der Körper einer Klasse besteht aus einer eingerückten Folge von Anweisungen, die man auch durch eine pass-Anweisung ersetzen kann. Jetzt wissen wir genug, um eine einfache Python-Klasse definieren zu können: class Konto(object): pass

Anstelle von pass stünde bei einer „richtigen” Klasse die Definition der Attribute und Methoden dieser Klasse. Aber auch, wenn diese fehlen, können wir für unsere obige „sinnlose” Klasse bereits Objekte definieren:

19.8 Methoden

>>> class Konto(object): ... pass ... >>> x = Konto() >>> y = Konto() >>> z = x >>> print(x)

Wie wir sehen, können wie sogar eine Klasse ausdrucken. Die Ausgabe sagt uns, dass x eine Instanz der Klasse Konto im Namensraum __main__ ist. Die Zahl nach dem at gibt den Speicherort dieser Klasse an.

19.8

Methoden

Eine Methode unterscheidet sich äußerlich nur in zwei Aspekten von einer Funktion: ■



Sie ist eine Funktion, die innerhalb einer class-Definition definiert ist. Der erste Parameter einer Methode ist immer eine Referenz self auf die Instanz, von der sie aufgerufen wird.

Der Parameter self erscheint nur bei der Definition einer Methode. Beim Aufruf wird er nicht angegeben. Beispiel mit Methode: class Konto(object): def ueberweisen(self, ziel, betrag): pass def einzahlen(self, betrag): pass def auszahlen(self, betrag): pass def kontostand(self): pass

Obige Klasse können wir bereits benutzen, auch wenn wir natürlich kaum etwas Sinnvolles damit anstellen können. Wir speichern sie unter dem Namen konto.py und führen folgende Anweisungen in der interaktiven Python-Shell aus: $ 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 konto import Konto >>> x = Konto() >>> x.einzahlen(12300000) >>>

159

160

19 Objektorientierte Programmierung

19.9

Konstruktor

Die Methode __init__ wird aufgerufen, sobald ein Objekt einer Klasse in Python instanziiert wird. Meistens wird __init__ als der Konstruktor einer Klasse bezeichnet. Normalerweise, d.h. in anderen objektorientierten Sprachen, versteht man aber unter einem Konstruktor eine spezielle Methode, mit der ein Objekt konstruiert wird. In Python ist jedoch ein Objekt bereits existent bzw. konstruiert, wenn die Methode __init__ aufgerufen wird. Die Methode __init__ wird dazu benutzt, wie der Name vermuten lässt, ein Objekt der Klasse zu initialisieren. Die __init__-Methode wird im Prinzip wie jede andere Methode definiert: def __init__(self, inhaber, kontonummer, kontostand, kontokorrent=0): self.Inhaber = inhaber self.Kontonummer = kontonummer self.Kontostand = kontostand self.Kontokorrent = kontokorrent

Auch diese Klasse wollen wir in der interaktiven Shell ausprobieren: $ 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 konto import Konto >>> x = Konto() Traceback (most recent call last): File "", line 1, in TypeError: __init__() takes at least 4 arguments (1 given) >>> x = Konto("Bill Boe", 234322, 1000)

Wir erkennen, dass wir bei der Definition eines Objektes nun mindestens drei Parameter angeben müssen. Versucht man ein Objekt ohne Angabe von Argumenten zu generieren, erhält man eine Fehlermeldung, dass das Objekt mindestens vier Argumente („takes at least 4 arguments”) erwarte, aber nur einen („1 given”) erhalten habe. Der scheinbare Widerspruch, dass wir kein Argument übergeben haben, und der Meldung „1 given” kommt daher, dass das Argument „self” automatisch generiert wird.

19.10

Destruktor

Für eine Klasse kann man auch einen Destruktor __del__ definieren. Wenn man eine Instanz einer Klasse mit del löscht, wird der Destruktor aufgerufen. Allerdings nur, falls es keine weitere Referenz auf diese Instanz gibt. Destruktoren werden selten benutzt, da man

19.11 Lauffähige Version der Kontoklasse

sich normalerweise nicht um das Aufräumen im Speicher kümmern muss. Im Folgenden sehen wir ein Beispiel mit Konstruktor und Destruktor: class Greeting: def __init__(self, name): self.name = name def __del__(self): print("Destruktor gestartet") def SayHello(self): print("Guten Tag", self.name)

Diese Klasse wird nun interaktiv benutzt: $ 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 greeting import Greeting >>> x1 = Greeting("Bernd") >>> x2 = x1 >>> del x1 >>> del x2 Destruktor gestartet

19.11

Lauffähige Version der Kontoklasse

class Konto(object): def __init__(self, inhaber, kontonummer, kontostand, kontokorrent=0): self.Inhaber = inhaber self.Kontonummer = kontonummer self.Kontostand = kontostand self.Kontokorrent = kontokorrent def ueberweisen(self, ziel, betrag): if(self.Kontostand - betrag < -self.Kontokorrent): # Deckung nicht genuegend return False else: self.Kontostand -= betrag ziel.Kontostand += betrag return True def einzahlen(self, betrag): self.Kontostand += betrag

161

162

19 Objektorientierte Programmierung

def auszahlen(self, betrag): self.Kontostand -= betrag def kontostand(self): return self.Kontostand

Speichert man obigen Code konto.py ab, kann man mit der Klasse in einer python-Shell wie folgt arbeiten: $ 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 konto import Konto >>> konto1 = Konto("Jens",70711,1957.17) >>> konto2 = Konto("Maria",73813,4142.18) >>> konto1.kontostand() 1957.17 >>> konto1.einzahlen(42.83) >>> konto1.kontostand() 2000.0 >>> konto2.kontostand() 4142.18 >>> konto1.ueberweisen(konto2, 857.82) True >>> konto1.kontostand() 1142.1799999999998 >>> konto2.kontostand()kuchenkl 5000.0 >>>

19.12

Public-Attribute

Leider hat unsere Klasse Konto() noch einen kleinen Schönheitsfehler. Man kann von außen direkt auf die Attribute zugreifen, was dem Prinzip der Datenkapselung widerspricht: >>> konto1.Kontostand 1142.1799999999998 >>> konto2.Kontostand 5000.0 >>> konto2.Kontonummer 73813 >>> konto2.Kontostand = 1000000 >>> konto2.Kontostand 1000000

Der einfache lesende Zugriff stellt natürlich im obigen Beispiel kein Problem dar, aber wenn jemand direkt den Kontostand verändert, hat das gravierende Folgen für unsere

19.13 Datenkapselung und die Benutzung von Public- Protected- und Private-Attributen

Bank. Selbst wenn der Kontostand korrekterweise wirklich eine Million nach einer Einzahlung betragen sollte, so wären bestimmte andere Größen der Buchführung, z.B. der Gesamtbestand aller Konten, nicht mehr synchron. Normalerweise müsste ja bei jeder Einzahlung diese Größe auch angepasst werden, was wir in unserem Beispiel nicht getan haben. Die Methode Einzahlen und Überweisen müsste auch beispielsweise Maßnahmen bzgl. des Geldwäschegesetzes ergreifen, z.B. Warnmeldungen ausgeben, wenn eine Einzahlung über einen bestimmten Betrag geht. All dies wäre außer Kraft gesetzt, wenn ein direkter Zugriff auf die Attribute möglich ist.

19.13

Datenkapselung

Normalerweise sind alle Attribute einer Klasseninstanz öffentlich, d.h. von außen zugänglich. Python bietet einen Mechanismus, um dies zu verhindern. Die Steuerung erfolgt nicht über irgendwelche speziellen Schlüsselwörter, sondern über die Namen, d.h. ein einfacher, dem eigentlichen Namen vorangestellten Unterstrich für protected-Attribute und ein zweifacher vorgestellter Unterstrich für private-Attribute, wie man der folgenden Tabelle entnehmen kann: 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.

__name

Private

Sind von außen nicht sichtbar und nicht benutzbar.

Unsere Konto-Beispielklasse sieht mit „private”-Attributen wie folgt aus: class Konto(object): def __init__(self, inhaber, kontonummer, kontostand, kontokorrent=0): self.__Inhaber = inhaber self.__Kontonummer = kontonummer self.__Kontostand = kontostand self.__Kontokorrent = kontokorrent def ueberweisen(self, ziel, betrag): if(self.__Kontostand - betrag < -self.__Kontokorrent): # Deckung nicht genuegend return False else: self.__Kontostand -= betrag

163

164

19 Objektorientierte Programmierung

ziel.__Kontostand += betrag return True def einzahlen(self, betrag): self.__Kontostand += betrag def auszahlen(self, betrag): self.__Kontostand -= betrag def kontostand(self): return self.__Kontostand

Wir testen nun in der interaktiven Python-Shell, ob wir wirklich nicht auf die privateAttribute zugreifen können: $ bernd@saturn:~/bodenseo/python/examples$ 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 konto import Konto >>> konto = Konto("Jens",70711,2013.00) >>> konto.__Kontostand Traceback (most recent call last): File "", line 1, in AttributeError: 'Konto' object has no attribute '__Kontostand'

Eigentlich würde man die Fehlermeldung erwarten, dass man auf das Attribut __Kontostand nicht zugreifen darf, da es private ist. Stattdessen kommt die obige Meldung, die so tut, als gäbe es kein Attribut __Kontostand in der Klasse Konto.

19.14

Statische Member

Bisher hatte jedes Objekt einer Klasse seine eigenen Attribute und Methoden, die sich von denen anderer Objekte unterschieden. Man bezeichnet dies als „nicht-statisch” oder dynamisch, da sie für jedes Objekt einer Klasse dynamisch erstellt werden. Wie kann man aber z.B. die Anzahl der verschiedenen Instanzen einer Klasse zählen? In unserer Konto()-Klasse entspräche dies der Anzahl der verschiedenen Konten. Statische Attribute werden außerhalb des Konstruktors direkt im class-Block definiert. Es ist Usus, die statischen Member direkt unterhalb der class-Anweisung zu positionieren. 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:

19.14 Statische Member

19.14.1

__del__

class C(object): counter = 0 def __init__(self): C.counter += 1 def __del__(self): C.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))

Starten wir das obige Programm, erhalten wir folgende Ausgabe: $ python3 counter.py Anzahl der Instanzen: Anzahl der Instanzen: Anzahl der Instanzen: Anzahl der Instanzen:

1 2 1 0

Allerdings gibt es hierbei einen Haken. Nehmen wir an, dass wir die Variablen nicht explizit mittels __del__ zerstören, also die letzten vier Zeilen löschen: class C(object): counter = 0 def __init__(self): C.counter += 1 def __del__(self): C.counter -= 1 if __name__ == "__main__": x = C() print("Anzahl der Instanzen: " + str(C.counter)) y = C() print("Anzahl der Instanzen: " + str(C.counter))

erhalten wir eine Fehlermeldung: $ python3 counter.py Anzahl der Instanzen: 1 Anzahl der Instanzen: 2

165

166

19 Objektorientierte Programmierung

Exception AttributeError: "'NoneType' object has no attribute 'counter'" in ignored Exception AttributeError: "'NoneType' object has no attribute 'counter'" in ignored

Die Fehlermeldungen resultieren daher, dass die Methode __del__ in diesem Fall erst aufgerufen wird, wenn die Klasse bereits gelöscht ist. Eine Lösung des Problems besteht darin, die globalen Variablen mit einem Unterstrich zu versehen. Variablen, die mit einem Unterstrich beginnen, werden zuerst gelöscht, wenn ein Programm endet. class C(object): counter = 0 def __init__(self): C.counter += 1 def __del__(self): C.counter -= 1 if __name__ == "__main__": _x = C() print("Anzahl der Instanzen: " + str(C.counter)) _y = C() print("Anzahl der Instanzen: " + str(C.counter))

Obiges Skript liefert nun die folgende Ausgabe: $ python3 counter.py Anzahl der Instanzen: 1 Anzahl der Instanzen: 2

19.15

Properties

In der folgenden Klasse P wollen wir zeigen, wie man korrekt im Sinne der Datenkapselung auf private Attribute zugreift. In den meisten Fällen muss man ein Attribut lesen und ändern können. Dazu muss man dann zwei Methoden schreiben: eine, um einen Attributwert lesen zu können (in unserem Fall „getX()”), und eine, um diesen Wert ändern zu können (in unserem Fall „setX()”). Diese Abfragemethoden werden allgemein auch als „getter” und die Änderungsmethoden als „setter” bezeichnet. class P(object): def __init__(self,x): self.__x = x def getX(self):

19.15 Properties

return self.__x def setX(self, x): self.__x = x

In der folgenden interaktiven Beispielsitzung zeigen wir, wie man mit diesen Methoden arbeitet: >>> from p import P >>> a = P(19) >>> a.getX() 19 >>> a.setX(42) >>> b = a.getX() * 0.56 >>> b 23.520000000000003 >>>

Deutlich bequemer wäre es natürlich, wenn wir direkt b = a.__x * 0.56 statt b = a.getX() * 0.56

schreiben könnten. Dies geht jedoch nicht, weil __x ein private-Attribut ist, und wir wollen ja keinesfalls das Konzept der Datenkapselung aufweichen oder verletzen. Mit den Properties bietet Python ein Sprachkonstrukt, was einem einen leichteren Zugriff auf privateAttribute ermöglicht. Wir erweitern obiges Beispiel um eine Property x: class P(object): def __init__(self,x): self.__x = x def getX(self): return self.__x def setX(self, x): self.__x = x x = property(getX, setX)

Das erste Argument von property muss dem „getter”, also in unserem Fall der Methode getX(), entsprechen. Das zweite Argument enthält den Namen der „setter”-Methode, also „setX()” in unserem Beispiel. Jetzt können wir x benutzen, als wäre es eine öffentliche Variable: >>> >>> >>> >>> >>>

from p import P a = P(19) b = P(10) c = a.x + b.x c

167

168

19 Objektorientierte Programmierung

29 >>>

Ohne Zweifel ist dies in der Benutzung angenehmer. Dennoch erfolgen alle Zugriffe auf das private Attribut _x nur über die korrekten Zugriffsmethoden, also setX() und getX(). Möchte man lediglich einen lesenden Zugriff auf ein privates Attribut erlauben, dann ruft man die Property nur mit einem Argument, also der „getter”-Methode auf: class P(object): def __init__(self,x): self.__x = x def getX(self): return self.__x def setX(self, x): self.__x = x x = property(getX)

Wir können in obiger Beispielanwendung sehen, dass wir lesend zugreifen können. Versuchen wir jedoch, der Property x etwas zuzuweisen, erhalten wir eine Fehlermeldung. >>> from p import P >>> a.x = 42 Traceback (most recent call last): File "", line 1, in AttributeError: can't set attribute >>>

19.16

Dynamische und statische Attribute

Bisher hatte jedes Objekt einer Klasse seine eigenen Attribute und Methoden, die sich von denen anderer Objekte unterschieden. Man bezeichnet dies als „nicht-statisch” oder „dynamisch”, da sie für jedes Objekt einer Klasse dynamisch erstellt werden. Wie kann man jedoch Informationen speichern, die sich nicht auf ein bestimmtes Objekt beziehen, sondern für die ganze Klasse relevant sind? Eine solche Information wäre beispielsweise die Anzahl aller Objekte einer Klasse, also in unserem Fall die Anzahl der verschiedenen Konten der Klasse Konto. Wie wir bereits gesehen haben, werden Instanzattribute innerhalb der __init__-Methode angelegt, wohingegen statische Attribute außerhalb dieser Methode direkt im class-Block definiert werden. Außerdem muss jedem Klassenattribut ein Initialwert zugewiesen werden. Es ist Usus, die statischen Member direkt unterhalb der class-Anweisung zu positionieren. 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.

19.17 Vererbung

Wir erläutern dies in einem kleinen Beispiel: class P(object): __counter = 0; def __init__(self,x): self.__x = x P.__counter += 1 def __del__(self): P.__counter -=1 def getCounter(): return P.__counter

Wie das Zählen funktioniert, zeigen wir in der folgenden interaktiven Sitzung: >>> >>> >>> 1 >>> >>> 2 >>> >>> 2 >>> >>> 2 >>> >>> 1 >>> >>> 0 >>>

from p_counter import P x = P(42) P.getCounter() y = P(44) P.getCounter() z = x P.getCounter() del x P.getCounter() del y P.getCounter() del z P.getCounter()

19.17 19.17.1

Vererbung

Oberbegriffe und Oberklassen

Jeder kennt die 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, Benziner, mit Diesel oder mit Strom angetriebene Autos. In der objektorientierten Programmierung spricht man hierbei von Vererbung und von Ober- und Unterklassen.

169

170

19 Objektorientierte Programmierung

Ein weiteres Beispiel eines Oberbegriffes 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 Steuernummer, Religionszugehörigkeit BILD 19.6 Vererbung von oder Ähnliches ausnehmen. Nehmen wir nun Personeneigenschaften an, dass man diese Klasse für die Firmenbuchhaltung benutzen will. Man merkt schnell, dass nun die Information über die Religionszugehörigkeit, die Steuerklasse oder Kinderzahl fehlt, um das Nettogehalt korrekt zu berechnen. Ebenso sieht es bei der Kundenklasse aus. Hier machen Information wie Religionszugehörigkeit, Steuerklasse oder Kinderzahl keinen Sinn, aber zum Beispiel die bevorzugte Zahlungsmethode oder Ähnliches. Es macht also Sinn, die Personenklasse als Oberklasse für die Angestellten-, Kunden- und Lieferantenklasse einzuführen. 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.

19.17.2

Vererbung in Python

Bisher haben wir im Prinzip immer Unterklassen definiert. In der Kopfzeile hatten wir immer eine Basisklasse angegeben, nämlich „object”. Also beispielsweise bei der Definition unserer Kontoklasse: class Konto(object): pass

Jede Klasse erbt in Python von der Klasse „object”. In der Einführung zur objektorientierten Programmierung hatten wir bereits allgemein über mögliche Unterklassen einer allgemeinen Klasse „Konto” gesprochen. Eine reale Bank hat viele verschiedene Kontenarten, so wie beispielsweise Sparbücher und Girokonten. Ein Sparbuch ebenso wie ein Girokonto erben also von der Basisklasse „Konto”, und ihre Kopfzeilen enthalten den Namen der Basisklasse. class Sparbuch(Konto): pass class Girokonto(Konto): pass

Einige Konzepte der Vererbung möchten wir jedoch nicht an einem praktischen Beispiel wie der Kontenklasse, sondern an einer abstrakten Oberklasse mit abstrakten Unterklassen demonstrieren. Wir definieren eine Klasse A mit einer __init__-Methode und einer Methode m:

19.17 Vererbung

class A(object): 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")

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 m von B wurde aufgerufen

171

172

19 Objektorientierte Programmierung

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. Man bezeichnet dies als Überschreiben1 einer Methode. Man darf dies nicht mit Überladen verwechseln, was wir später behandeln werden. 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))

Starten wir obiges Programm, sind eigentlich nur die beiden letzten Print-Anweisungen von Interesse. $ 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 print("self.contentA von y: " + str(y.contentA)) AttributeError: 'B' object has no attribute 'contentA'

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 1

engl. overwriting

19.17 Vererbung

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 Objektes einer Basisklasse einführen, auch in der Unterklasse verwenden wollen. Also in unserem Beispiel hätten wir vielleicht gerne das Attribut contentA. 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")

Ruft man das gesamte 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 >>>

173

174

19 Objektorientierte Programmierung

19.18 19.18.1

Mehrfachvererbung

Theorie

In unseren bisherigen Klassenbeispielen erbte eine Unterklasse immer nur von einer Basisklasse. Es gibt aber Problemstellungen, in denen es sinnvoll ist, wenn eine Klasse von zwei oder mehr Basisklassen erben kann. Man spricht in diesen Fällen von Mehrfachvererbung, wenn eine Klasse von mehr als einer Basisklasse erbt. Syntaktisch realisiert man dies wie folgt: Soll eine Klasse von mehreren Basisklassen erben, gibt man die Basisklassen durch Kommata getrennt in die Klammern 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 19.7 Vererbungsbaum bei Mehrfachvererbung

Die Frage stellt sich nun, wie Python auf der Suche nach einem Attribut- oder Methodennamen vorgeht. Auch wenn es in Wirklichkeit komplizierter ist, kann man es sich so vorstellen: Zuerst erfolge eine Tiefensuche2 , dann wird von links nach rechts3 gesucht, wobei nicht zweimal in derselben Klasse gesucht wird, wenn sich die Klassenhierarchie dort überlappt. Falls ein Attribut nicht in Unterklasse gefunden wird, wird anschließend in Basis1 gesucht, dann rekursiv in den Basisklassen von Basis1, also in unserem Beispiel Basis1.1, Basis1.2 und Basis1.3. Wenn auch in all diesen Klassen nichts gefunden wurde, geht die Suche in Basis2 weiter, dann rekursiv in die Basisklassen von Basis2 usw. Dies wollen wir an folgendem Beispiel demonstrieren:

2 3

englisch: depth-first search, DFS left-to-right search

19.18 Mehrfachvererbung

class B11(object): def m(self): print("This is method m of class B11") class B12(object): def m(self): print("This is method m of class B12") class B21(object): def m(self): print("This is method m of class B21") class B1(B11,B12): def m(self): print("This is method m of class B1") class B2(B21): def m(self): print("This is method m of class B2") class U(B1,B2): def m(self): print("This is method m of class U")

x = U() x.m() B1.m(x) B12.m(x)

Startet man dieses Programm, sieht man, dass die Methode m von U aufgerufen wird, wenn man m auf ein Objekt von U anwendet. Außerdem sehen wir, dass wir mit B1.m(x) die Methode m von B1 dennoch auf ein Objekt von U anwenden können: $ python3 mehrfachvererbung.py This is method m of class U This is method m of class B1 This is method m of class B12

Im Folgenden entfernen wir die Methode m aus der Klasse U und ersetzen den Klassenrumpf durch die pass-Anweisung: class B11(object): def m(self): print("This is method m of class B11") class B12(object): def m(self): print("This is method m of class B12") class B21(object):

175

176

19 Objektorientierte Programmierung

def m(self): print("This is method m of class B21") class B1(B11,B12): def m(self): print("This is method m of class B1") class B2(B21): def m(self): print("This is method m of class B2") class U(B1,B2): pass x = U() x.m()

Nach dem anfangs Gesagten müsste nun Python die erste, also die am weitesten links stehende Klasse der Basisklassen durchsuchen. Starten wir das Programm, sehen wir, dass nun wirklich m von B1 aufgerufen wird: $ python3 mehrfachvererbung.py This is method m of class B1

Entfernt man nun die Methode m auch noch aus B1 und ersetzt auch hier den Rumpf durch pass, sieht man, dass nun die Methode m von B11 aufgerufen wird. Entfernen wir nun auch in B11 die Methode m, so wird m aus B12 aufgerufen. Wir können nun so weiter fortfahren: Entfernen wir m aus B12, wird anschließend m aus B2 aufgerufen.

19.18.2

Diamond-Problem

Bei dem Diamond-Problem4 handelt es sich um ein Mehrdeutigkeitsproblem, was durch Mehrfachvererbung in der objektorientierten Programmierung entstehen kann. Es kann auftreten, wenn eine Klasse D auf zwei verschiedenen Vererbungspfaden über eine Klasse B und eine Klasse C von der gleichen Basisklasse A abstammt. Falls D eine Methode, sagen wir „m”, aufruft, für die gilt: ■

m wird in A definiert



B überschreibt m



C überschreibt m



m wird nicht in D überschrieben.

4

BILD 19.8 Diamond-Problem

englisch: diamond problem oder „deadly diamond of death”

19.18 Mehrfachvererbung

Die Frage, die sich dann stellt: Von welcher Klasse wird m vererbt? In Python hängt es von der Reihenfolge der Klassen bei der Definition von D ab: Falls D als class D(B,C): definiert ist, dann kommt m aus B; falls D ansonsten als class D(C,B): definiert ist, dann kommt m aus C. 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.

19.18.3

Beispiel: CalendarClock

Bisher haben wir die Vererbung an sehr abstrakten und theoretischen Klassen erklärt. Wir möchten nun an einem praktischen Beispiel die Vererbung oder genauer sogar das Prinzip der Mehrfachvererbung demonstrieren. Wir schreiben eine Klasse „CalendarClock”, die eine Uhr implementiert, die Uhrzeit und Datum implementiert. Zunächst implementieren wir zwei voneinander unabhängige Klassen: eine Klasse „Clock” und eine Klasse „Calendar”.

BILD 19.9 CalendarClock

Die Klasse Clock simuliert das sekundenmäßige Ticken einer Uhr. Im Kapitel 8 (Verzweigungen) hatten wir in einer Übungsaufgabe das Ticken, also das Vorwärtsschreiten der Uhr um eine Sekunde, bereits simuliert (siehe Seite 58). Der Code der Lösung dieser Übungsaufgabe (siehe Seite 385) befindet sich nun in unserer Methode tick(). 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): def __init__(self, h, min, sec): """ Die Parameter h, min, sec müssen Ganzzahlen sein, und es muss gelten: 0 print(x) 12:59:59 >>> x.tick() >>> print(x) 13:00:00

19.18 Mehrfachvererbung

>>> x.tick() >>> print(x) 13:00:01 """ if self.second == 59: self.second = 0 if self.minute == 59: self.minute = 0 if self.hour == 23: self.hour = 0 else: self.hour += 1 else: self.minute += 1 else: self.second += 1

if __name__ == "__main__": x = Clock(12,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 12:59:59 13:00:00

Was passiert, wenn jemand statt Ganzzahlen Fließkommazahlen oder Strings eintippt? Oder falls jemand eine Minutenzahl eingibt, die die Zahl 59 übersteigt? Wir wollen diese und andere Fehlerfälle in der folgenden interaktiven Python-Shell-Sitzung testen: >>> from clock import Clock >>> x = Clock(7.7,45,17) Traceback (most recent call last): File "", line 1, in File "clock.py", line 18, in __init__ self.set_Clock(h,min,sec) File "clock.py", line 32, in set_Clock raise TypeError("Stunden müssen Ganzzahlen zwischen 0 und 23 sein!") TypeError: Stunden müssen Ganzzahlen zwischen 0 und 23 sein! >>> x = Clock(24,45,17) Traceback (most recent call last): File "", line 1, in File "clock.py", line 18, in __init__

179

180

19 Objektorientierte Programmierung

self.set_Clock(h,min,sec) File "clock.py", line 32, in set_Clock raise TypeError("Stunden müssen Ganzzahlen zwischen 0 und 23 sein!") TypeError: Stunden müssen Ganzzahlen zwischen 0 und 23 sein! >>> x = Clock(23,60,17) Traceback (most recent call last): File "", line 1, in File "clock.py", line 18, in __init__ self.set_Clock(h,min,sec) File "clock.py", line 36, in set_Clock raise TypeError("Minuten müssen Ganzzahlen zwischen 0 und 59 sein!") TypeError: Minuten müssen Ganzzahlen zwischen 0 und 59 sein! >>> x = Clock("23","60","17") Traceback (most recent call last): File "", line 1, in File "clock.py", line 18, in __init__ self.set_Clock(h,min,sec) File "clock.py", line 32, in set_Clock raise TypeError("Stunden müssen Ganzzahlen zwischen 0 und 23 sein!") TypeError: Stunden müssen Ganzzahlen zwischen 0 und 23 sein! >>> x = Clock(23,17) Traceback (most recent call last): File "", line 1, in TypeError: __init__() takes exactly 4 arguments (3 given) >>>

Unser Ziel ist ja eine Klasse „CalendarClock”, die eine Uhrzeitfunktion mit Kalenderfunktion kombiniert. Dazu benötigen wir noch eine Klasse „Calendar”. Ähnlich wie die tickMethode bei der Clock-Klasse brauchen wir auch bei der Calendar-Klasse eine Methode, um den Kalender weiterzubewegen oder „weiterzublättern”. Wir nennen diese Methode „advance”. Sie zählt zu einem gegebenen Kalenderdatum einen Tag hinzu. Dabei müssen wir die Anzahl der Tage für die bestimmten 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 58. Die dazugehörige Lösung befindet sich auf Seite 383). In unserer Klasse implementieren wir diese Funktionalität unter dem Namen „leapyear” als Klassenmethode, die . Für die Anzahl der Monate benutzen wir eine Liste 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]

19.18 Mehrfachvererbung

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

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.day = d self.month = m self.year = 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. """

181

182

19 Objektorientierte Programmierung

return "{0:02d}.{1:02d}.{2:4d}".format(self.day, self.month, self.year)

def advance(self): """ setzt den Kalender auf den nächsten Tag unter Berücksichtigung von Schaltjahren """ max_days = Calendar.months[self.month-1] if self.month == 2 and Calendar.leapyear(self.year): max_days += 1 if self.day == max_days: self.day= 1 if self.month == 12: self.month = 1 self.year += 1 else: self.month += 1 else: self.day += 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)

19.18 Mehrfachvererbung

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: >>> from calendar import Calendar >>> x = Calendar(31,12,2012) >>> x = Calendar("31",12,2012) Traceback (most recent call last): File "", line 1, in 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 "", line 1, in 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 der Klasse Clock und der Klasse 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 und richtig. """ 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.

183

184

19 Objektorientierte Programmierung

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 ClockKlasse 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 """ 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.hour Clock.tick(self) if (self.hour < previous_hour): self.advance() def __str__(self): """ Erzeugt die Stringdarstellung eines CalendarClock-Objekts """ s = "{0:02d}.{1:02d}.{2:4d}".format(self.day, self.month, self.year) s += " " + "{0:02d}:{1:02d}:{2:02d}".format(self.hour, self.minute, self.second) return s

if __name__ == "__main__": x = CalendarClock(31,12,2013,23,59,59)

19.19 Polymorphie

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

19.19

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

Polymorphie

Ein wesentliches Konzept der objektorientierten Programmierung stellt die Polymorphie dar. Der 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 aufrufen kann. Man spricht dann vom Überladen von Methoden. In Python kann man auch Operatoren und Standardfunktionen überladen, worauf wir im nächsten Abschnitt eingehen. Methoden und Funktionen in Python haben bereits eine implizite Polymorphie wegen des dynamischen Typkonzepts von Python. Betrachten wir die folgende Python-Funktion: def f(x, y): print("values: ", x, y) f(42,43) f(42, 43.7) f(42.3,43) f(42.0, 43.9)

185

186

19 Objektorientierte Programmierung

Die Funktion f rufen wir mit verschiedenen Typ-Paarungen auf. Beim ersten Aufruf mit (int, int), dann mit (int, float), dann (float, int) und im vierten Aufruf mit (float, float). In getypten Programmiersprachen wie C++ müssten wir entsprechend dieser Typ-Paarungen f in vier Varianten definieren. Ein C++-Programm, das dem obigen Python-Programm entspricht, könnte wie folgt aussehen: #include using namespace std; void f(int x, int y ) { cout >> a = 3 + 4 >>> a 7 >>> 3 + 5.543 8.543 >>> s = "Hello" >>> print(s + " World") Hello World >>>

BILD 19.10 Überladen des +-Operators

Python erlaubt es auch, 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.__ad d __(y) auf, falls es eine solche Methode in der Klasse K gibt, ansonsten erfolgt die Fehlermeldung Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'K' and 'K'

Binäre Operatoren: Operator

Methode

+

object.__add__(self, other)

-

object.__sub__(self, other)

*

object.__mul__(self, other)

//

object.__floordiv__(self, other)

/

object.__div__(self, other)

%

object.__mod__(self, other)

**

object.__pow__(self, other[, modulo])

«

object.__lshift__(self, other)

»

object.__rshift__(self, other)

&

object.__and__(self, other)

187

188

19 Objektorientierte Programmierung

Operator

Methode

^

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.__idiv__(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)

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

19.21

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.

19.22 Aufgaben

Die Verarbeitung von Stapelspeichern5 wird in Python, wie wir gesehen haben, mittels append und pop realisiert. Üblicherweise, d.h. in anderen Programmiersprachen, werden meistens die Funktionen push6 und pop7 verwendet. Wir wollen im Folgenden Beispiel die Klasse „list” um eine Methode „push” erweitern, die genau das Gleiche macht wie append: class Plist(list): 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)

19.22

Aufgaben

1. Aufgabe: In dieser Aufgabe geht es um eine Roboterklasse. Uns interessiert nicht das Aussehen und die Beschaffenheit dieses Roboters. Wir interessieren uns lediglich für seine Position in einer imaginären „Landschaft”. Diese Landschaft soll zweidimensional sein. Sie kann durch ein Koordinatensystem beschrieben werden.

BILD 19.11 Roboter

5 6 7

auch Kellerspeicher genannt, im Englischen stack deutsch: einkellern deutsch: auskellern

189

190

19 Objektorientierte Programmierung

Ein Roboter unserer Klasse hat also zwei Attribute, die die x- und die y-Koordinate darstellen. Es empfiehlt sich natürlich, diese Informationen in einer 2er-Liste 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. Wir brauchen also noch ein Attribut mit dieser Orientierung. Außerdem können wir noch ein weiteres Attribut mit dem Namen des Roboters definieren. Die genaue Beschreibung der benötigten Methoden können Sie der folgenden helpBeschreibung entnehmen: Help on module robot: NAME robot DESCRIPTION Roboterklasse zur Positionsbeschreibung und Veränderung von Objekten in einem zweidimensionalen Koordinatensystem CLASSES builtins.object Robot class Robot(builtins.object) | Methods defined here: | | __init__(self, x=0, y=0, orientation='north', name='marvin') | | __str__(self) | Stringdarstellung einer Instanz | | getName(self) | Liefert den Namen des Roboters zurück | | getOrientation(self) | Ein Aufruf von x.getOrientation() für einen Roboter x liefert | dessen aktuelle Orientierung zurück, also eine der Richtungen | "west", "south", "east" oder "north". | | getPosition(self) | Liefert eine 2er-Liste mit [x,y] zurück. | | 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]. | | Falls eine unbekannte Orientierung übergeben wird, wird der | Roboter nicht bewegt. | | newOrientation(self, o)

19.22 Aufgaben

| | | | | | | | | | | | | | | | | | | | | | |

Mit der Methode newOrientation ändern wir die Orientierung des Roboters. o has to be in {"north","south","west","east"} rename(self, name) Damit kann man dem Roboter einen neuen Namen geben. setPosition(self, pos) Damit kann man den Roboter auf eine neue Position im Koordinatensystem positionieren. pos muss eine Liste oder ein Tupel mit zwei Elementen sein Ansonsten wird nichts getan. ---------------------------------------------------------------Data descriptors defined here: __dict__ dictionary for instance variables (if defined) __weakref__ list of weak references to the object (if defined)

FILE /home/data/bodenseo/python/beispiele/robot.py (END)

Lösung: 1. Aufgabe, Seite 400

2. Aufgabe: Neanderthal- 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 BILD 19.12 Neandertal-Arithmetik 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

191

192

19 Objektorientierte Programmierung

Bierdeckel. Man könnte aber ebenso gut eine Null, eine Eins oder irgendein anderes Zeichen verwenden. Es gilt: 7d ezi mal = |||||||unär

Schreiben Sie eine Klasse CaveInt, die die vier Grundrechenarten für die Neandertalarithmetik 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: 2. Aufgabe, Seite 403

3. 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: 3. Aufgabe, Seite 404

TEIL II Weiterführende Themen

20 20.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 wenn wir Fehler nie voll- BILD 20.1 Fehler sind menschlich ständig vermeiden werden 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 gibt es immer wieder „kleine Fehler”, häufig sind es kleine Tippfehler. 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 von einzelnen Wörtern, also zum Beispiel 1

2

3

Boehm und Barry sprechen von 40 - 50 %, die zum Beheben von Design- und Code-Fehlern benötigt werden: Boehm, Barry W. 1987. Ïmproving Software Productivity.ÏEEE 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.” In 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.

196

20 Tests und Fehler

Schlüsselwörtern4 oder von Strukturregeln. In der Linguistik würde man das als Satzbauregeln bezeichnen, also zum Beispiel der vergessene Doppelpunkt hinter dem „else” oder eckige statt runder Klammern bei einem Funktionsaufruf. Neben den syntaktischen Fehlern, die sich häufig relativ leicht finden und beheben lassen, gibt es auch die semantischen Fehler.5 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-Anweisungen. Das Codefragment ist syntaktisch korrekt. Es könnte aber sein, dass der Schreiber des Programmes 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 2 4 5

englisch: key words 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!”

20.2 Modultests

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 nicht die Problemstellung kennen. Nehmen wir an, dass der Programmierer die Zahlen von 1 bis 10 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.

20.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 Programmes zu testen, d.h. man prüft, ob sie die geforderte Funktionalität bringen. Es empfiehlt sich, Tests auf Modulebene durchzuführen, da die dortigen Funktionalitäten noch eine begrenzte bzw. überschaubare Komplexität zeigen und die Schnittstellen BILD 20.2 Testen und Messen 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.

20.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”, gespeichert unter „abc.py”. Wird die-

197

198

20 Tests und Fehler

ses Modul mit „import abc” importiert, dann hat das built-in-Attribut __name__ den Wert „abc”. Wird abc 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: >>> from fibonacci import fib, fiblist >>> fib(0) 0 >>> fib(1) 1 >>> fib(10) 55 >>> fiblist(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] >>> fiblist(-8) [0, 1] >>> fib(-1) 0 >>> fib(0.5) Traceback (most recent call last): File "", line 1, in File "fibonacci.py", line 6, in fib for i in range(n): TypeError: 'float' object cannot be interpreted as an integer >>>

20.3 Modultests unter Benutzung von __name__

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 testen, 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

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__”. Wir können unser Modul nun so umschreiben, dass die Tests nur gestartet werden, wenn das Modul direkt gestartet wird:

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.

199

200

20 Tests und Fehler

""" 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.

20.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. Im Folgenden zeigen wir das Vorgehen an einem simplen Beispiel. Dazu haben wir das vorige fibonacci-Modul bis auf die Funktion fib abgespeckt: 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

20.4 doctest-Modul

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 >>> 610 >>>

fib(0) fib(1) fib(10) fib(15)

""" 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.

201

202

20 Tests und Fehler

Deshalb bauen wir wieder einen kleinen Fehler ein. Dazu ändern wir wieder 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 Got: 987 ********************************************************************** 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.

20.5

Testgetriebene Entwicklung

Im vorigen Kapitel hatten wir bereits eine fertig geschriebene Fibonacci-Funktion. Man kann auch so vorgehen, dass man bereits am Anfang Ergebnisse in den Docstring schreibt

20.5 Testgetriebene Entwicklung oder „Im Anfang war der Test”

und die Funktion dann erst entwickelt. 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()

Es versteht sich von selbst, dass ein Test dieses Moduls außer für fib(0) nur Fehler liefert: $ 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

203

204

20 Tests und Fehler

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. Wie immer in der SWBranche werden auch häufig nur die englischen Begriffe benutzt, d.h. „test first development” oder noch geläufiger „test-driven development” (TDD).

20.6

unittest

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) 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.

20.6 unittest

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

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

205

206

20 Tests und Fehler

if n == 20: a = 42 return a

Die Ausgabe eines Testlaufs sieht nun wie folgt aus: $ python3 fibonacci_unittest.py blabal F ====================================================================== FAIL: testCalculation (__main__.FibonacciTest) ---------------------------------------------------------------------Traceback (most recent call last): File "fibonacci_unittest.py", line 12, in testCalculation self.assertEqual(fib(20), 6765) AssertionError: 42 != 6765 ---------------------------------------------------------------------Ran 1 test in 0.000s 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)

20.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): 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.

20.7 Methoden der Klasse TestCase

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()

Ein Aufruf führt zu folgendem Ergebnis: $ 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 20.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. (Fortsetzung nächste Seite)

207

208

20 Tests und Fehler

TABELLE 20.1 Methoden der Klasse TestCase (Fortsetzung) Methode

Bedeutung

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.

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.

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 >> import os >>> info = os.stat("abc.py") >>> type(info) >>> 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 >>>

21.3 os-Modul

21.3.1

Vorbemerkungen

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 "", line 1, in 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.

21.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'

213

214

21 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. „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") >>> 42 >>>

Es stellt sich natürlich die Frage, wie können wir eine Umgebungsvariable im laufenden Prozess ändern bzw. neu erzeugen. Wir erreichen dies, indem wir direkt das Dictionary os.environ manipulieren: >>> os.environ["ABC"] = "42" >>> os.getenv("ABC") '42' >>> os.environ["ABC"] '42' >>>

21.3 os-Modul





supports_bytes_environ( ) Wenn dieses Attribut den Wert True hat, existiert auch ein Dictionary os.environb, welches die Umgebungsvariablen als Bytestrings enthält. 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") >>>

21.3.3

Dateiverarbeitung auf niedrigerer Ebene

Im Kapitel 10 (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, weslhalb 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

215

216

21 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 bernd@saturn:~/bodenseo/python/tmp$

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 os.write(fd, "Noch etwas Text\n") TypeError: 'str' does not support the buffer interface bernd@saturn:~/bodenseo/python/tmp$

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 bernd@saturn:~/bodenseo/python/tmp$ ■

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.

21.3 os-Modul

– 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. – 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 ist 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 und 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 read-only 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

217

218

21 Systemprogrammierung

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. 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") 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 eine Fehlermeldung, wenn man das Skript startet, wie man im Folgenden sieht: bernd@saturn:~/bodenseo/python/tmp$ python3 os_write.py Traceback (most recent call last): File "os_write.py", line 3, in fd = os.open( "blabla.txt", os.O_WRONLY ) OSError: [Errno 2] No such file or directory: 'blabla.txt' bernd@saturn:~/bodenseo/python/tmp$ ■

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: bernd@saturn:~/bodenseo/python/beispiele$

21.3 os-Modul





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: import os master_pty,slave_tty = os.openpty() terminal_name = os.ttyname(slave_tty) 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 bernd@saturn:~/bodenseo/python/tmp$ ■

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))

219

220

21 Systemprogrammierung

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)) 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)) 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' bernd@saturn:~/bodenseo/python/beispiele$ ■

3

dup() Liefert die Kopie eines Datei-Handles3 zurück.

engl. file descriptor

21.3 os-Modul

BILD 21.2 Systematik der exec-Familie

21.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 einer regulären und ausführbaren Datei konstruiert. Man nennt diese Datei „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 Namen als Parameter als 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.

221

222

21 Systemprogrammierung

Das Bash-Skript „test.sh”: #!/bin/bash script_name=$0 arg1=$1 current=`pwd` echo $script_name, $arg1 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. ■

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 "", line 1, in 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")

21.3 os-Modul

>>> 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:/usr/ games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/bernd/ bin:.::bin/bck_scripts/:bin/bck_scripts/ current directory: /home/bernd/bin2 bernd@saturn:~/bin2$

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

223

224

21 Systemprogrammierung

Wir wollen nun PATH und die anderen alten Umgebungsvariablen auch an das Skript übergeben. Wir können dies mit der update-Methode erreichen. os.environ enthält alle definierten Umgebungsvariablen, der aktuellen Shell. Dann können wir unser Skript wieder von allen Orten aufrufen, sofern noch bin2 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 "", line 1, in 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 current directory: /home/data/bodenseo/python bernd@saturn:~/bodenseo/python$

21.3 os-Modul



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. 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$ ■

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:

225

226

21 Systemprogrammierung

>>> import os >>> os.execl("test_params.sh", "test_params.sh", "arg1", "arg2") Traceback (most recent call last): File "", line 1, in 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$

21.3.5 ■

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$

21.3 os-Modul

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 durch die Stringvariable „path” bezeichnete Verzeichnis geändert. >>> os.getcwd() '/home/bernd' >>> os.chdir("/home/bernd/bodenseo/python") >>> os.getcwd() '/home/data/bodenseo/python' >>>



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)

227

228

21 Systemprogrammierung

>>> 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 auf -1 beim Aufruf. 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 "", line 1, in 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")



extsep Ein Attribut mit dem Wert des Separators für die Dateikennung4 , also „.” >>> os.extsep '.' >>>





4

fork( ) siehe 22 (Forks) forkpty( ) siehe 22 (Forks) file extension

21.3 os-Modul



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] >>>





5

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. engl. user ID

229

230

21 Systemprogrammierung

>>> 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 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.

21.3 os-Modul

Im folgenden Beispiel wollen wir einen Prozess mit der pid 7036 beenden: >>> import os >>> os.kill(7036,9) ■







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 Majornummer 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 Minornummer 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: bernd@saturn:~/bodenseo/python/beispiele$ python3 os_major.py Major Device Number : 8 Minor Device Number : 7 bernd@saturn:~/bodenseo/python/beispiele$

231

232

21 Systemprogrammierung



makedev( ) Diese Funktion erzeugt eine Gerätenummer aus Minor- und der Majornummer. 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 bernd@saturn:~/bodenseo/python/beispiele$ ■

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): File "", line 1, in 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.

21.3 os-Modul

Der optionale Parameter „mode” entspricht dem Modus, also z.B. „0755”. 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. 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.

233

234

21 Systemprogrammierung

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("""Kommand "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 Kommand "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 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

21.3 os-Modul

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'} bernd@saturn:~/bodenseo/python/beispiele$ ■

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. 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

235

236

21 Systemprogrammierung

7 directories, 0 files bernd@saturn:~/tmp$

Aus dem folgendem Beispiel lernen wir, dass wir keine Verzeichnisse mit removedirs löschen können, die nicht leer sind. Bei der Ausführung von „os.removedirs("x/y/c")” ist das Verzeichnis c das einzige Unterverzeichnis und Datei von y. Der Aufruf löscht deshalb auch automatisch das Elternverzeichnis „y”. >>> import os >>> os.removedirs("x/y") Traceback (most recent call last): File "", line 1, in 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 "", line 1, in 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 bernd@saturn:~/tmp$ ■



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)

21.3 os-Modul

[('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 "", line 1, in OSError: [Errno 39] Directory not empty: 'x_new' >>>

Will man ein nichtleeres 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. 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.

237

238

21 Systemprogrammierung

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)) ... 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 >>>

21.3 os-Modul



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( )



system( command ) Eine Kommandostring „command” wird als in einer Untershell ausgeführt. 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( ) uname( ) Die Funktion uname liefert ein Tupel zurück, welches die folgenden Informationen über das Betriebssystem enthält: (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' >>>

239

240

21 Systemprogrammierung











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 Kind-Prozess 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:

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") >>> 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.

21.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”

21.3 os-Modul



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") '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. 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:

241

242

21 Systemprogrammierung

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



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. 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.

21.3 os-Modul

>>> 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 "", line 1, in 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'





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")

6

Als Metadaten bezeichnet man in diesem Zusammenhang „Zugriffsrechte”, „Besitzer” oder Ähnliches.

243

244

21 Systemprogrammierung

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 >>> ■

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

21.3 os-Modul

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



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

245

246

21 Systemprogrammierung



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 Pfadnamekomponenten 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/' >>>



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.

21.3 os-Modul

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'



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. 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' >>>

247

248

21 Systemprogrammierung



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', '') >>> os.path.split("/abc/xyz") ('/abc', 'xyz') >>> os.path.split("xyz.py") ('', 'xyz.py') >>>



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:

21.4 shutil-Modul

>>> 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/', '') >>>

21.4

shutil-Modul

Im vorigen Abschnitt hatten wir bereits eine Datei unter Benutzung des shutil-Modules 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, 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 ■

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. >>> >>> >>> >>> >>>

7 8

fh = open("abc.txt") fhw = open("spam.txt", "w") shutil.copyfileobj(fh,fhw) fh.close() fhw.close

src steht für das englische Wort source, was Quelle in Deutsch bedeutet. dst steht dabei als Abkürzung für destination, was in Deutsch Ziel bedeutet.

249

250

21 Systemprogrammierung







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=, ignore_dangling_symlinks=False) Ein Verzeichnisbaum „src” wird rekusiv in einen Verzeichnisbaum „dst” kopiert. Wir betrachten das Beispiel, das wir bereits in Beispiel 21.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 "", line 1, in 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' >>>

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 Teilbaumes liegt. In anderen Worten „src” ist ein Teilstring von „dst”, wenn man von Pfadnamen in kanonischer Darstellung, also ohne Links, ausgeht. 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") >>> 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'])] >>>

21.4 shutil-Modul

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')] >>>



get_unpack_formats() Liefert eine Liste der Dekomprimierformate zurück. Jedes Element der Ergebnisliste entspricht einem Tupel der Form: (Name, Extension, Beschreibung) >>> 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 erzeugt werden soll, ohne die übliche Endung. Der vollständige Name der Archivdatei ergibt sich mit dem Parameter „format”, welcher der Extension des Archivformats entspricht, also „zip”, „tar”, „bztar” oder „gztar”.

251

252

21 Systemprogrammierung

Der vollständige Name der Archivdatei wird also 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 "", line 1, in 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) 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

21.4 shutil-Modul

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 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, 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 ist ein Tupel, 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.



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

253

254

21 Systemprogrammierung

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.

22 22.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 22.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

22.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 Programmes 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

256

22 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. Um Prozesse forken zu können, müssen wir das Modul os in Python importieren. Das folgende Beispiel-Skript zeigt einen Eltern-Prozess (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 22.2 veranschaulicht nochmals, was genau beim Forking passiert: 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()))

22.2 Fork in Python

BILD 22.2 Forking

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')

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]

257

258

22 Forks

3. count of [6429] 3. count of [6428] 3. count of [6430] bernd@saturn:~/bodenseo/python/beispiele$

23 23.1

Das Modul „pickle”

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:

BILD 23.1 Daten „eingurken”

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 Python3. 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.

260

23 Das Modul „pickle”

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()

23.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']

24 24.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 24.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ücken 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.

24.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

262

24 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 >>>

Im obigen Beispiel wurde geprüft, ob das Wort„einfach” im String s vorkommt. Im Folgenden zeigen wir Schritt für Schritt, wie die Stringvergleiche 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:

24.3 Überlappungen und Teilstrings

Im nächsten Schritt kann eine vollständige Übereinstimmung gefunden werden, denn es gilt s[4:7] == sub

24.3

Überlappungen und Teilstrings

Mit regulären Ausdrücken können prinzipiell zwei Aufgaben bewerkstelligt werden: 1. Überlappungen: Feststellen, ob solch 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.

24.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: 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.

263

264

24 Reguläre Ausdrücke

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)

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

24.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.” 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

1

englisch: escape

24.5 Matching-Problem

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 BILD 24.2 „under” und Menge O (blauer Kreis). C ist eine Teilmenge von O. In die- „over” matching sem Diagramm erkennt man auch eine Menge U (hellgrüner 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 >>> 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: >>> ... ... ... ... Der >>> ... ...

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" Ausdruck hat gepasst if re.search("cow","A cat and a rat can't be friends."): print "Der Ausdruck hat gepasst" else:

265

266

24 Reguläre Ausdrücke

... print "Der Ausdruck hat nicht gepasst" ... Der Ausdruck hat nicht gepasst

24.6 24.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("") ... else: ... print("no match") ... matched matched matched matched no match no match >>>

24.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: r"M[ae][iy]er"

24.8 Endliche Automaten

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 „^”.

24.8

Endliche Automaten

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 2

englisch: finite state machine (FSM)

267

268

24 Reguläre Ausdrücke

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 24.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.

24.9

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. Sie BILD 24.4 Wortbegrenzer matchen leere Strings in Abhängigkeit von deren Nachbarschaft, d.h. es hängt davon ab, welches Zeichen vor und nach dem leeren String steht. \b passt zum Beispiel, wenn ein leerer String zwischen einem \W und einem \w steht oder umgekehrt, wenn er zwischen \w und \W steht. \B bezeichnet wie üblich das Komplement, das bedeutet, es werden leere Strings zwischen \W und \W und leere Strings zwischen \w und \w gematcht. Diesen Sachverhalt haben wir im nebenstehenden Diagramm veranschaulicht.

24.10 Anfang und Ende eines Strings

TABELLE 24.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.

24.10

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 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. 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))

269

270

24 Reguläre Ausdrücke

>>> print(re.search(r"M[ae][iy]er", s2)) >>> print(re.match(r"M[ae][iy]er", s1)) >>> 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. Aber die Syntax der regulären Ausdrücke stellt eine andere 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) >>> 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: >>> s = s2 + "\n" + s1 >>> print(re.search(r"^M[ae][iy]er", s, re.MULTILINE)) >>> print(re.search(r"^M[ae][iy]er", s, re.M)) >>> 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.

24.11 Optionale Teile

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 >>> 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)) >>>

24.11

Python.")) Python and Perl.")) Python.\nSome prefer Java or

Python.\nSome prefer Java or

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: 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: r"Feb(ruary)? 2011"

271

272

24 Reguläre Ausdrücke

24.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 matched, 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]+ .*"

Aber auch mit Plus- und Sternchen-Operator fehlt noch etwas Wichtiges: Wir wollen in bestimmten Situation 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]+"

24.12 Quantoren

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 min-Mal, 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 Telefonbuch3 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 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 3

Das gesamte Telefonbuch finden Sie in unserem Programm- und Beispielverzeichnis.

273

274

24 Reguläre Ausdrücke

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

24.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.

24.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. 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

24.13 Gruppierungen und Rückwärtsreferenzen

>>> 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: Wolfgang Amadeus Mozart Samuel Beckett London

Wir möchten diesen Text automatisch in folgendes Format umschreiben: 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 „” stößt. Alles, was zwischen „” 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 „(.*)” gelautet. Das zugehörige Python-Skript:

275

276

24 Reguläre Ausdrücke

import re fh = open("tags.txt") for i in fh: res = re.search(r"(.*)",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 Nachnamen. 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"] 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))

24.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 nahezu 15.000 Zeilen mit Postleitzahlen mit 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: 90402,"Nürnberg",9564,"Nürnberg, Stadt",9,"Bayern" 80331,"München",9184,"München",9,"Bayern"

24.14 Umfangreiche Übung

86150,"Augsburg",9761,"Augsburg, Stadt",9,"Bayern" 89073,"Ulm",8421,"Ulm",8,"Baden-Württemberg" 70173,"Stuttgart",8111,"Stuttgart",8,"Baden-Württemberg" 71032,"Böblingen",8115,"Böblingen",8,"Baden-Württemberg" 78462,"Konstanz",8335,"Konstanz",8,"Baden-Württemberg" 88662,"Überlingen",8435,"Bodenseekreis",8,"Baden-Württemberg" 88045,"Friedrichshafen",8435,"Bodenseekreis",8,"Baden-Württemberg" 79098,"Freiburg im Breisgau",8311,"Freiburg im Breisgau",8,"Baden-Wü rttemberg" 76131,"Karlsruhe",8212,"Karlsruhe",8,"Baden-Württemberg" 68159,"Mannheim",8222,"Mannheim",8,"Baden-Württemberg" 67059,"Ludwigshafen am Rhein",7314,"Ludwigshafen am Rhein, Stadt",7," Rheinland-Pfalz" 66740,"Saarlouis",10044,"Saarlouis",10,"Saarland" 66111,"Saarbrücken",10041,"Stadtverband Saarbrücken",10,"Saarland" 78048,"Villingen-Schwenningen",8326,"Schwarzwald-Baar-Kreis",8,"Baden-Wü rttemberg" 78532,"Tuttlingen",8327,"Tuttlingen",8,"Baden-Württemberg" 60311,"Frankfurt am Main",6412,"Frankfurt am Main, Stadt",6,"Hessen" 50667,"Köln",5315,"Köln, Stadt",5,"Nordrhein-Westfalen"

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. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.

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 Düsseldorf 569.364 Nordrhein-Westfalen Bremen 539.403 Bremen Hannover 515.001 Niedersachsen Duisburg 514.915 Nordrhein-Westfalen Leipzig 493.208 Sachsen Nürnberg 488.400 Bayern Dresden 477.807 Sachsen Bochum 391.147 Nordrhein-Westfalen Wuppertal 366.434 Nordrhein-Westfalen Bielefeld 321.758 Nordrhein-Westfalen Mannheim 306.729 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 post_codes_germany.txt. import re import codecs

277

278

24 Reguläre Ausdrücke

fh_post_codes = codecs.open("post_codes_germany.txt", encoding="iso -8859-1") PLZ = {} for line in fh_post_codes: (post_code, city, rest) = line.split(",",2) PLZ[city.strip("\"")] = post_code

fh_largest_cities = codecs.open("largest_cities_germany.txt", encoding=" utf-8") 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, PLZ[city])

Die Ausgabe für obiges Programm sieht wie folgt aus: $ python3 largest_cities_postcode.py 14199 Berlin 22769 Hamburg 81929 München 51149 Köln 65936 Frankfurt am Main 45359 Essen 44388 Dortmund 70629 Stuttgart 40629 Düsseldorf 28779 Bremen 30669 Hannover 47279 Duisburg 4357 Leipzig 90491 Nürnberg 1478 Dresden 44894 Bochum 42399 Wuppertal 33739 Bielefeld 68309 Mannheim

24.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

24.16 Alternativen

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')] >>>

24.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)"

279

280

24 Reguläre Ausdrücke

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.

24.17

Kompilierung 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 kompilieren. 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 >>>

Auf dem SRE_Pattern-Objekt ce kann man nun die Methode search anwenden, die nur einen String braucht, auf dem versucht wird, das zuvor kompilierte Pattern zu matchen: >>> x = ce.search("Eine Binärzahl: 00111") >>> x >>> x.span() (17, 21) >>> x.group() '0111' >>>

Das SRE_Pattern-Objekt kennt auch eine Methode findall4 , 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 "", line 1, in AttributeError: '_sre.SRE_Match' object has no attribute 'findall' 4

ebenso wie match, split sub und andere

24.17 Kompilierung von regulären Ausdrücken

TABELLE 24.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

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.

von Unicode-Einstellungen abhängig.

>>> x = ce.findall("Eine Binärzahl: 00111 und noch eine 01011") >>> x ['0111', '01', '011'] >>>

Kompilierte reguläre Ausdrücke sparen in der Regel nicht viel Rechenzeit, weil Python sowieso automatisch reguläre Ausdrücke kompiliert und zwischenspeichert, auch wenn man sie mit re.search() oder re.match() aufruft. Ein guter Grund dafür, sie zu verwenden, besteht darin, die Definition und die Benutzung von regulären Ausdrücken zu trennen.

281

282

24 Reguläre Ausdrücke

24.18 24.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. Wir demonstrieren dieses Verhalten mit einem Zitat von Abraham Lincoln:

BILD 24.5 Splitting Strings

>>> 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: >>> line = "James;Miller;teacher;Perl" >>> line.split(";") ['James', 'Miller', 'teacher', 'Perl']

24.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck

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.']

24.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: >>> 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;"

283

284

24 Reguläre Ausdrücke

>>> 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 (logische 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.5 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. 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 (logische Schluss), der aus einem Obersatz, einem Untersatz und einer Konklusion (Schlussfolgerung) besteht - somit:

5

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.

24.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck

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 Pfostenlock in sechzig Sekunden graben; deswegen: Konklusion: Sechzig Männer können ein Pfostenloch in einer Sekunde graben.

24.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!"""

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)

285

286

24 Reguläre Ausdrücke

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 '} >>>

24.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:6 >>> 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.

24.20

Aufgaben

1. Aufgabe: 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 24 (Reguläre Ausdrücke), Seite 405

6

Der im Beispiel verwendete String entspricht den letzten Worten des Romans „Finnegans Wake” von James Joyce.

24.20 Aufgaben

2. Aufgabe: Was matcht x(xy)*x? (1) (2) (3) (4) (5)

xx xyxyxyx xxyxy xyxx xxyxyx

Lösung: Lösungen zu Kapitel 24 (Reguläre Ausdrücke), Seite 406

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 24 (Reguläre Ausdrücke), Seite 406

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 24 (Reguläre Ausdrücke), Seite 407

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 24 (Reguläre Ausdrücke), Seite 407

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.

287

288

24 Reguläre Ausdrücke

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 24 (Reguläre Ausdrücke), Seite 408

25

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() and map() noch nie gemocht und sie bereits 1993 widerwillig in Python aufgenommnen, nachdem er eine Codeerweiterung mit diesen Funktionalitäten von einem, wie er glaubt, Lisp-Hacker erhalten hatte. In Python 3 sollten sie nach seinem Willen verschwinden. 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 also 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”).

25.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.

BILD 25.1 Dateien

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

290

25 lambda, map, filter und reduce

Bei dem 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 45 46 47 48 49 50 51

25.1 lambda

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: >>> anwenden(lambda x: x + 42,range(10)) [42, 43, 44, 45, 46, 47, 48, 49, 50, 51]

Auch solche Anwendungen sind jetzt möglich: >>> for i in [17, 22,42]: ... anwenden(lambda x: x + i, range(10)) ...

291

292

25 lambda, map, filter und reduce

[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 mit Hilfe des Schleifenparameters i drei verschiedene Funktionen kreiert.

25.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 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. Für das folgende Beispiel „vergessen” wir nun „lambda” und „map” und beschränken uns auf eine „konventionelle” Programmierung:

25.2 map

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 entsprechend mit den Werten aus den Listen versorgt. >>> 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, der Parameter y seine Werte aus der Liste b und der Parameter z seine Werte aus der Liste c beziehen.

293

294

25 lambda, map, filter und reduce

25.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]

25.4

reduce

Eingangs des Kapitels erwähnten wir bereits, dass Guido van Rossum lambda, map, filter und reduce nicht mehr in Python 3 wollte. reduce ist diejenige Funktion, die er am wenigsten mag.1 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)

1

„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.”

25.4 reduce

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

Im folgenden Beispiel veranschaulichen wir die Arbeitsweise von reduce an einem konkreten Beispiel: >>> reduce(lambda x,y: x+y, [47,11,42,13]) 113

Im folgenden Diagram sind die Ergebniswerte dargestellt:

295

296

25 lambda, map, filter und reduce

25.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 25 (lambda, map, filter und reduce), Seite 410

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, was eine Liste mit 2-Tupel (Bestellnummer, Gesamtpreis) liefert. Lösung: Lösungen zu Kapitel 25 (lambda, map, filter und reduce), Seite 410

26 26.1

Listen-Abstraktion/ List Comprehension

Die Alternative zu Lambda und Co.

Im vorigen Kapitel über lambda, filter, reduce and 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, eigentlich auch im Deutschen besser als „List BILD 26.1 Comprehension” bekannt, ist eine elegante Methode, Mengen in Durchfahrt verboten Python zu definieren oder zu erzeugen. Die List Comprehension für Lambda und Co. kommt der mathematischen Notation von Mengen sehr nahe. In der Mathematik definiert man die Quadratzahlen der natürlichen Zahlen beispielsweise als {x 2 |x ∈ N} oder die Menge der komplexen ganzen Zahlen {(x, y)|x ∈ Z ∧ y ∈ Z}.

26.2

Einführung in die Listen-Abstraktion

Die Listen-Abstraktion ist eine einfache Methode, um Listen zu erzeugen, und man kann sie als vollständigen Ersatz für den Lambda-Operator sowie die Funktionen map, filter und reduce ansehen. Die Syntax der List Comprehension ist im Allgemeinen leichter verständlich. Allgemein verwendet man sie, 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 nutzt man es, um Unterlisten zu erzeugen, d.h. es werden nur Elemente aus einer anderen Liste, einem Tupel oder allgemein eines sequentiellen Datentyps übernommen, die bestimmte Bedingungen erfüllen. Schauen wir uns zunächst einmal an, mit welchem Aufwand es verbunden ist, auf traditionellem Wege, also ohne Listen-Abstraktion, solche Aufgaben zu lösen. Im Folgenden erzeugen wir die Liste der Quadratzahlen für die Zahlen von 1 bis 9:

298

26 Listen-Abstraktion/List Comprehension

>>> >>> ... ... >>> [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)]

Nur zur Erinnerung: 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]

map liefert einen Iterator auf ein list-Objekt zurück, während die Listen-Abstraktion eine Liste zurückgibt.

26.3

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)]

26.4 Weitere Beispiele

26.4

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] >>>

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). 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", "yellow", "blue" ] >>> things = [ "house", "car", "tree" ] >>> coloured_things = [ (x,y) for x in colours for y in things ] >>> print(coloured_things) [('red', 'house'), ('red', 'car'), ('red', 'tree'), ('green', 'house'), ('green', 'car'), ('green', 'tree'), ('yellow', 'house'), ('yellow', 'car'), ('yellow', 'tree'), ('blue', 'house'), ('blue', 'car'), (' blue', 'tree')] >>>

26.5

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 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 . . . ”

26.6

Anspruchsvolleres Beispiel

Berechnung der Primzahlen bis 100 nach dem Sieb des Eratosthenes: >>> >>> >>> [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.

26.7 Mengen-Abstraktion

26.7

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: >>> >>> >>> >>> >>> {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} >>>

26.8

Rekursive Funktion zur Berechnung der Primzahlen

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 [1] else: p = primes(int(sqrt(n))) no_p = {j for i in p for j in range(i*2, n, i)} p = {x for x in range(2, n) if x not in no_p} return p print(primes(40))

301

302

26 Listen-Abstraktion/List Comprehension

26.9

Generator Comprehension/ 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 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: >>> 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 eigentlich im vorigen Ausdruck ein Klammerpaar 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)) >>> x**2 for x in range(1,11) File "", line 1 x**2 for x in range(1,11) ^ SyntaxError: invalid syntax

26.10 Aufgaben

26.10

Aufgaben

1. Aufgabe: Mittels einer Listen-Abstraktion soll die Menge aller Paare von natürlichen Zahlen kleiner als ein gegebenes n gebildet werden unter den Bedingungen, dass die erste der Zahlen ohne Rest durch die zweite teilbar ist und die beiden Zahlen nicht identisch sind. Lösung: Lösungen zu Kapitel 26 (Listen-Abstraktion/List Comprehension), Seite 411

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. Lösung: Lösungen zu Kapitel 26 (Listen-Abstraktion/List Comprehension), Seite 411

3. Aufgabe: Schreiben Sie eine Funktion, die obige Wahrscheinlichkeit für eine beliebige Summe als Argument berechnet. Lösung: Lösungen zu Kapitel 26 (Listen-Abstraktion/List Comprehension), Seite 412

303

27 27.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 ihn wie eine Funktion vorstellen, die Parameter hat und bei einem Aufruf eine Folge von Werten zurückliefert, also zum Beispiel ein Array oder eine Liste. Allerdings mit dem Unterschied, dass diese Folge von Werten nicht auf einmal zurückgeliefert wird, sondern einen Wert nach dem anderen. Dies hat zwei Vortei- BILD 27.1 Generatoren le: Zum einen spart man Speicherplatz, weil nie eine solche Liste 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 potentiell 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.

27.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 = ["Paris","Berlin","London","Vienna"] >>> for city in cities: ... print("city: " + city) ... city: Paris

306

27 Generatoren und Iteratoren

city: Berlin city: London city: Vienna >>>

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 "", line 1, in StopIteration >>>

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: >>> capitals = { "France":"Paris", "Netherlands":"Amsterdam", "Germany":" Berlin", "Switzerland":"Bern" } >>> for country in capitals: ... print("The capital city of " + country + " is " + capitals[ country]) ... The capital city of Switzerland is Bern The capital city of Netherlands is Amsterdam The capital city of Germany is Berlin The capital city of France is Paris >>> 1

Häufig wird hierfür der englische Begriff „built-in” benutzt!

27.3 Generatoren

27.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","Vienna"] for city in cities: yield city for city in city_generator(): print(city)

Trifft der Programmfluss auf die yield-Anweisung, wird der Generator wie bei einer returnAnweisung 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 Vienna

Die for-Schleife endet also, sobald alle Städte im Generator durchlaufen sind. Wir können allerdings auch eine Endlosschleife im Generator verwenden. def city_generator(): cities = ["Paris","Berlin","London","Vienna"] while True: city = cities.pop(0) yield city cities.append(city)

Speichern wir obige Definition 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() >>> y = city_generator() >>> next(x) 'Paris' >>> next(x) 'Berlin' >>> next(y) 'Paris' >>> next(x) 'London' >>> next(x)

307

308

27 Generatoren und Iteratoren

'Vienna' >>> next(x) 'Paris' >>> next(y) '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=", ") ... Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London , Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin , London, Vienna, Paris, Berlin, London, Vienna,

Um eine solche Endlosschleife zu vermeiden, könnten wir unsere for-Schleife mit einem Zähler und einem break versehen, welchen nach einer bestimmten Anzahl von Iterationen ausgeführt wird. Dies könnte beispielsweise so aussehen: def city_generator(): cities = ["Paris","Berlin","London","Vienna"] while True: city = cities.pop(0) yield city cities.append(city)

if __name__ == "__main__": count = 0 for city in city_generator(): if count == 10: print() break print(city, end=", ") count += 1

27.4 Beispiele

Ein Aufruf liefert dann, wie erwartet, folgende Ausgabe: $ python3 cities_forever.py Paris, Berlin, London, Vienna, Paris, Berlin, London, Vienna, Paris, Berlin, $

27.4 27.4.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 27.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 in range(len(items)): for cc in permutations(items[:i]+items[i+1:]): yield [items[i]]+cc

print("""permutations for (['r','e','d']""") for p in permutations(['r','e','d']):

309

310

27 Generatoren und Iteratoren

print(''.join(p)) print("""permutations of the letters of the string "bin" """) for p in permutations(list("bin")): print(''.join(p))

Obiges Programm liefert folgende Ausgabe: $ python3 permutations_generator.py 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

27.4.2

Variationen und Kombinationen

Werden bei einer Anordnung nicht alle Objekte für die Anordnung ausgewählt, dann spricht man von einer Variation statt von einer Permutation. Formal heißt dies: Aus n Objekten werden k ohne Zurücklegen und ohne Beachtung der Reihenfolge ausgewählt. Die Anzahl der Variationen lässt sich wie folgt berechnen: n! = n · (n − 1) · (n − 2)...(n − k + 1) (n − k)! Spielt die Reihenfolge dabei keine Rolle, so bezeichnet man dies als eine Kombination. Bei der Lotterie 6 aus 49 handelt es sich beispielsweise um eine Kombination bzw. Selektion. Die Anzahl berechnet sich wie folgt: Ã ! Ã ! n n n! n · (n − 1) · (n − 2)...(n − k + 1) = = = k n −k k! · (n − k)! k! Wir können die Kombinationen mit folgendem Generator erzeugen: def selections(items, n): if n==0: yield [] else: for i in range(len(items)): for ss in selections(items, n-1): if (not items[i] in ss):

27.4 Beispiele

yield [items[i]]+ss print("6 aus 49") i = 0 l = range(1,50) for s in selections(l,6): print(s) if i > 20: break else: i = i+1

Obiges Programm liefert folgende Ausgabe:2 $ python selections.py 6 aus 49 [1, 2, 3, 4, 5, 6] [1, 2, 3, 4, 5, 7] [1, 2, 3, 4, 5, 8] [1, 2, 3, 4, 5, 9] [1, 2, 3, 4, 5, 10] [1, 2, 3, 4, 5, 11] [1, 2, 3, 4, 5, 12] [1, 2, 3, 4, 5, 13] [1, 2, 3, 4, 5, 14] [1, 2, 3, 4, 5, 15] [1, 2, 3, 4, 5, 16] [1, 2, 3, 4, 5, 17] [1, 2, 3, 4, 5, 18] [1, 2, 3, 4, 5, 19] [1, 2, 3, 4, 5, 20] [1, 2, 3, 4, 5, 21] [1, 2, 3, 4, 5, 22] [1, 2, 3, 4, 5, 23] [1, 2, 3, 4, 5, 24] [1, 2, 3, 4, 5, 25] [1, 2, 3, 4, 5, 26] [1, 2, 3, 4, 5, 27]

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)

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. >>> import random >>> print(random.sample(range(1,50),6)) [19, 10, 30, 2, 14, 41]

2

Achtung: Es dauert ziemlich lange, bis die ersten Ergebnisse nach der Ausgabe „6 aus 49” kommen! Es handelt sich nicht um eine Endlosschleife!

311

312

27 Generatoren und Iteratoren

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 ,

27.5

Generatoren zähmen

Eine deutlich elegantere Methode besteht jedoch darin, einen Generator zu verwenden, der die ersten n Elemente eines anderen Generators ausgibt. Wir schreiben einen solchen Generator, den wir firstn nennen. Als Parameter erhält er einen Generator g und eine Anzahl n: def firstn(g, n): for i in range(n): yield next(g)

def city_generator(): cities = ["Paris","Berlin","London","Vienna"] while True: city = cities.pop(0) yield city cities.append(city)

if __name__ == "__main__": for city in firstn(city_generator(), 10): print(city, end=", ")

Aber Python wäre nicht Python, wenn es nicht eine noch elegantere Methode gäbe. Man findet sie in dem Modul itertools. Mit der Methode islice kann man sich die Elemente von einer Start- bis zu einer Endposition angeben lassen. Im nächsten Beispiel die ersten fünf Elemente, die von von city_generator() generiert werden: >>> import itertools >>> from cities_forever import city_generator >>> itertools.islice(city_generator(),0,5) >>> cities = itertools.islice(city_generator(),0,5) >>> list(cities) ['Paris', 'Berlin', 'London', 'Vienna', 'Paris']

27.6

send-Methode

Seit Python 2.5 ist es mit Generatoren auch möglich, Werte zu empfangen und nicht nur auszugeben. Die yield-Anweisung wurde verändert, sodass man nun Werte an sie „senden”

27.7 Generator-Ausdrücke

kann. Dies geschieht mittels der send-Methode. Im folgenden Beispiel wird ein Wert, wenn er an den Generator gesendet wird, in der Variablen i gespeichert: i = (yield s[count % 6])

Dadurch wird der Wert der Laufvariablen auf einen neuen Wert gesetzt, in unserem Beispiel auf 4 gesetzt. def abc(): s = "abcdef" count = 0 while True: i = (yield s[count % 6]) if i: count = i else: count += 1 if __name__ == "__main__": x = abc() print(next(x)) print(next(x)) print(x.send(4)) print(next(x)) print(next(x))

Wir erhalten folgende Ausgabe: $ python3 abc_generator_with_send.py a b e f a

27.7

Generator-Ausdrücke

Generator-Ausdrücke sind Listen-Abstraktionen (List Comprehensions) sehr ähnlich. Statt eckigen 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: >>> >>> [1, >>> >>>

squares1 = [i**2 for i in range(1,10)] squares1 4, 9, 16, 25, 36, 49, 64, 81] squares2 = (i**2 for i in range(1,10)) type(squares2)

313

314

27 Generatoren und Iteratoren

>>> list(squares2) [1, 4, 9, 16, 25, 36, 49, 64, 81]

Man sieht, dass list(squares2) und squares1 identisch sind. >>> squares = (i**2 for i in range(1,5)) >>> next(squares) 1 >>> next(squares) 4 >>> next(squares) 9 >>> next(squares) 16 >>> next(squares) Traceback (most recent call last): File "", line 1, in StopIteration

Der obigen Listenausdruck ist identisch mit dem folgenden Generator: def squares(): for i in range(1,5): yield i ** 2

27.8

Aufgaben

1. Aufgabe: Schreiben Sie einen Generator, der die Fibonacci-Zahlen generiert. Zur Erinnerung: 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 . . .

27.8 Aufgaben

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 , . . .

315

28 28.1

Memoisation

Bedeutung und Herkunft des Begriffes

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 BILD 28.1 Gehirn 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 Programmierer programmiert werden, aber einige Programmiersprachen wie auch Python stellen Mechanismen zur Verfügung, welche die Implementierung der Memoisation erleichtern.

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.

318

28 Memoisation

28.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 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, 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

28.3 Memoize in einer Class

def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) fib = memoize(fib) print(fib(40))

Als nächsten Punkt kommen wir zu einem Konzept, was als „Dekorateur” (englisch decorator: Raumgestalter, Dekorateur) bezeichnet wird. Schauen wir uns die folgende Codezeile an: fib = memoize(fib)

In diesem Fall spricht man davon, dass die fib-Funktion durch die memoize-Funktion dekoriert wird.

28.3

Memoize in einer Class

Wir können das Zwischenspeichern der Ergebnisse auch in einer Klasse statt einer Funktion einkapseln. Wir demonstrieren dies im folgenden kleinen Beispiel: class Memoize: 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]

Da wir ein Dictionary verwenden, können wir keine veränderlichen2 Argumente verwenden. Anders ausgedrückt: Die Argumente dürfen nur unveränderlich 3 , also z.B. Tupel, sein.

28.4

Dekorateure in Python

Ein Dekorateur in Python ist ein aufrufbares Python-Objekt, das benutzt wird, um eine Funktion, eine Methode oder eine Klassendefinition zu modifizieren. Das ursprüngliche 2 3

englisch: mutable englisch: immutable

319

320

28 Memoisation

(als das zu modifizierende) Objekt wird dem Dekorateur als Argument zugeführt. Der Dekorateur liefert ein modifiziertes Objekt zurück, also beispielsweise eine modifizierte Funktion, Dieses modifizierte Objekt wird an den Namen gebunden, der modifiziert wird. Python-Dekorateure haben eine ähnliche Syntax wie in Java. Beispiel: Dekorateur für die Memoisation Wir hatten bereits einen Dekorateur benutzt, ohne es so zu nennen. Bei der memoizeFunktion, im Beispiel am Anfang dieses Kapitels unseres Tutorials, handelt es sich um einen Dekorateur. Allerdings hatten wir in diesem Beispiel nicht die spezielle Syntax von Dekorateuren benutzt, also das „@”-Zeichen. Statt der Anweisung fib = memoize(fib)

können wir vereinfachend folgende Anweisung benutzen: @memoize

Allerdings muss diese Zeile unmittelbar vor der dekorierten Funktion stehen. Nun können wir unsere fib-Funktion dekorieren. Mit Dekorateur sieht unsere Fibonacci-Funktion wie folgt aus: 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) #fib = memoize(fib) print(fib(40))

28.5 Überprüfung von Argumenten durch Dekorateure

28.5

Überprüfung von Argumenten durch Dekorateure

Im Abschnitt über rekursive Funktionen hatten wir die Fakultätsfunktion eingeführt. Dabei wollten wir die Funktion so einfach wie möglich halten. Hätten wir die Argumente der Fakultätsfunktion auf Plausibilität geprüft, dann hätten wir die zugrunde liegende Idee verschleiert, und der Kern des Algorithmus wäre nicht mehr so erkennbar gewesen. So darf die Funktion keinesfalls mit negativen Werten oder Fließkommazahlen, also float-Werten, aufgerufen werden. In beiden Fällen kommt es zu einer endlosen Rekursion, die allerdings glücklicherweise durch den endlichen Rekursionsstack von Python mit einem RuntimeError abgebrochen wird: RuntimeError: maximum recursion depth exceeded in comparison

Das folgende Programm benutzt eine Dekorateur-Funktion, um sicherzustellen, dass es sich bei dem verwendeten Argument um eine positive ganze Zahl handelt: 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 faculty(n): if n == 1: return 1 else: return n * faculty(n-1) for i in range(1,10): print(i, faculty(i)) print(faculty(-1))

321

29 29.1

NumPy

Übersicht

NumPy und SciPy sind OpenSource-Erweiterungsmodule für Python, die schnelle vorkompilierte Funktionen für mathematische und numerische Routinen bereitstellen. NumPy („Numeric Python”) dient zur Bearbeitung von großen Arrays und Matrizen mit numerischen Daten. SciPy („Scientific Python”) erweitert die Funktionalität von NumPy mit nützlichen Funktionen wie Minimierung, Regression, Fourier-Transformation und vielen anderen.

BILD 29.1 Visualisierte Matrix

Sowohl NumPy als auch SciPy sind nicht standardmäßig installiert. Bei der Installation sollte man jedoch beachten, dass man NumPy vor SciPy installieren muss. NumPy und SciPy stellen eine kostenlose und freie Alternative zu MATLAB dar. Auch wenn MATLAB über eine große Anzahl von zusätzlichen Tools verfügt, besteht der große Vorteil von NumPy und SciPy darin, dass sie auf Python aufbauen und Python eine modernere und allgemeinere Programmiersprache als MATLAB ist. Anmerkung zum Bild oben rechts: Das Diagramm in dem Bild entspricht der grafischen Veranschaulichung einer Matrix mit 14 Zeilen und 20 Spalten. Es handelt sich dabei um ein sogenanntes „Hinton-Diagramm”. Die Größe eines Quadrats entspricht der Größe eines Wertes in der Matrix. Die Farbe bestimmt, ob ein Wert positiv oder negativ ist. Also zum Beispiel entspricht die Farbe Rot den negativen Werten und die Farbe Grün in unserem Beispiel den positiven Werten. NumPy basiert auf dem Python-Modul Numarray, welches seinerseits eine komplette Neuüberarbeitung eines noch älteren Moduls Numeric ist. NumPy bietet einen großen Zeitvorteil gegenüber Standard-Python. Wir wollen dies im Folgenden demonstrieren. Zuvor gehen wir noch kurz auf die Funktion arange von numpy ein. Wie range erzeugt arange gleichmäßig verteilte Werte. Diese Werte werden in einem speziellen NumPy-Datentyp ndarray abgespeichert:

324

29 NumPy

>>> import numpy as np >>> x = np.arange(1,10) >>> print(x) [1 2 3 4 5 6 7 8 9] >>> type(x) >>> # mit Schrittweite: ... >>> x = np.arange(1,10,3) >>> print(x) [1 4 7] >>>

Nun wollen wir, wie eingangs gesagt, den Zeitvorteil demonstrieren, den NumPy bietet. Wir berechnen dazu die komponentenweise Addition von zwei Vektoren. Im folgenden Programm befinden sich zwei Funktionen. In der Funktion trad_version stellen wir die Vektoren als Listen dar, die wir mit der range-Funktion erzeugen. In der Funktion numpy_version benutzen wir das NumPy-Modul. Als Vektoren benutzen wir eindimensionale Arrays der numpy-Klasse ndarray-Array. Diese erzeugen wir mit der Funktion arange. import time import numpy as np def trad_version(): t1 = time.time() X = range(10000000) Y = range(10000000) Z = [] for i in range(len(X)): Z.append(X[i] + Y[i]) return time.time() - t1 def numpy_version(): t1 = time.time() X = np.arange(10000000) Y = np.arange(10000000) Z = X + Y return time.time() - t1 t_trad = trad_version() t_numpy = numpy_version() print("Benötigte Zeit für traditionelle Lösung:") print(t_trad) print("Benötigte Zeit für Lösung mit NumPy:") print(t_numpy) print("Lösung mit NumPy " + str(t_trad * 100 / t_numpy) + " % schneller")

Ruft man dieses Programm auf, erhält man folgendes beeindruckende Ergebnis:

29.2 Arrays in NumPy

$ python3 numpy_comparison.py Benötigte Zeit für traditionelle Lösung: 6.556201934814453 Benötigte Zeit für Lösung mit NumPy: 0.11806702613830566 Lösung mit NumPy 5552.949158840005 % schneller

29.2

Arrays in NumPy

Arrays sind der zentrale Bestandteil von NumPy. Der wesentliche Unterschied zu Listen in Python besteht darin, dass die Elemente eines Arrays alle vom gleichen Typ sein müssen, normalerweise float oder int. Arrays sind bedeutend effizienter und schneller als Listen in Python. Prinzipiell können Arrays wie Listen angesehen werden, allerdings mit den folgenden Unterschieden: ■



Alle Elemente haben den gleichen Typ, d.h. Integer, Float (Real) oder Komplexe Zahlen. Die Anzahl der Elemente muss a priori bekannt sein, d.h. wenn das Array erzeugt wird. Ein Array kann später nicht mehr verändert werden.

Selbstverständlich sind Arrays in NumPy nicht auf eine Dimension beschränkt. Sie können beliebig dimensional sein. Erzeugung eines Arrays aus einer Liste: >>> import numpy as np >>> x = np.array([42,47,11], int) >>> x array([42, 47, 11]) >>>

Die Methode array wandelt eine Sequenz von Sequenzen – im Beispiel Tupel – in ein zweidimensionales Array: >>> x = np.array( ((11,12,13), (21,22,23), (31,32,33)) ) >>> print(x) [[11 12 13] [21 22 23] [31 32 33]] >>>

Entsprechend wandelt sie Folgen von Folgen von Folgen in ein 3-dimensionales Array: >>> x = np.array( ( ((111,112), (121,122) ), ((211,212),(221,222)) ) ) >>> print(x) [[[111 112] [121 122]] [[211 212]

325

326

29 NumPy

[221 222]]] >>> print(x[1][0][1]) 212 >>> print(x[1][1][1]) 222 >>>

Das Attribut ndim gibt Auskunft über die Anzahl der Dimensionen eines Arrays. Die Methode shape() liefert ein Tupel mit den Array-Dimensionen zurück: >>> x = np.array( ((7,3,0), (7,11,9), (15,3,7),(12,4,8))) >>> x.ndim 2 >>> x.shape (4, 3) >>> x array([[ 7, 3, 0], [ 7, 11, 9], [15, 3, 7], [12, 4, 8]]) >>>

BILD 29.2 Achsen

Ein Besonderheit von NumPy besteht darin, dass man die Verteilung der Elemente auf die Dimensionen auch im Nachhinein wieder verändern kann. Dazu steht der shape-Parameter zur Verfügung. Wie die Achsen eines ndimensionalen Vektorraums auf den shape-Parameter verteilt sind, zeigen die beiden Abbildungen. Die Achsen (axis) eines Arrays beschreiben die Reihenfolge der Indizierungen (axis=0 steht für den ersten Index, axis=1 für den zweiten und so weiter. Die Ausprägung („shape”) eines Arrays bezeichnet ein Tupel mit der Anzahl der Elemente pro Achse (Dimension). Im Fall der obersten Abbildung gilt shape = (6,3), d.h. wir haben sechs Zeilen und drei Spalten. Die Bearbeitung von Arrays erfolgt ähnlich wie bei Listen: >>> x = np.array([42,47,11]) >>> x[:2] array([42, 47]) >>> x = np.array([42,47,11]) >>> x[0] 42 >>> x[:2] array([42, 47]) >>> x[1] = 49 >>> x array([42, 49, 11]) >>>

BILD 29.3 Achsen

29.3 Arrays flach machen

Im Folgenden zeigen wir ein Beispiel eines zweidimensionalen Arrays: >>> x = np.array([[0.314, 0.5,17],[1.4142,0.67, 7]], float) >>> x[0] array([ 0.314, 0.5 , 17. ]) >>> x[1] array([ 1.4142, 0.67 , 7. ]) >>> x[0,2] 17.0 >>>

Auch bei 2-dimensionalen Arrays kann man Slices einsetzen: >>> x[:,0] array([ 0.314 , 1.4142]) >>> x[:,1] array([ 0.5 , 0.67]) >>> x[:,2] array([ 17., 7.]) >>>

Wir wir bereits erfahren haben, besteht ein NumPy-Array im Gegensatz zu normalen Listen in Python nur aus Daten des gleichen numerischen Typs, also Integer oder Float. Mit dem Attribut dtype kann man den Typ der Werte eines Arrays ermitteln. >>> x.dtype dtype('float64')

float64 ist ein numerischer Typ von NumPy, der die Zahlen in doppelter Präzision abspeichert, ähnlich zu dem Typ float in Python.

29.3

Arrays flach machen

Es gibt zwei Methoden, um ein mehrdimensionales Array flach zu machen1 : ■

flatten()



ravel()2

Die Arbeitsweise können wir im folgenden Beispiel an einem dreidimensionalen Array beobachten: import numpy as np x = np.array([ [[ 0, 1], [ 2, 1 2

3], [ 4,

5], [ 6,

7]],

englisch: flatten Dies Wahl des Begriffes ist „verwirrend” (ravelling): Das Wort „ravel” bedeutet im Englischen „verwirren, verwickeln, verheddern”, was die Methode ravel aber nicht tut. Sie tut das Gegenteil, was aber Englisch dem Verb „ravel out” entsprechen würde.

327

328

29 NumPy

[[ 8, 9], [10, 11], [12, 13], [14, 15]], [[16, 17], [18, 19], [20, 21], [22, 23]]]) print("Aufruf von x.flatten():") print(x.flatten()) print("Aufruf von x.flatten():") print(x.ravel()) print("x:") print(x)

Sowohl flatten als auch ravel liefern ein Objekt zurück, verändern aber nicht ihr Objekt. flatten und ravel liefern die gleichen Ergebnisse zurück, aber es gibt Unterschiede in der Implementierung. flatten erzeugt immer eine komplette Kopie der Daten, während im Falle von ravel opportunistisch nur so viel kopiert wird, wie notwendig ist, d.h. es wird versucht, wenn möglich eine View zurückzuliefern.3 Wir sehen bei der Ausgabe des Skripts, dass das ursprüngliche Array x unverändert geblieben ist. $ python3 numpy_arrays.py Aufruf von x.flatten(): [ 0 1 2 3 4 5 6 7 8 Aufruf von x.flatten(): [ 0 1 2 3 4 5 6 7 8 x: [[[ 0 1] [ 2 3] [ 4 5] [ 6 7]]

9 10 11 12 13 14 15 16 17 18 19 20 21 22 23] 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]

[[ 8 9] [10 11] [12 13] [14 15]] [[16 [18 [20 [22

17] 19] 21] 23]]]

29.4

Arrays umdimensionieren

Arrays lassen sich in NumPy beliebig umdimensionieren. Dazu wird die Methode reshape(t) zur Verfügung gestellt. Das Argument von reshape ist ein Tupel, dessen Dimension 3

Verwirrend? ravel hat also seinen Namen zu Recht erhalten – siehe vorige Fußnote.

29.4 Arrays umdimensionieren

die Dimension des veränderten Arrays darstellt, während die Werte dieses Tupels die Verteilung der Elemente über die Dimensionen widerspiegelt. Die Methode reshape() liefert ein Array mit geänderten Dimensionen zurück, ohne dass die Daten des als Parameter übergebenen Arrays verändert werden. Beispiel: >>> import numpy as np >>> x = np.array(range(24)) >>> y = x.reshape((3,4,2)) >>> y array([[[ 0, 1], [ 2, 3], [ 4, 5], [ 6, 7]], [[ 8, 9], [10, 11], [12, 13], [14, 15]], [[16, [18, [20, [22,

17], 19], 21], 23]]])

>>> x array([ 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23]) >>>

8,

9, 10, 11, 12, 13, 14, 15,

Im folgenden Beispiel zeigen wir, wie wir mit Slices geschickt den Rand eines Arrays wegschneiden können. Es werden also die erste Zeile, die letzte Zeile, die erste Spalte und die letzte Spalte entfernt: >>> x = np.array(range(100)) >>> y = x.reshape(10,10) >>> y array([[ 0, 1, 2, 3, 4, 5, [10, 11, 12, 13, 14, 15, [20, 21, 22, 23, 24, 25, [30, 31, 32, 33, 34, 35, [40, 41, 42, 43, 44, 45, [50, 51, 52, 53, 54, 55, [60, 61, 62, 63, 64, 65, [70, 71, 72, 73, 74, 75, [80, 81, 82, 83, 84, 85, [90, 91, 92, 93, 94, 95, >>> y[1:-1,1:-1] array([[11, 12, 13, 14, 15, 16, [21, 22, 23, 24, 25, 26, [31, 32, 33, 34, 35, 36, [41, 42, 43, 44, 45, 46,

6, 16, 26, 36, 46, 56, 66, 76, 86, 96,

7, 17, 27, 37, 47, 57, 67, 77, 87, 97,

8, 18, 28, 38, 48, 58, 68, 78, 88, 98,

17, 27, 37, 47,

18], 28], 38], 48],

9], 19], 29], 39], 49], 59], 69], 79], 89], 99]])

329

330

29 NumPy

[51, [61, [71, [81,

52, 62, 72, 82,

53, 63, 73, 83,

54, 64, 74, 84,

55, 65, 75, 85,

56, 66, 76, 86,

57, 67, 77, 87,

58], 68], 78], 88]])

>>>

29.5

Arrays konkatenieren

Im folgenden Beispiel konkatenieren4 wir drei eindimensionale Arrays. Die Elemente des zweiten Arrays werden an das erste Array angehängt. Anschließend werden die Elemente des dritten Arrays daran angehängt: >>> import numpy as np >>> x = np.array([11,22]) >>> y = np.array([18,7,6]) >>> z = np.array([1,3,5]) >>> np.concatenate((x,y,z)) array([11, 22, 18, 7, 6, 1, >>>

3,

5])

Wenn wir mehrdimensionale Arrays konkatenieren, wird default-mäßig nach der ersten Achse (axis = 0) verknüpft. Man kann allerdings auch den optionalen Parameter axis explizit setzen: >>> x = np.array(range(24)) >>> x = x.reshape((3,4,2)) >>> y = np.array(range(100,124)) >>> y = y.reshape((3,4,2)) >>> z = np.concatenate((x,y)) >>> z array([[[ 0, 1], [ 2, 3], [ 4, 5], [ 6, 7]],

4

[[ 8, [ 10, [ 12, [ 14,

9], 11], 13], 15]],

[[ [ [ [

17], 19], 21], 23]],

16, 18, 20, 22,

Konkatenieren stammt vom lateinischen catena, was Kette bedeutet. Unter Konkatenieren versteht man also „verketten” oder „verknüpfen”.

29.5 Arrays konkatenieren

[[100, [102, [104, [106,

101], 103], 105], 107]],

[[108, [110, [112, [114,

109], 111], 113], 115]],

[[116, [118, [120, [122,

117], 119], 121], 123]]])

>>> >>> z = np.concatenate((x,y),axis = 1) >>> z array([[[ 0, 1], [ 2, 3], [ 4, 5], [ 6, 7], [100, 101], [102, 103], [104, 105], [106, 107]], [[ 8, [ 10, [ 12, [ 14, [108, [110, [112, [114,

9], 11], 13], 15], 109], 111], 113], 115]],

[[ 16, [ 18, [ 20, [ 22, [116, [118, [120, [122,

17], 19], 21], 23], 117], 119], 121], 123]]])

>>> >>> z = np.concatenate((x,y),axis = 2) >>> z array([[[ 0, 1, 100, 101], [ 2, 3, 102, 103], [ 4, 5, 104, 105], [ 6, 7, 106, 107]], [[

8,

9, 108, 109],

331

332

29 NumPy

[ 10, [ 12, [ 14, [[ [ [ [

16, 18, 20, 22,

11, 110, 111], 13, 112, 113], 15, 114, 115]], 17, 19, 21, 23,

116, 118, 120, 122,

117], 119], 121], 123]]])

>>>

29.6

Array, neue Dimension hinzufügen

Neue Dimensionen können einem Array mittels Slicing und np.newaxis hinzugefügt werden. Wir demonstrieren diese Technik im folgenden Beispiel: >>> x = np.array([2,5,18,14,4]) >>> x array([ 2, 5, 18, 14, 4]) >>> y = x[:, np.newaxis] >>> y array([[ 2], [ 5], [18], [14], [ 4]]) >>>

29.7

Array mit Nullen und Einsen initialisieren

Es gibt zwei Möglichkeiten, Arrays mit Nullen oder Einsen zu initialisieren. Die Methode ones(t) benötigt ein Tupel t mit der „shape” des zu schaffenden Arrays und füllt das Array entsprechend mit Einsen. Bei Default wird das Array mit Einsen vom Typ float gefüllt. Wenn man Einsen vom Typ Integer braucht, kann man den optionalen Parameter dtype auf int setzen: >>> np.ones((2,3)) array([[ 1., 1., 1.], [ 1., 1., 1.]]) >>> np.ones((3,4),dtype=int) array([[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]) >>>

29.8 Matrizenarithmetik

Was wir über die Methode ones() gesagt haben, gilt analog für die Methode zeros(), wie wir im folgenden Beispiel sehen: >>> np.zeros((2,4)) array([[ 0., 0., 0., [ 0., 0., 0., >>>

0.], 0.]])

Es gibt noch eine weitere interessante Möglichkeit, eine Matrix mit Einsen oder Nullen zu füllen, wenn das Array die „shape” eines anderen existierenden Arrays „a” haben soll. Für diesen Zweck stellt NumPy die Methoden ones_like(a) und zeros_like(a) zur Verfügung. >>> x = np.array([2,5,18,14,4]) >>> np.ones_like(x) array([1, 1, 1, 1, 1], dtype=int32) >>> np.zeros_like(x) array([0, 0, 0, 0, 0]) >>>

29.8

Matrizenarithmetik

Bisher haben wir gesehen, wie man Arrays erzeugen und ändern kann. In diesem Abschnitt wollen wir zeigen, wie wir in Python mittels NumPy ohne Aufwand und effizient Matrizenarithmetik betreiben können, also ■

Matrizenaddition



Matrizensubtraktion



Matrizenmultiplikation



Skalarprodukt



Kreuzprodukt



und weitere arithmetische Funktionen auf Matrizen

Die arithmetischen Standardoperationen +, -, *, /, ** und % werden elementweise angewendet, d.h. die Arrays müssen die gleichen Größen haben, damit man diese Operatoren anwenden kann. >>> import numpy as np >>> x = np.array([1,5,2]) >>> y = np.array([7,4,1]) >>> x + y array([8, 9, 3], dtype=int32) >>> x * y array([ 7, 20, 2], dtype=int32) >>> x - y array([-6, 1, 1], dtype=int32) >>> x / y array([ 0.14285714, 1.25 ,

2.

])

333

334

29 NumPy

>>> x % y array([1, 1, 0], dtype=int32) >>>

29.9

Vektoraddition und Vektorsubtraktion

Vielen dürfte die Vektoraddition aus dem Physikunterricht der Schule bekannt sein. Man benutzt die Vektoraddition, um die Gesamtkraft zu berechnen, wenn verschiedene Einzelkräfte mit unterschiedlichen Ausrichtungen auf einen als punktförmig angenommenen Körper einwirken. Wenn man wissen will, welche Kraft insgesamt BILD 29.4 Achsen auf den Körper ausgeübt wird, muss man eine sogenannte Vektoraddition durchführen. Grafisch wird eine Vektoraddition realisiert, indem man durch Parallelverschiebung an die Spitze des ersten Vektors, also die Stelle, an der sich der Pfeil befindet, den Anfang des zweiten Vektors ansetzt. Rechnerisch kann man mit der Vektoraddition die Gesamtverschiebung der Vektoren ermitteln, indem man die jeweiligen Komponenten miteinander addiert. >>> import numpy as np >>> x = np.array([3,2]) >>> y = np.array([5,1]) >>> z = x + y >>> z array([8, 3], dtype=int32) >>>

Einen Vektor y von einem Vektor x zu subtrahieren, ist das Gleiche, als wenn man das Negative von y zu dem Vektor x addiert. Es gilt also: x - y = x + (-y). Geometrisch kann man die Subtraktion eines Vektors wie folgt durchführen: Um y von x zu subtrahieren, platzieren wir die End- BILD 29.5 Achsen punkte von x und y auf den gleichen Punkt. Dann zeichnen wir einen Pfeil von der Spitze von y zu der Spitze von x. Dieser „Pfeil” ist ein Repräsentant des Vektors x - y, siehe Bild auf der rechten Seite. Mathematisch gesehen wird die Vektorsubtraktion durch eine komponentenweise Subtraktion der einzelnen Komponenten durchgeführt. >>> x = np.array([3,2]) >>> y = np.array([5,1])

29.9 Vektoraddition und Vektorsubtraktion

>>> z = x - y >>> z array([-2, 1], dtype=int32) >>>

Das Skalarprodukt wird häufig auch als inneres Produkt oder Punktprodukt bezeichnet. Mathematisch stellt das Skalarprodukt eine algebraische Operation dar, die zwei Koordinationvektoren gleicher Größe als Argument nimmt und eine einfache Zahl zurückliefert. Das Ergebnis wird berechnet, indem die Komponenten mit gleichem Index multipliziert und die so erhaltenen Produkte anschließend addiert werden. Der Name Punktprodukt (oder englisch „dot product”) stammt übrigens davon, dass der „.” häufig als Operatorzeichen für diese Operation genutzt wird. Der Name Skalarprodukt stellt mehr den Aspekt in den Vordergrund, dass das Ergebnis der Operation ein Skalar ist. Definition des Skalarprodukts: ⃗ a ·⃗ b =| ⃗ a |·|⃗ a | cos ∠(⃗ a ,⃗ b) Aus der Definition des Skalarprodukts können wir ersehen, dass es benutzt werden kann, um den Winkel zwischen zwei Vektoren zu ermitteln: Berechnung des Skalarprodukts: ⃗ a ·⃗ b = a1 · b1 + a2 · b2 + a3 · b3 Es ist leicht einzusehen, dass man den Betrag, also die Länge eines Vektors, auch über das Skalarprodukt definieren bzw. berechnen kann: q p p |⃗ a |= ⃗ a ·⃗ a = a 1 · a 1 + a 2 · a 2 + a 3 · a 3 = a 12 + a 22 + a 32 Wir wollen nun mit konkreten Vektoren mit dem Skalarprodukt in NumPy „herumspielen”: Wir benutzen zwei Beispielvektoren ⃗ x und ⃗ y:    2 −2 ⃗ x =  7  und ⃗ y = 3  −3 4 

Der Absolutbetrag von ⃗ x berechnet sich wie folgt: v u    u 2 −2 u p |⃗ x |= ⃗ a ·⃗ a =u t 7  ·  3  −3 4 Dies entspricht in NumPy-Notation: numpy.sqrt(np.dot(x,x))

Der Winkel zwischen ⃗ x und ⃗ y berechnet sich als: cos ∠(⃗ x ,⃗ y) =

⃗ x ·⃗ y ⃗ | x |·|⃗ y|

335

336

29 NumPy

Im unten stehenden Skript wird dies wie folgt berechnet: cos_xy = np.dot(x,y) / ( x_abs * y_abs)

In der folgenden interaktiven Python-Shell wenden wir die obigen Überlegungen an: >>> import numpy as np >>> x = np.array([2,7,-3]) >>> x_abs = np.sqrt(np.dot(x,x)) >>> x_abs 7.8740078740118111 >>> y = np.array([-2,3,4]) >>> y_abs = np.sqrt(np.dot(y,y)) >>> y_abs 5.3851648071345037 >>> cos_xy = np.dot(x,y) / ( x_abs * y_abs) >>> cos_xy 0.11791665765914455 >>> angle_xy = np.arccos(cos_xy) >>> angle_xy # Winkel in Bodenmaß 1.4526046862352173 >>> angle_xy * 360 / 2 / np.pi # Winkel in Gradmaß 83.228117822203146 >>>

29.10

Matrix-Klasse

Die Matrix-Klasse ist eine Unterklasse der NumPy-Arrays (ndarray). Ein Matrix-Objekt erbt alle Attribute und Methoden von ndarry. Ein Unterschied besteht darin, dass die NumPyMatrizen streng 2-dimensional sind, während NumPy-Arrays von beliebiger Dimension sein können, also n-dimensional. Der größte Vorteil von Matrizen liegt darin, dass sie eine komfortable Notation für verschiedene Matrizenoperationen wie z.B. die Matrix-Multiplikation zur Verfügung stellen. Wenn X und Y zwei Matrizen sind, dann definiert X * Y die Matrix-Multiplikation. Wenn allerdings X und Y zwei ndarrays sind, dann definiert X * Y eine komponentenweise Multiplikation. >>> x = np.array( ((2,3), (3, 5)) ) >>> y = np.array( ((1,2), (5, -1)) ) >>> x * y array([[ 2, 6], [15, -5]]) >>> x = np.matrix( ((2,3), (3, 5)) ) >>> y = np.matrix( ((1,2), (5, -1)) ) >>> x * y matrix([[17, 1], [28, 1]])

29.10 Matrix-Klasse

29.10.1

Matrix-Produkt

Das Produkt einer (l x m)-Matrix A = (a i j )i =1...l , j =1..m und einer (m x n)-Matrix B = (b i j )i =1...m, j =1..n ist eine Matrix C = (c i j )i =1...l , j =1..n , die wie folgt berechnet wird: ci j =

m X

ai k · bk j

k=1

Die folgende Grafik verdeutlicht dieses Verfahren.

Es gibt mehrere Möglichkeiten, eine Matrizenmultiplikation durchzuführen: ■

Wir definieren zwei Matrizen x und y als zweidimensionale Arrays und bilden das Kreuzprodukt, wodurch eine Matrizenmultiplikation durchführt wird: >>> import numpy as np >>> x = np.array( ((2,3), (3, 5)) ) >>> y = np.array( ((1,2), (5, -1)) ) >>> np.dot(x,y) array([[17, 1], [28, 1]])



Wie eben, aber statt des Kreuzprodukts casten wir x und y in Matrix-Objekte: >>> import numpy as np >>> x = np.array( ((2,3), (3, 5)) ) >>> y = np.array( ((1,2), (5, -1)) ) >>> np.mat(x) * np.mat(y) matrix([[17, 1], [28, 1]]) >>> x = np.mat("2 3; 3 5") >>> y = np.mat("1 2; 5 -1") >>> np.mat(x) * np.mat(y) matrix([[17, 1], [28, 1]]) >>>

337

338

29 NumPy



Wir definieren x und y sofort als Matrix-Objekte. >>> import numpy as np >>> x = np.mat("2 3; 3 5") >>> y = np.mat("1 2; 5 -1") >>> np.mat(x) * np.mat(y) matrix([[17, 1], [28, 1]]) >>>

mat ist übrigens eine Abkürzung für matrix. Wir hätten also auch ebensogut Folgendes schreiben können: >>> import numpy as np >>> x = np.matrix("2 3; 3 5") >>> y = np.matrix("1 2; 5 -1") >>> np.matrix(x) * np.matrix(y) matrix([[17, 1], [28, 1]]) >>>

29.10.2

Eine einfache praktische Anwendung

In dem folgenden praktischen Beispiel kommen wir auf die Schokoladenseite des Lebens zu sprechen. Nehmen wir an, wir haben vier Personen, die wir Lukas, Mia, Leon und Hannah taufen. Jeder von ihnen hat Pralinen der Marken A, B und C gekauft. Lukas kaufte 100 g der Marke A, 175 g der Marke B und 210 von C. Mia wählte 90 g von A, 160 g von B und 150 g von C. Leon kaufte 200 g von A, 50 von B und 100 g von C. Hannah mag allem Anschein nach die Sorte B nicht, weil sie keine Pralinen dieser Sorte gekauft hat. Dafür scheint sie ein echter Fan der Sorte C zu sein, weil sie davon gleich 310 g gekauft hat. Außerdem kaufte sie noch 120 g der Marke A.

BILD 29.6 Pralinen

Nun wollen wir natürlich wissen, wie viel die einzelnen Sorten kosten: ■

A kostet 2.98 pro 100 g,



B kostet 3.90 und



C nur 1.99 Euro.

Wenn wir nun berechnen wollen, wie viel jeder von ihnen zu zahlen hat, können wir NumPy und die Matrizenmultiplikation nutzen:

29.11 Inverse Matrix

>>> PersAnz = np.array ([[100,175,210],[90,160,150],[200,50,100],[120,0,310]]) >>> Preis_per_100_g = np.array([2.98,3.90,1.99]) >>> Preis_in_Cent = np.dot(PersAnz,Preis_per_100_g) >>> Preis_in_ Euro = Preis_in_Cent / np.array([100,100,100,100]) >>> Preis_in_Euro array([ 13.984, 11.907, 9.9 , 9.745]) >>>

Das bedeutet, dass Lukas 13.98 Euro, Mia 11.97 Euro, Leon 9.90 Euro und Hannah 9.75 Euro zu zahlen haben.

29.11

Inverse Matrix

Mit der Funktion inv (numpy.linalg.inv) kann man eine Matrix invertieren. Hat man die inverse Matrix A_inv zu einer quadratischen Matrix A berechnet, dann gilt, dass die Matrizenmultiplikation von A und A_inv die Einheitsmatrix ergibt, also: np.dot(A, A_inv) = np.dot(A_inv, A) = E Hierbei ist E die Einheitsmatrix, also die Matrix, die auf der Diagonalen nur Einsen hat und ansonsten Nullen. Die Einheitsmatrix lässt sich übrigens mit der Funktion eye von numpy generieren: >>> E = np.eye(3) >>> E array([[ 1., 0., [ 0., 1., [ 0., 0., >>>

0.], 0.], 1.]])

Im folgenden Beispiel entspricht E der Einheitsmatrix, wenn man von numerischen Ungenauigkeiten absieht: >>> A = np.array([[3, 5, 1], [1,-1,2], [2,0,-2]]) >>> A_inv = np.linalg.inv(A) >>> E = np.dot(A,A_inv) >>> E array([[ 1.00000000e+00, -5.55111512e-17, 2.77555756e-17], [ -5.55111512e-17, 1.00000000e+00, 1.11022302e-16], [ -5.55111512e-17, 0.00000000e+00, 1.00000000e+00]]) >>>

339

340

29 NumPy

29.12

Kreuzprodukt / Vektorprodukt

Nun müssen wir wieder den Konsum der leckeren Pralinen einstellen und uns einem kalorienärmeren, mathematischeren Thema zuwenden, dem Kreuzprodukt oder Vektorprodukt. Das Kreuz- oder Vektorprodukt ist eine binäre Operation im dreidimensionalen Raum. Das Kreuzprodukt zweier Vektoren ⃗ a und ⃗ b wird mit ⃗ a ×⃗ b bezeichnet. Das Ergebnis ist ein Vektor, der senkrecht zu den Vektoren ⃗ a und ⃗ b steht, die multipliziert werden. Senkrecht im Sinne eines Rechtssystems, d.h. die beiden Vektoren ⃗ a und ⃗ b sowie der Vektor ⃗ a ×⃗ b bilden ein Rechtssystem. Sie verhalten sich wie Daumen, Zeigefinger und Mittelfinger der rechten Hand. Dies wird manchmal auch als Drei-Finger-Regel bezeichnet. Das Kreuzprodukt ist definiert als:

BILD 29.7 Kreuzprodukt

⃗ a ×⃗ b = (| ⃗ a | · |⃗ b | · sin ∠(⃗ a ,⃗ b)) ·⃗ n wobei der Vektor ⃗ n derjenige zu ⃗ a und ⃗ b senkrechte Einheitsvektor ist, der diese zu einem Rechtssystem ergänzt. ⃗ n steht senkrecht auf der von ⃗ a und ⃗ b aufgespannten Ebene. Es gibt zwei Vektoren, die diese Eigenschaft erfüllen. Der richtige ist der, der mit der Drei- FingerRegel ermittelt werden kann. Falls einer der Vektoren, die multipliziert werden, Null ist oder wenn die beiden Vektoren parallel sind, dann ist ihr Kreuzprodukt gleich Null. Die Länge des Ergebnisvektors entspricht der Fläche des von den beiden multiplizierten Vektoren aufgespannten Parallelogramms. Falls die beiden Vektoren senkrecht zueinander stehen, erhalten wir ein Rechteck. >>> import numpy as np >>> x = np.array([0,0,1]) >>> y = np.array([0,1,0]) >>> np.cross(x,y) array([-1, 0, 0]) >>> np.cross(y,x) array([1, 0, 0]) >>>

29.13

Lineare Gleichungssysteme

NumPy besitzt viele weitere Funktionen und Methoden zur Berechnungen von weiteren Anwendungen aus der Linearen Algebra. Dazu bietet sich vor allen Dingen das Paket linalg an. linalg bietet beispielsweise die Funktion solve zum Lösen von linearen Gleichungssystemen.

29.13 Lineare Gleichungssysteme

Betrachten wir das folgende Gleichungssystem: 4x 1 − 2x 2 + 7x 3 = −7 −x 1 + 5x 2 + 3x 3 + 2x 4 = 12 −x 1 + 2x 2 + 5x 3 − 2x 4 = −8 x 1 + 2x 2 + 3x 3 + 4x 4 = 14 Ein solches Gleichungssystem kann man unter Benutzung der Matrix-Vektor-Multiplikation wie folgt schreiben: A ·⃗ x =⃗ b wobei A eine Matrix ist, die sogenannte Koeffizientenmatrix. Hier eine 4 x 4 Koeffizientenmatrix, wie es unserem Beispiel entspricht:   a1 1 a1 2 a1 3 a1 4  a 1 a 2 a 3 a 4  2 2 2 2     a 3 1 a 3 2 a 3 3 a 3 4 a4 1 a4 2 a4 3 a4 4 Mit den Werten aus unserem Beispielgleichungssystem sieht A dann wie folgt aus:   4 −2 7 0 −1 5 3 2     −1 2 5 −2 1 2 3 4 Der Vektor ⃗ b entspricht den Werten auf den rechten Seiten der Gleichungssysteme:   −7  12    ⃗ b=  −8 14 Damit sieht unser Gleichungssystem wie folgt aus:       4 −2 7 0 x1 −7 −1     5 3 2   x 2   12   ·  =   −1 2 5 −2 x 3  −8 1 2 3 4 x4 14 Nun kommen wir endlich zur Lösung dieses Gleichungssystems in Python: >>> import numpy as np, numpy.linalg >>> A = np.array([[4,-2,7,0],[-1,5,3,2],[-1,2,5,-2],[1,2,3,4]]) >>> b = np.array([-7, 12, -8, 14]) >>> x = np.linalg.solve(A,b) >>> x array([ 1., 2., -1., 3.]) >>>

341

342

29 NumPy

29.14

Polynome

NumPy eignet sich auch hervorragend zum Umgang mit Polynomen und stellt hierfür auch ein Paket polynomial zur Verfügung. Zur Darstellung von Polynomen f (x) =

0 X

a i · x i = a n · x n + a n−1 · x n−1 + . . . a 2 · x 2 + a 1 · x + a 0

i =n

dient die Klasse poly1d.5 . Beispiel: Wir wollen das folgende Beispielpolynom in Python implementieren: f (x) = 3 · x 2 − 2 · x + 5 Wir sehen in der folgenden interaktiven Python-Shell, dass poly1d ein ndarray mit den Koeffizienten der Funktion erwartet. Dabei müssen die Koeffizienten in absteigender Reihenfolge angegeben werden, d.h. die Koeffizienten von den höchsten Exponenten zuerst. >>> >>> >>> 5 >>> 45

import numpy as np f = np.poly1d([3,-2,5]) f(0) f(4)

Polynome kann man bekanntlich auch in Linearfaktoren über die Nullstellen zerlegen. Dann kann man eine Funktion auch in Produktschreibweise schreiben. Seien 3, 2 und -1 die Nullstellen von f. Dann kann man f schreiben als f (x) = (x − 3)(x − 2)(x + 1) In NumPy definiert man f ebenfalls mit poly1d, aber man muss den zweiten Parameter auf True setzen. Dann erwartet poly1d keine Koeffizienten, sondern ein ndarry mit Nullstellen. >>> import numpy as np >>> f = np.poly1d([3,2,-1],True) >>> f poly1d([ 1, -4, 1, 6]) >>> f(3) 0 >>> f(2) 0 >>> f(-1) 0 >>> 5

1d steht für 1-dimensional.

29.15 Aufgaben

Von einem Polynom kann man sich auch die Nullstellen mit der Funktion roots6 bestimmen lassen: Beispiel: Wir berechnen die Nullstellen für das Polynom: f (x) = x 3 − x 2 − 10 · x − 8 Die Berechnung in NumPy sieht wie folgt aus: >>> import numpy as np >>> f = np.poly1d([ 1, >>> np.roots(f) array([ 4., -2., -1.]) >>>

-1, -10,

-8])

Mit numpy lassen sich auch die Ableitung und unbestimmte Integrale von Polynomen bestimmen. Dazu gibt es die Methoden deriv()7 und integ(): >>> import numpy as np >>> f = np.poly1d([3,-2,-1]) >>> f.deriv() poly1d([ 6, -2], dtype=int32) >>> f.integ() poly1d([ 1., -1., -1., 0.]) >>>

29.15

Aufgaben

1. Aufgabe: Prüfen Sie, ob die folgenden Vektoren orthogonal (senkrecht) zueinander sind.     2 −2   1. ⃗ x = 7 und ⃗ y = 1  −3 1     2 −5 2. ⃗ x =  3  und ⃗ y = 4  −1 2 Lösung: Lösungen zu Kapitel 29 (NumPy), Seite 415

6 7

„roots” bezeichnet im Englischen auch die Nullstellen eines Polynoms Englisch: derivation

343

344

29 NumPy

2. Aufgabe: Bestimmen Sie den Winkel zwischen den beiden Vektoren:     5 −3   ⃗ x = 9 und ⃗ y = 0  −5 6 Lösung: Lösungen zu Kapitel 29 (NumPy), Seite 415

3. Aufgabe: Lösen Sie das folgende Gleichungssystem: −2x 1 − 2x 2 + 2x 3 + x 4 = −5 −3x 1 + 4x 2 + −2x 4 = −1 −x 1 + 2x 2 + 3x 3 + 2x 4 = 0 3x 1 + 2x 2 + 3x 3 + 2x 4 = 4

Lösung: Lösungen zu Kapitel 29 (NumPy), Seite 416

4. Aufgabe: Berechnen Sie die Nullstellen für das folgende Polynom: f (x) = 4 · x 3 + 12 · x 2 − 7 · x − 5

Lösung: Lösungen zu Kapitel 29 (NumPy), Seite 416

5. Aufgabe: Bestimmen Sie für das folgende Polynom das unbestimmte Integral und die Ableitung: f (x) = 2 · x 3 − 3 · x 2 − 2 · x − 1

Lösung: Lösungen zu Kapitel 29 (NumPy), Seite 416

TEIL III Umfassende Beispiele

30 30.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 30.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 Dreien. — „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.”

348

30 Bruchklasse

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!

30.2

Zurück in die Gegenwart

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 30.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 12 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)

Wie sieht es aus, wenn wir einen dieser Brüche versuchen auszudrucken? Wenn wir obiges Programm unter brueche.py gespeichert haben, können wir es direkt auf der interaktiven Python-Shell testen: $ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57)

30.2 Zurück in die Gegenwart

[GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from brueche import Bruch >>> x = Bruch(2,3) >>> print(x)

Positiv ist, dass wir einen Bruch ausgeben können, aber leider erhalten wir kein Ergebnis, dass wir uns wünschen würden. Für die Ausgabe der Variable x würden wir eher „2/3” erwarten. Dies lässt sich ziemlich einfach realisieren. Dazu erweitern wir unsere Klasse um eine Methode __str__. def __str__(self): return str(self.zaehler)+'/'+str(self.nenner)

Die Methode stellt eine Wandlung des Klassenobjekts, in unserem Fall also ein Bruch, in die String-Repräsentation des Objekts dar. Wird print mit einem Klassenobjekt aufgerufen, wendet Python automatisch die __str__-Methode auf das Objekt an und druckt den Ergebnisstring aus: $ 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 brueche import Bruch >>> x = Bruch(2,3) >>> 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. 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.

349

350

30 Bruchklasse

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. Im Folgenden führen wir die gleichen Berechnungen mit Variablen aus: >>> a = 170 >>> b = 391 >>> a, b = b, a >>> print(a,b) 391 170 >>> a, b = b, a >>> print(a,b) 170 51 >>> a, b = b, a >>> print(a,b) 51 17 >>> a, b = b, a >>> print(a,b) 17 0

% b

% b

% b

% b

Wir können unser Programm nun um eine Klassenmethode 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. Unsere Klasse sieht nun wie folgt aus: class Bruch(object): def __init__(self,z,n): self.zaehler = z self.nenner = n self.kuerze() def __str__(self): return str(self.zaehler)+'/'+str(self.nenner) def ggT(cls,a,b): while b != 0: a,b = b,a%b return a def kuerze(self): g = self.ggT(self.zaehler, self.nenner)

30.3 Rechenregeln

self.zaehler = int(self.zaehler/g) self.nenner = int(self.nenner/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. Bis jetzt fehlen aber noch die wichtigsten Methode in unserer Bruchklasse BIS HIER. Wir können Brüche definieren und ausgeben, aber wir können keine Operationen auf den Brüchen ausführen.

30.3

Rechenregeln

Wie auch beim Rechnen mit natürlichen Zahlen1 oder ganzen Zahlen2 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.

30.3.1

Multiplikation von Brüchen

Als Erstes zeigen wir, wie man Brüche multipliziert, da dies am einfachsten ist: 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: 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 1 2

N = 1, 2, 3, ... Z = −3, −2. − 1, 01, 2, 3, ...

351

352

30 Bruchklasse

Nun haben wissen wir genug, um unsere Bruchklasse um eine Multiplikation zu erweitern. Dazu müssen wir die Methode __mul__ bereitstellen3 : def __mul__(self,other): p = Bruch(self.zaehler * other.zaehler, self.nenner * other.nenner) p.kuerze() return p

In der Methode __mul__ erzeugen wir einen neuen Bruch P. Dieser wird mit der Initialisierung sofort auf das Ergebnis der Multiplikation gesetzt. Da der Bruch noch in nicht vollständig gekürzter Form vorliegen kann, rufen wir nach der Initialisierung noch die Methode kuerze mit „p.kuerze()” auf. Wir können nun zwei Brüche multiplizieren, wie wir im folgenden Beispiel sehen: $ 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 brueche import Bruch >>> x = Bruch(2,3) >>> y = Bruch(4,15) >>> print(x * y) 8/45 >>> 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”.

30.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 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__4 : def __truediv__(self,other): p = Bruch(self.zaehler * other.nenner, self.nenner * other.zaehler) p.kuerze() return p 3 4

Siehe Kapitel Operator-Überladung, Seite 187 Vor Python 3 hieß diese Methode __div__.

30.3 Rechenregeln

Im Folgenden zeigen wir, wie man Brüche in unserer Bruchklasse dividieren kann: $ 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 brueche import Bruch >>> x = Bruch(2,3) >>> y = Bruch(15,4) >>> print(x/y) 8/45

30.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. Sind die Nenner gleich, können die Zähler einfach addiert werden. Beispiel: 2 3 2 4 3 3 2 · 4 + 3 · 3 17 + = · + · = = 3 4 3 4 4 3 3·4 12 Nun können wir die Python-Methode für die Addition (__add__) angeben: def __add__(self,other): s = fraction(self.zaehler*other.nenner+other.zaehler * self. nenner, self.nenner*other.nenner) s.reduce() return s

30.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. Beispiel: 3 2 3 3 2 4 3·3−2·4 1 − = · + · = = 4 3 4 3 3 4 3·4 12 Die Python-Methode für die Subtraktion (__sub__) kann fast gleich wie die Additionsmethode geschrieben werden: def __sub__(self,other): s = Bruch(self.zaehler*other.nenner+other.zaehler * self.nenner,

353

354

30 Bruchklasse

self.nenner*other.nenner) s.kuerze() return s

Im Folgenden wollen wir noch die Methoden für die Vergleichsoperatoren von Brüchen angeben, also für „==”, „!=”, „>”, „>=”, „= other.zaehler * self.nenner < other.zaehler * self.nenner other.zaehler * self.nenner >= other.zaehler * self.nenner < other.zaehler * self.nenner >> from combinatorics import all_colours >>> all_colours(["a","b","c","d"],3) >>> list(all_colours(["a","b","c","d"],3)) [['d', 'a', 'b'], ['d', 'a', 'c'], ['d', 'b', 'a'], ['d', ', 'c', 'a'], ['d', 'c', 'b'], ['a', 'd', 'b'], ['a', ', 'b', 'd'], ['a', 'b', 'c'], ['a', 'c', 'd'], ['a', ', 'd', 'a'], ['b', 'd', 'c'], ['b', 'a', 'd'], ['b', ', 'c', 'd'], ['b', 'c', 'a'], ['c', 'd', 'a'], ['c', ', 'a', 'd'], ['c', 'a', 'b'], ['c', 'b', 'd'], ['c', >>> list(all_colours("abcd",3)) [['c', 'd', 'b'], ['c', 'd', 'a'], ['c', 'b', 'd'], ['c', ', 'a', 'd'], ['c', 'a', 'b'], ['d', 'c', 'b'], ['d', ', 'b', 'c'], ['d', 'b', 'a'], ['d', 'a', 'c'], ['d', ', 'c', 'd'], ['b', 'c', 'a'], ['b', 'd', 'c'], ['b', ', 'a', 'c'], ['b', 'a', 'd'], ['a', 'c', 'd'], ['a', ', 'd', 'c'], ['a', 'd', 'b'], ['a', 'b', 'c'], ['a',

information.

'b', 'd', 'c', 'a', 'd', 'b',

'c'], 'c'], 'b'], 'c'], 'b'], 'a']]

['d ['a ['b ['b ['c

'b', 'c', 'a', 'd', 'c', 'b',

'a'], 'a'], 'b'], 'a'], 'b'], 'd']]

['c ['d ['b ['b ['a

Der Unterschied zu unserem k_permutations-Generator besteht darin, dass wir immer mit einer Zufallspermutation starten: >>> list(all_colours("abc",2)) [['a', 'c'], ['a', 'b'], ['c', >>> list(all_colours("abc",2)) [['a', 'b'], ['a', 'c'], ['b', >>> list(all_colours("abc",2)) [['c', 'b'], ['c', 'a'], ['b', >>> list(all_colours("abc",2)) [['c', 'a'], ['c', 'b'], ['a',

31.4

'a'], ['c', 'b'], ['b', 'a'], ['b', 'c']] 'a'], ['b', 'c'], ['c', 'a'], ['c', 'b']] 'c'], ['b', 'a'], ['a', 'c'], ['a', 'b']] 'c'], ['a', 'b'], ['b', 'c'], ['b', 'a']]

Mastermind in Python

Nun können wir mit der Implementierung unseres eigentlichen Mastermind-Programms beginnen. Unser Programm soll den n-stelligen Farbcode herausfinden, den ein menschlicher Spieler sich aus einer Menge Farben, also zum Beispiel colours = ["red","green","blue","yellow","orange","pink"]

ausdenkt. Die Anzahl der Stellen legen wir in number_of_positions fest. Also zum Beispiel den Wert 4: number_of_positions = 4

Wir erzeugen eine leere Liste, die später die Versuche und deren Bewertungen enthalten wird: guesses = []

31.4 Mastermind in Python

Diese Liste besteht aus 2-Tupeln, z.B. [(['blue', 'orange', 'red', 'yellow'], (1, 3)), (['blue', 'red', 'yellow ', 'orange'], (1, 3))]

Das erste Element eines solchen 2-Tupels entspricht einer Liste mit einer 4-stelligen Farbauswahl, also zum Beispiel: ['blue', 'orange', 'red', 'yellow']

und das zweite Element entspricht einer Bewertung, also einem 2-Tupel mit einer Zahl als erster Komponente für die Anzahl der schwarzen Stifte (also richtige Farben an der richtigen Position) und der Anzahl der weißen Stifte (also der richtigen Farben an der falschen Stelle): (1, 3)

Obige Bewertung bedeutet also: Eine Farbe war richtig und stand auch an der richtigen Stelle. Drei andere Farben waren zwar richtig, standen aber an der falschen Position. Als Nächstes erzeugen wir einen Iterator für alle möglichen k-Permutationen der Farben „colours”: permutation_iterator = all_colours(colours, number_of_positions)

Dann erzeugen wir eine erste Farbauswahl, also vier Zufallsfarben aus der durch „colours” definierten Liste von möglichen Farben: current_colour_choice = next(permutation_iterator)

Mit dieser Liste erzeugen wir nun einen „Versuch”, d.h. ein Tupel bestehend aus dieser Farbliste und einer Bewertung. Wir setzen die Bewertung auf (0,0), weil wir nichts Näheres wissen und weil dieses Tupel für den folgenden Ablauf gesetzt sein muss: new_guess = (current_colour_choice, (0,0) )

Die while-Schleife erzeugt Versuche, die nicht im Widerspruch zu vorigen Versuchen stehen. Als Endekriterium dient die Bewertung, die die Funktion new_evaluation für eine Farbauswahl zurückliefert: Die Schleife endet, wenn ■



das Programm die Farbauswahl erfolgreich geraten hat, also das Tupel (number_of_ positions, 0), in unserem Fall (4,0), zurückgeliefert wird oder die Antworten des menschlichen Mitspielers widersprüchlich waren, d.h. es wird das Tupel (-1,0) zurückgeliefert.

Schauen wir uns nun die Funktion new_evaluation genauer an: def new_evaluation(current_colour_choice): rightly_positioned, permutated = get_evaluation(current_colour_choice) if rightly_positioned == number_of_positions: return(current_colour_choice, (rightly_positioned, permutated))

361

362

31 Mastermind

if not answer_ok((rightly_positioned, permutated)): print("Input Error: Sorry, the input makes no sense") return(current_colour_choice, (-1, permutated)) guesses.append((current_colour_choice, (rightly_positioned, permutated ))) view_guesses() current_colour_choice = create_new_guess() if not current_colour_choice: return(current_colour_choice, (-1, permutated)) return(current_colour_choice, (rightly_positioned, permutated))

Die Variablen rightly_positioned und permutated entsprechen der Anzahl der schwarzen und der weißen Stifte. Mit der Funktion get_evaluation wird eine Bewertung vom menschlichen Mitspieler erfragt. Falls der Wert von rightly_positioned gleich der Anzahl der Positionen ist, also number_of_positions, sind alle Farben geraten und stehen an der richtigen Position. Mit der Funktion answer_ok wird überprüft, ob die Bewertung des menschlichen Mitspielers Sinn macht. So darf zum Beispiel die Summe der Werte von rightly_positioned und permutated nicht größer als die Anzahl der Positionen number_of_positions sein: if not answer_ok((rightly_positioned, permutated)): print("Input Error: Sorry, the input makes no sense") return(current_colour_choice, (-1, permutated))

Falls die die Bewertung okay war, wird der Versuch und die Bewertung in der Liste der guesses mit der Methode append aufgenommen. Anschließend wird mit create_new_guess versucht, eine neue mögliche Farbkombination zu erzeugen, die im Einklang mit den bisherigen Versuchen steht. Falls nicht wird eine -1 im Bewertungstupel übergeben: current_colour_choice = create_new_guess() if not current_colour_choice: return(current_colour_choice, (-1, permutated)) return(current_colour_choice, (rightly_positioned, permutated))

Im Folgenden finden Sie ein komplettes Listing von mastermind.py: import random from combinatorics import all_colours def inconsistent(p, guesses): """ the function checks, if a permutation p, i.e. a list of colours like p = ['pink', 'yellow', 'green', 'red'] is consistent with the previous colours. Each previous colour permutation guess[0] compared (check()) with p has to return the same amount of blacks (rightly positioned colours) and whites (right colour at wrong position) as the corresponding evaluation (guess[1] in the list guesses) """ for guess in guesses: res = check(guess[0], p) (rightly_positioned, permutated) = guess[1]

31.4 Mastermind in Python

if res != [rightly_positioned, permutated]: return True # inconsistent return False # i.e. consistent def answer_ok(a): """ checking of an evaluation given by the human player makes sense. 3 blacks and 1 white make no sense for example. """ (rightly_positioned, permutated) = a if (rightly_positioned + permutated > number_of_positions) \ or (rightly_positioned + permutated < len(colours) number_of_positions): return False if rightly_positioned == 3 and permutated == 1: return False return True def get_evaluation(guess): """ asks the human player for an evaluation """ show_current_guess(guess) rightly_positioned = int(input("Blacks: ")) permutated = int(input("Whites: ")) return (rightly_positioned, permutated) def new_evaluation(current_colour_choice): """ This function gets a new colour choice, checks the consistency of this choice, adds the choice together with the evaluation as a tuple to the list of guesses. It returns a tuple with the colour choice and the evaluation of this choice.""" rightly_positioned, permutated = get_evaluation(current_colour_choice) if rightly_positioned == number_of_positions: return(current_colour_choice, (rightly_positioned, permutated)) if not answer_ok((rightly_positioned, permutated)): print("Input Error: Sorry, the input makes no sense") return(current_colour_choice, (-1, permutated)) guesses.append((current_colour_choice, (rightly_positioned, permutated ))) view_guesses() current_colour_choice = create_new_guess() if not current_colour_choice: return(current_colour_choice, (-1, permutated)) return(current_colour_choice, (rightly_positioned, permutated))

def check(p1, p2): """ check() calculates the number of bulls (blacks) and cows (whites) of two permutations """ blacks = 0 whites = 0 for i in range(len(p1)):

363

364

31 Mastermind

if p1[i] == p2[i]: blacks += 1 else: if p1[i] in p2: whites += 1 return [blacks, whites] def create_new_guess(): """ a new guess is created, which is consistent to the previous guesses """ next_choice = next(permutation_iterator) while inconsistent(next_choice, guesses): try: next_choice = next(permutation_iterator) except StopIteration: print("Error: Your answers were inconsistent!") return () return next_choice def show_current_guess(new_guess): """ The current guess is printed to stdout """ print("New Guess: ",end=" ") for c in new_guess: print(c, end=" ") print() def view_guesses(): """ The list of all guesses with the corresponding evaluations is printed """ print("Previous Guesses:") for guess in guesses: guessed_colours = guess[0] for c in guessed_colours: print(c, end=" ") for i in guess[1]: print(" %i " % i, end=" ") print()

colours = ["red","green","blue","yellow","orange","pink"] guesses = [] number_of_positions = 4 permutation_iterator = all_colours(colours, number_of_positions) current_colour_choice = next(permutation_iterator) new_guess = (current_colour_choice, (0,0) ) while (new_guess[1][0] == -1) or (new_guess[1][0] != number_of_positions) : print(guesses) new_guess = new_evaluation(new_guess[0])

32 32.1

Textklassifikation

Einführung in die Textklassifikation

Die Klassifikation und Kategorisierung von Dokumenten ist ein Thema der Informationswissenschaft, eine Wissenschaft, die sich mit der Untersuchung von Informationen und Wissen beschäftigt. Dabei geht es um die Auswertung, die Selektion, das Erschließen, das Suchen und Finden sowie das Bereitstellen und Verwerten von Informationen. Wenn es um Dokumente geht, die sich Klassen oder Kategorien zuordnen lassen, kann man sich BILD 32.1 Textklassifikation als Prisma der automatischen Dokumentenklassifikation bedienen. Sehr häufig handelt es sich bei den Dokumenten um reine Textdokumente, und dann spricht man von Textklassifikation. Dokumentenklassifikation sind allgemeine Dokumente, die auch Bilder, Grafiken, Musik, Partituren usw. enthalten können. Dies mag ziemlich abstrakt klingen, aber heutzutage gibt es viele Situationen, in denen Firmen die automatische Klassifikation bzw. Kategorisierung von Dokumenten benötigen. Man denke nur beispielsweise an eine große Firma, die jeden Tag Berge von Eingangspost zu bewältigen hat, sowohl in elektronischer als auch in papiergebundener Form. Üblicherweise hat ein großer Teil dieser Eingangspost weder einen korrekten Betreff noch eine Namens- oder Abteilungsnennung, aus der eindeutig hervorgeht, wohin, also zu welcher Person oder Abteilung, das Poststück weitergeleitet werden kann. Jemand muss also diese Post lesen und entscheiden, um welches Anliegen es geht, also beispielsweise „Adressänderung”, „Beschwerdeschreiben”, „Anfrage zu einem Produkt” usw. Dieser „Jemand” könnte auch ein automatisches Textklassifikationssystem sein. Die Geschichte der automatischen Textklassifikation geht zurück bis in an den Anfang der 1960er-Jahre. Aber erst der unglaubliche Anstieg in verfügbaren Online-Dokumenten in den letzten beiden Jahrzehnten im Zusammenhang mit der Expansion des Internets hat sich das Interesse an der automatischen Dokumentenklassifikation und dem Data Mining erneuert und intensiviert. Anfänglich war die Dokumentenklassifikation auf heuristische Methoden fixiert, d.h. man versuchte, die Aufgaben dadurch zu lösen, dass man Expertenwissen in Regelwerke packte. Dieser Ansatz stellte sich als hochgradig ineffizient für kom-

366

32 Textklassifikation

plexe Aufgaben heraus, sodass heutzutage der Fokus auf automatischem Lernen und der Anwendung von Clustering-Methoden liegt. Die Aufgabe der Textklassifikation besteht darin, Dokumente in Abhängigkeit ihres semantischen Inhaltes einer oder mehreren Kategorien oder Klassen zuzuordnen.

Man unterscheidet eine Lernphase und eine Klassifikationsphase. Die Lernphase kann man grob in drei Arten unterteilen: ■





Die notwendige Information für das Lernen, also die korrekte Labelung der Dokumente, erfolgt beim überwachten Lernen durch „Eingriff” eines externen Mechanismus, üblicherweise menschliches Feedback. Unter halbüberwachtem Lernen (semi-supervised learning) versteht man eine Mischung aus überwachtem und nicht überwachtem Lernen. Nicht überwachtes Lernen (automatisches Lernen) erfolgt komplett ohne Einwirkung von außen.

Wir werden einen Textklassifikator in Python implementieren, der auf dem naiven BayesKlassifikator basiert. Naive Bayes ist der am häufigsten benutzte Textklassifikator und steht im Fokus der Forschung. Ein naiver Bayes-Klassifikator geht von der strengen Unabhängigkeit der verwendeten Merkmale aus, d.h. das Vorhandensein oder die Abwesenheit eines bestimmten Merkmals in einer Klasse steht in keiner Beziehung zum Vorhandensein bzw. Abwesenheit von allen anderen Merkmalen. Eine „naive” Annahme, die dennoch in der praktischen Anwendung zu hervorragenden Ergebnissen führt.

32.2 Textklassifikation: Aufgabe

32.2

Textklassifikation: Aufgabe

Gegeben ist eine Menge von Klassen C = {c 1 , c 2 , ...c m } und eine Menge von Dokumenten D = {d 1 , d 2 , ...d n }. Die Aufgabe der Textklassifikation besteht nun darin, jedem Paar (c i , d j ) ∈ C × D, wobei 1 ≤ i ≤ m ∧ 1 ≤ j ≤ n) gilt, einen Wert 0 oder 1 zuzuordnen, d.h. den Wert 0, wenn das Dokument d j nicht zur Klasse c i gehört, ansonsten 1. Es gibt verschiedene Ansätze, dieses Entscheidungsproblem zu lösen. Die wichtigsten sind wohl: ■

Naive Bayes



Support Vector Machine



Nearest Neighbour

32.3 32.3.1

Naive-Bayes-Klassifikator

Definition

Ein Naive-Bayes-Klassifikator ist ein probabilistischer Klassifikator, der nach dem englischen Mathematiker Thomas Bayes benannt ist. Er leitet sich aus dem Bayes-Theorem ab. Beim Naive-Bayes-Klassifikator wird jedes Objekt, in unserem Fall jedem Textdokument, eine Klasse oder Kategorie zugeordnet, zu der es mit der größten Wahrscheinlichkeit gehört. Die zugrunde liegende Annahme beim Naive-Bayes-Klassifikator besteht darin, von einer strengen Unabhängigkeit der verwendeten Merkmale auszugehen, d.h. das Vorhandensein oder die Abwesenheit eines bestimmten Merkmales in einer Klasse steht in keiner Beziehung zum Vorhandensein bzw. Abwesenheit von allen anderen Merkmalen. Diese Annahme ist mit dem Namensbestandteil „Naive” gemeint. Eine Voraussetzung, die in der praktischen Anwendung des Naive-Bayes-Konzeptes meist nicht gegeben ist, aber dennoch werden häufig mit diesem „naiven” Ansatz ausreichend gute Ergebnisse erzielt, und das bei einer schnellen Berechenbarkeit, durch die sich der NBK auszeichnet.

32.3.2

Bayes-Theorem

Definition unabhängiger Ereignisse: Zwei Ereignisse E und F sind unabhängig, wenn das Auftreten von E die Wahrscheinlichkeit, dass F eintritt, nicht beeinflusst und umgekehrt: P (E |F ) = P (E ) und P (F |E ) = P (F ) Die „bedingte Wahrscheinlichkeit” ist die Wahrscheinlichkeit des Eintretens eines Ereignisses A unter der Bedingung, dass das Eintreten eines anderen Ereignisses B bereits bekannt ist.

367

368

32 Textklassifikation

Für die bedingte Wahrscheinlichkeit gilt: P (A|B ) =

P (A ∩ B ) P (B )

Bayes-Theorem: P (A|B ) =

P (B |A)P (A) P (B )

P (A|B ) bezeichnet die bedingte Wahrscheinlichkeit für ein Ereignis A unter der Bedingung, dass B gilt. P (A|B ) wird im Zusammenhang mit dem Bayes-Theorem häufig auch als Aposteriori-Wahrscheinlichkeit bezeichnet. P (B ) ist die A-priori-Wahrscheinlichkeit für das Ereignis B und P (A) ist die A-prioriWahrscheinlichkeit für das Ereignis A. (B |A) bezeichnet die bedingte Wahrscheinlichkeit für ein Ereignis B unter der Bedingung, dass A gilt. Ein Vorteil des Naive-Bayes-Klassifikators besteht darin, dass er nur eine kleine Trainingsmenge benötigt, um die zur Klassifikation notwendigen Parameter einzustellen.

32.4

Formale Herleitung der Naive-Bayes-Klassifikation

Gegeben ist eine Menge von Klassen C = {c 1 , c 2 , ...c m } und eine Menge von Dokumenten D = {d 1 , d 2 , ...d n }. Jedes Dokument ist mit einer Klasse etikettiert1 . Die Menge D von Dokumenten wird benötigt, um den Klassifikator zu trainieren. Die Klassifikation besteht darin, die wahrscheinlichste Klasse für ein unbekanntes Dokument auszuwählen. Wir bezeichnen die Anzahl der Vorkommen eines Wortes w t in einem Dokument d i mit Ni t . N tC bezeichnet die Anzahl der Vorkommen eines Wortes w t in allen Dokumenten einer gegebenen Klasse C. P (d i |c j ) ist 1, falls d i mit c j etikettiert ist, ansonsten 0. Die Wahrscheinlichkeit für ein Wort w t unter der Bedingung, dass die Klasse c j gegeben ist, wird wie folgt berechnet: cj

P (w t |c j ) =

1 + Nt |V | +

|V | X s

1

englisch: labelled

Cj

Ns

32.4 Formale Herleitung der Naive-Bayes-Klassifikation

Die Wahrscheinlichkeit P (c j ) für eine Klasse c j ist der Quotient aus der Anzahl der Dokumente der Klasse c j und der Anzahl der Dokumente aller Klassen, d.h. des Lernsets: |D| X

P (c j ) =

P (d i |c j )

i =1

|D|

Nun kommen wir zu der Formel, die wir zur Klassifikation eines Dokuments benötigen, d.h. die Wahrscheinlichkeit für eine Klasse c j unter der Bedingung, dass ein Dokument d i gegeben ist: di Y

P (c j ) P (c j |d i ) =

P (w di k |c j )

k=1 |C | X

|d Yi |

(P (c r )

r =1

P (w di k |c r ))

k=1

Leider ist die vorherige Formel für P (c|d i ) numerisch nicht stabil, weil der Nenner wegen fortschreitender Rundungsfehler Null werden kann. Wir ändern dies, indem wir den Kehrwert berechnen und den Ausdruck als Summe von stabilen Quotienten berechnen: 1 P (c|d ) Dies führt zu folgendem Ausdruck: |V | X

c

|d | 1 P (c 1 ) Y = P (c|d ) P (c) k=1

(1 + Nd1 )(|V | + k

(1 + Ndc )(|V | + k

s=1 |V | X

c

N sc ) +...+ c

Ns 1 )

|d | P (c |C | ) Y

P (c)

k=1

s=1

Die folgenden Zwischenschritte haben wir oben ausgelassen: |C | X

1 = P (c|d )

(P (c r )

r =1

|d | Y

P (w k |c r ))

k=1

P (c)

|d | Y

P (w k |c)

k=1 |C | X

(P (c r )

r =1

|d | Y k=1

c

1 + N dr

k

|V | +

|V | X s=1

= P (c)

|d | Y

1 + Ndc

k=1

|V | X

|V | +

s=1

) c

Ns r

k

N sc

(1 + Nd|C | )(|V | + k

(1 + Ndc )(|V | + k

|V | X

N sc )

s=1

|V | X s=1

c

N s |C | )

369

370

32 Textklassifikation

(P (c 1 )

|d | Y k=1

c

1 + N d1

k

|V | +

|V | X

)

P (c)

|d | Y

1 + Ndc

k=1

|V | X

|V | +

k=1

c

Ns 1

s=1

=

(P (c 2 )

k

c

1 + N d2

k

|V | +

|V | X

)

P (c)

N sc

|d | Y

1 + Ndc

k=1

|V | X

|V | +

(P (c |C | )

c

Ns 2

s=1

+

s=1

32.5

|d | Y

N sc

1 + Nd|C |

k=1

|V | X

k

|V | +

N s|C |

s=1

+...+ k

|d | Y

P (c)

|d | Y

1 + Ndc

k=1

|V | X

|V | +

s=1

k

N sc

s=1

Textklassifikation in Python

Python ist ideal für die Textklassifikation wegen seiner Stringklasse und deren mächtiger Methoden. Weiterhin werden mit dem Modul re leistungsstarke Tools zur Verfügung gestellt, die weit über den Rahmen anderer Programmiersprachen hinausgehen. Die vorliegende Python-Implementierung eines Naive-Bayes-Klassifikators ist nicht auf Effizienz optimiert. Die Dokumente werden in unserer Implementierung nach dem sogenannten „bag of words”-Modell implementiert, was wir im folgenden Diagramm illustrieren:

32.5.1

BagOfWords-Klasse

Die Klasse BagOfWords verwaltet im Wesentlichen ein Dictionary self.__bag_of_words, das Wörter mit ihren Häufigkeiten enthält bzw. zählt. class BagOfWords(object): """ Implementing a bag of words, words corresponding with their frequency of usages in a "document" for usage by the Document class, DocumentClass class and the Pool class.""" def __init__(self): self.__number_of_words = 0 self.__bag_of_words = {}

)

32.5 Textklassifikation in Python

def __add__(self,other): """ Overloading of the "+" operator to join two BagOfWords """ erg = BagOfWords() sum = erg.__bag_of_words for key in self.__bag_of_words: sum[key] = self.__bag_of_words[key] if key in other.__bag_of_words: sum[key] += other.__bag_of_words[key] for key in other.__bag_of_words: if key not in sum: sum[key] = other.__bag_of_words[key] return erg def add_word(self,word): """ A word is added in the dictionary __bag_of_words""" self.__number_of_words += 1 if word in self.__bag_of_words: self.__bag_of_words[word] += 1 else: self.__bag_of_words[word] = 1 def len(self): """ Returning the number of different words of an object """ return len(self.__bag_of_words) def Words(self): """ Returning a list of the words contained in the object """ return self.__bag_of_words.keys()

def BagOfWords(self): """ Returning the dictionary, containing the words (keys) with their frequency (values)""" return self.__bag_of_words def WordFreq(self,word): """ Returning the frequency of a word """ if word in self.__bag_of_words: return self.__bag_of_words[word] else: return 0

32.5.2

Document-Klasse

Die Klasse Document wird sowohl für die eigentlichen Textdokumente verwendet als auch als Basisklasse für die DocumentClass, also eine Klasse im Sinne der Textklassifikation.

371

372

32 Textklassifikation

class Document(object): """ Used both for learning (training) documents and for testing documents. The optional parameter learn has to be set to True, if a classificator should be trained. If it is a test document learn has to be set to False. """ def __init__(self, vocabulary): self.__name = "" self.__document_class = None self._words_and_freq = BagOfWords() Document._vocabulary = vocabulary def read_document(self,filename, learn=False): """ A document is read. It is assumed, that the document is either encoded in utf-8 or in iso-8859... (latin-1). The words of the document are stored in a Bag of Words, i.e. self._words_and_freq = BagOfWords() """ try: text = open(filename,"r", encoding='utf-8').read() except UnicodeDecodeError: text = open(filename,"r", encoding='latin-1').read() text = text.lower() words = re.split("[^\wäöüÄÖÜß]*",text) self._number_of_words = 0 for word in words: self._words_and_freq.add_word(word) if learn: Document._vocabulary.add_word(word)

def __add__(self,other): """ Overloading the "+" operator. Adding two documents consists in adding the BagOfWords of the Documents """ res = Document(Document._vocabulary) res._words_and_freq = self._words_and_freq + other. _words_and_freq return res def vocabulary_length(self): """ Returning the length of the vocabulary """ return len(Document._vocabulary) def WordsAndFreq(self): """ Returning the dictionary, containing the words (keys) with their frequency (values) as contained in the BagOfWords attribute of the document""" return self._words_and_freq.BagOfWords()

32.5 Textklassifikation in Python

def Words(self): """ Returning the words of the Document object """ d = self._words_and_freq.BagOfWords() return d.keys() def WordFreq(self,word): """ Returning the number of times the word "word" appeared in the document """ bow = self._words_and_freq.BagOfWords() if word in bow: return bow[word] else: return 0 def __and__(self, other): """ Intersection of two documents. A list of words occuring in both documents is returned """ intersection = [] words1 = self.Words() for word in other.Words(): if word in words1: intersection += [word] return intersection

32.5.3

DocumentClass-Klasse

Diese Klasse implementiert die Klassen im Sinne der Textklassifikation. class DocumentClass(Document): def __init__(self, vocabulary): Document.__init__(self, vocabulary) self._number_of_docs = 0 def Probability(self,word): """ returns the probability of the word "word" given the class " self" """ voc_len = Document._vocabulary.len() SumN = 0 for i in range(voc_len): SumN = DocumentClass._vocabulary.WordFreq(word) N = self._words_and_freq.WordFreq(word) erg = 1 + N erg /= voc_len + SumN return erg def __add__(self,other): """ Overloading the "+" operator. Adding two DocumentClass objects consists in adding the BagOfWords of the DocumentClass objects """

373

374

32 Textklassifikation

res = DocumentClass(self._vocabulary) res._words_and_freq = self._words_and_freq + other. _words_and_freq return res def SetNumberOfDocs(self, number): self._number_of_docs = number def NumberOfDocuments(self): return self._number_of_docs

32.5.4

Pool-Klasse

Diese Klasse verwaltet die Dokumentenklassen und das Vokabular. Außerdem befinden sich hier die allgemeinen Lern- und Klassifikationsmethoden. class Pool(object): def __init__(self): self.__document_classes = {} self.__vocabulary = BagOfWords() def sum_words_in_class(self, dclass): """ The number of times all different words of a dclass appear in a class """ sum = 0 for word in self.__vocabulary.Words(): WaF = self.__document_classes[dclass].WordsAndFreq() if word in WaF: sum += WaF[word] return sum def learn(self, directory, dclass_name): """ directory is a path, where the files of the class with the name dclass_name can be found """ x = DocumentClass(self.__vocabulary) dir = os.listdir(directory) for file in dir: d = Document(self.__vocabulary) print(directory + "/" + file) d.read_document(directory + "/" + file, learn = True) x = x + d self.__document_classes[dclass_name] = x x.SetNumberOfDocs(len(dir))

def Probability(self, doc, dclass = ""):

32.5 Textklassifikation in Python

"""Calculates the probability for a class dclass given a document doc""" if dclass: sum_dclass = self.sum_words_in_class(dclass) prob = 0 d = Document(self.__vocabulary) d.read_document(doc) for j in self.__document_classes: sum_j = self.sum_words_in_class(j) prod = 1 for i in d.Words(): wf_dclass = 1 + self.__document_classes[dclass]. WordFreq(i) wf = 1 + self.__document_classes[j].WordFreq(i) r = wf * sum_dclass / (wf_dclass * sum_j) prod *= r prob += prod * self.__document_classes[j]. NumberOfDocuments() / self.__document_classes[dclass ].NumberOfDocuments() if prob != 0: return 1 / prob else: return -1 else: prob_list = [] for dclass in self.__document_classes: prob = self.Probability(doc, dclass) prob_list.append([dclass,prob]) prob_list.sort(key = lambda x: x[1], reverse = True) return prob_list def DocumentIntersectionWithClasses(self, doc_name): res = [doc_name] for dc in self.__document_classes: d = Document(self.__vocabulary) d.read_document(doc_name, learn=False) o = self.__document_classes[dc] & d intersection_ratio = len(o) / len(d.Words()) res += (dc, intersection_ratio) return res

Die obigen Klassen befinden sich in der Datei „NaiveBayes.py” in unserem Beispielverzeichnis. Im Beispielverzeichnis befinden sich auch die Verzeichnisse „learn” und „test” mit Lernund Testsets für die folgende Beispielanwendung zur Textklassifikation. Dabei handelt es sich um verschiedene Klassen mit Witzen:

375

376

32 Textklassifikation

from NaiveBayes import import os

Pool

DClasses = ["clinton",

"lawyer",

"math",

"medical",

"music",

"sex"]

base = "learn/" p = Pool() for i in DClasses: p.learn(base + i, i)

base = "learn/" for i in DClasses: dir = os.listdir(base + i) for file in dir: res = p.Probability(base + i + "/" + file) print(i + ": " + file + ": " + str(res))

Startet man das Programm, erhält man das folgende Ergebnis: bernd@saturn:~/bodenseo/python/beispiele$ python3 NaiveBayes_test.py | head learn/clinton/clinton20.txt learn/clinton/clinton15.txt learn/clinton/clinton56.txt learn/clinton/clinton36.txt learn/clinton/clinton10.txt learn/clinton/clinton1.txt learn/clinton/clinton14.txt learn/clinton/clinton69.txt learn/clinton/clinton25.txt learn/clinton/clinton34.txt ...

und dann folgen die Klassifikationsergebnisse: ... clinton: clinton20.txt: [['clinton', 1.0], ['medical', 9.901960867214498e -22], ['sex', 7.68058228879274e-28], ['music', 2.592331193036529e -29], ['lawyer', 2.3811580908948818e-29], ['math', 1.948648939490094e -35]] clinton: clinton15.txt: [['clinton', 0.9999997364637545], ['music', 2.441826815920904e-07], ['sex', 1.7744976589393507e-08], ['medical', 1.2295982769832164e-09], ['lawyer', 3.674688944524231e-10], ['math', 1.1520143389502419e-11]] clinton: clinton56.txt: [['clinton', 0.9999999994344095], ['music', 5.520351011801854e-10], ['math', 5.72601686083638e-12], ['lawyer', 5.1459928139047106e-12], ['sex', 2.4074110973205754e-12], ['medical', 2.760021719870858e-13]] clinton: clinton36.txt: [['clinton', 0.9999999992386299], ['music', 6.914992628977959e-10], ['lawyer', 6.648015266828207e-11], ['sex',

32.5 Textklassifikation in Python

1.7642685540607824e-12], ['math', 1.5179052741540378e-12], ['medical ', 1.0839574950384394e-13]] clinton: clinton10.txt: [['clinton', 0.9999999999999882], ['music', 1.1795522781235521e-14], ['sex', 3.486402118341457e-18], ['medical', 7.514449565885636e-19], ['math', 4.419633484697798e-19], ['lawyer', 2.613944672153929e-19]] clinton: clinton1.txt: [['clinton', 1.0], ['lawyer', 3.966110616318117e -39], ['music', 4.071780957518354e-44], ['sex', 3.2215177549148766e -45], ['medical', 1.0029914377606443e-45], ['math', 1.6849007485121964e-51]] clinton: clinton14.txt: [['clinton', 1.0], ['music', 4.6481471984787215e -32], ['medical', 4.914323838547423e-42], ['sex', 1.990905914149136e -42], ['lawyer', 1.4639896765847644e-42], ['math', 1.112918004222222e -46]] clinton: clinton69.txt: [['clinton', 0.9999999999999825], ['music', 1.7595454063410785e-14], ['lawyer', 1.64894907605748e-17], ['math', 1.4206270940103888e-18], ['medical', 7.215593474577993e-20], ['sex', 4.595073346783043e-21]] clinton: clinton25.txt: [['clinton', 0.9999999998207063], ['music', 1.7923166491945231e-10], ['lawyer', 5.502049137268664e-14], ['sex', 5.752336117034771e-15], ['math', 1.1389115763215046e-15], ['medical', 4.1549302112730125e-17]] ...

377

33 33.1

Lösungen zu den Aufgaben

Lösungen zu Kapitel 5 (Sequentielle Datentypen)

1. Aufgabe Wir haben die folgenden Datentypen kennengelernt: ■







str: Zeichenketten, meistens als Strings bezeichnet : list: Listen tuple: Tupel bytes und bytearry: 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 >>>

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.

380

33 Lösungen zu den Aufgaben

Falls Sie l[len(l) - 1] als Lösung gefunden hatten, ist dies auch korrekt, aber weniger elegant als obige Lösung. 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 "", line 1, in 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 "", line 1, in 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 "", line 1, in TypeError: 'str' object does not support item assignment

Strings sind unveränderlich. >>> l[2][0] = "g" Traceback (most recent call last): File "", line 1, in TypeError: 'str' object does not support item assignment

33.2 Lösungen zu Kapitel 6 (Dictionaries)

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 Sclicing-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.' >>>

Vielleicht interessiert es Sie auch, wie wir den String erzeugt hatten. Um das Folgende zu verstehen, müssen Sie allerdings das Kapitel 26 (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..' >>>

33.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"},

381

382

33 Lösungen zu den Aufgaben

"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 {'print preview': 'Anteprima di stampa', 'exit': 'Esci', 'save as': ' Salva come', 'file': 'File', 'close': 'Chiudi', 'print': 'Stampa', ' new': 'Nuovo', 'save': 'Salva', 'open': 'Apri'}

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)

: : : : : : : : : : : : : : : : : : : : :

("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"),

33.3 Lösungen zu Kapitel 8 (Verzweigungen)

("b",7) ("d",7) ("e",7) ("f",7) ("g",7) ("a",8) ("d",8) ("e",8) ("f",8) ("g",8) ("h",8)

: : : : : : : : : : :

("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')

33.3

Lösungen zu Kapitel 8 (Verzweigungen)

1. Aufgabe #!/usr/bin/python3 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:

383

384

33 Lösungen zu den Aufgaben

#!/usr/bin/python3 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")

2. 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) if len(seconds) < 2: seconds = "0" + seconds print("New time: " + hours + ":" + minutes + ":" + seconds)

33.4 Lösungen zu Kapitel 9 (Schleifen)

Mit Hilfe 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))

33.4

Lösungen zu Kapitel 9 (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”. 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. roman_number = input("Bitte römische Zahl eingeben: ")

385

386

33 Lösungen zu den Aufgaben

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 while sum - old_sum > eps: old_sum = sum sum += i i = i / 2 print(i, sum)

Die Ausgabe dieses Programms liefert uns folgende Werte: $ python3 frog.py | more 0.5 1

33.4 Lösungen zu Kapitel 9 (Schleifen)

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

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: koerner = 0 zuwachs = 1 for i in range(64): koerner += zuwachs

387

388

33 Lösungen zu den Aufgaben

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

33.5

Lösungen zu Kapitel 10 (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 = "" 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()

33.5 Lösungen zu Kapitel 10 (Dateien lesen und schreiben)

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: 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()

389

390

33 Lösungen zu den Aufgaben

33.6

Lösungen zu Kapitel 11 (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 liste = [["test", ["noch tiefer", "auch"]],1,(2,(2,3,4))] print(flatten(liste))

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] >>>

33.6 Lösungen zu Kapitel 11 (Listen und Tupel im Detail)

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: 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)]

391

392

33 Lösungen zu den Aufgaben

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) ('Eve', 'Turner', 44, 18.99) bernd@venus:~/bodenseo/python/beispiele$

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:")

33.7 Lösungen zu Kapitel 15 (Rekursive Funktionen)

for i in sorted(umsaetze, key=name): print(i)

33.7

Lösungen zu Kapitel 15 (Rekursive Funktionen)

1. Aufgabe 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])

393

394

33 Lösungen zu den Aufgaben

line += [1] return line print(pascal(6))

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):

33.7 Lösungen zu Kapitel 15 (Rekursive Funktionen)

# returns all primes between 2 and n primes = list(range(2,n+1)) max = sqrt(n) num = 2 while num < max: i = num while i 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) zu Rate zu ziehen: from math import sqrt def primes(n): if n == 0: return [] elif n == 1: return [1] else: p = primes(int(sqrt(n))) no_p = [j for i in p for j in range(i*2, n, i)] p = [x for x in range(2, n) 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 """ if len(x) == 1: # started by user

395

396

33 Lösungen zu den Aufgaben

# 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

33.8 Lösungen zu Kapitel 17 (Alles über Strings . . . )

33.8

Lösungen zu Kapitel 17 (Alles über Strings . . . )

1. Aufgabe 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 splitt 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)

Alternativ kann man auch ein split ohne maxsize verwenden und dann auf den ersten und letzten Index zugreifen: fh = open("addresses.txt", encoding="iso-8859-1")

397

398

33 Lösungen zu den Aufgaben

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 if __name__ == "__main__": s = "abc xyz abc xyz abc xyz" print(findnth(s, "xyz", 1)) print(findnth(s, "xyz", 3))

Dieses Programm befindet sich in unserem Beispielverzeichnis unter „findnth.py”. Startet man das Programm erhält man folgende Ausgaben:

33.8 Lösungen zu Kapitel 17 (Alles über Strings . . . )

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!' >>>

399

400

33 Lösungen zu den Aufgaben

33.9

Lösungen zu Kapitel 19 (Objektorientierte Programmierung)

1. 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. Ansonsten ist dieses Attribut natürlich nicht von besonderer Wichtigkeit für unsere Klasse. Sowohl mit der Methode __str__ als auch mit der Methode __repr__ wird ein Objekt der Klasse in eine Stringdarstellung gewandelt. String verändert aber gewissermaßen das interne „Aussehen” eines Objektes und wird beispielsweise aufgerufen von print, als print(x) für ein Objekt x der Klasse Robot ruft __str__ auf. Tippt man hingegen nur x in der interaktiven Shell ein, wird __repr__ aufgerufen. Ist nur die Methode __str__ aer nicht __repr__ definiert, dann erhält man beispielsweise die folgenden Ausgaben: >>> x >>> print(x) marvin faces north at position [3, 4] >>>

Ist hingegen die Methode __str__ nicht definiert, aber __repr__ definiert, dann wird in beiden Fällen __repr__ zur Ausgabe verwendet: >>> x (Marvin, north, [3, 4]) >>> print(x) (Marvin, north, [3, 4]) >>> """ Roboterklasse zur Positionsbeschreibung und Veränderung von Objekten in einem zweidimensionalen Koordinatensystem. """ class Robot(object): def __init__(self, x=0, y=0, orientation = "north", name="marvin"): self.position = [x,y] self.orientation = orientation

33.9 Lösungen zu Kapitel 19 (Objektorientierte Programmierung)

self.name = name def __str__(self): """ Stringdarstellung einer Instanz """ s = self.name + " faces " + self.orientation s += " at position " + str(self.position) return s def __repr__(self): """ Allgemeine Darstellung einer Instanz """ s = "(" + self.name + ", " + self.orientation s += ", " + 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

def newOrientation(self, o): """ Mit der Methode newOrientation ändern wir die Orientierung des Roboters. o has to be in {"north","south","west","east"} Falls eine unbekannte Orientierung übergeben wird, wird der Roboter nicht bewegt. """ if o in {"north","south","west","east"}: self.orientation = o def getOrientation(self): """ Ein Aufruf von x.getOrientation() für einen Roboter x liefert dessen aktuelle Orientierung zurück, also eine der Richtungen

401

402

33 Lösungen zu den Aufgaben

"west", "south", "east" oder "north". """ return

self.orientation

def getPosition(self): """Liefert eine 2er-Liste mit [x,y] zurück.""" return self.position def setPosition(self,pos): """Damit kann man den Roboter auf eine neue Position im Koordinatensystem positionieren. pos muss eine Liste oder ein Tupel mit zwei Elementen sein. Ansonsten wird nichts getan.""" if isinstance(pos, (tuple, list)) and len(pos) == 2: self.position = pos def rename(self,name): """ Damit kann man dem Roboter einen neuen Namen geben. """ self.name = name def getName(self): """ Liefert den Namen des Roboters zurück. """ return self.name

if __name__ == "__main__": from robot import Robot x = Robot(3,4,"north", "Marvin") print(x) x.move(10) x.newOrientation("west") x.move(7) print(x) new_name = "Andrew" print(x.getName() + " will be renamed as " + new_name) x.rename(new_name) print("Hi, this is " + x.getName()) x.setPosition([0,0]) print(x)

Ruft man obiges Modul direkt auf, erhält man folgende Ausgaben: $ python3 robot.py Marvin faces north at position [3, 4] Marvin faces west at position [-4, 14] Marvin will be renamed as Andrew Hi, this is Andrew Andrew faces west at position [0, 0]

33.9 Lösungen zu Kapitel 19 (Objektorientierte Programmierung)

2. 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 """ 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

403

404

33 Lösungen zu den Aufgaben

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))

3. Aufgabe class Plist(list): 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])

33.10 Lösungen zu Kapitel 20 (Tests und Fehler)

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]

33.10

Lösungen zu Kapitel 20 (Tests und Fehler)

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.,

33.11

Lösungen zu Kapitel 24 (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 (d) matcht 4, 6 (e) matcht 1, 5 (f) matcht 4, 6 (g) matcht 1, 3, 5

405

406

33 Lösungen zu den Aufgaben

2. Aufgabe Die Frage lautete, auf welchen der Strings (1) (2) (3) (4) (5)

xx xyxyxyx xxyxy xyxx xxyxyx

der Ausdruck x(xy)*x matcht. 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! 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, das der Zip-Code 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")

33.11 Lösungen zu Kapitel 24 (Reguläre Ausdrücke)

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 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')}

407

408

33 Lösungen zu den Aufgaben

>>> words = { w[0] for w in words} >>> words {'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 16 53 17 30

Häufigkeit

33.11 Lösungen zu Kapitel 24 (Reguläre Ausdrücke)

18 19 20 21 22 23 24 25 26 27 28 30 33 34 36 37 39 69

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 15 100 16 42 17 23 18 13 19 6 20 6

409

410

33 Lösungen zu den Aufgaben

21 22 23 24 25 26 27 28 30 33 34 36 37 39 69

33.12

4 1 2 1 3 2 1 1 1 1 1 2 1 1 1

Lösungen zu Kapitel 25 (lambda, map, filter und reduce)

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)] ] min_order = 100

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,

33.13 Lösungen zu Kapitel 26 (Listen-Abstraktion/List Comprehension)

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)

33.13

Lösungen zu Kapitel 26 (Listen-Abstraktion/List Comprehension)

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

411

412

33 Lösungen zu den Aufgaben

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)

if __name__ == "__main__": for i in range(14): print("sum: {0:2d}, prob: {1:5.2f} %".format(i,prob_for_sum(i) *100 ) )

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 %

33.14

Lösungen zu Kapitel 27 (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

33.14 Lösungen zu Kapitel 27 (Generatoren und Iteratoren)

for x in f: print x, counter += 1 if (counter > 10): break

2. Aufgabe 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

413

414

33 Lösungen zu den Aufgaben

def pendulum(g): while True: l = [] for i in g(): l.append(i) yield i while l: yield l.pop() if __name__ == "__main__": for i in pendulum(city_generator): print(i)

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()

33.15 Lösungen zu Kapitel 29 (NumPy)

if __name__ == "__main__": for i in pair_sum(numbers): print(i, end=", ")

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, ...

33.15

Lösungen zu Kapitel 29 (NumPy)

1. Aufgabe Wenn zwei Vektoren senkrecht zueinander stehen, muss das Skalarprodukt 0 ergeben, weil der cos π2 gleich Null ist. Wir prüfen dies für die beiden Vektorpaare: >>> >>> >>> 0 >>> >>> >>> 0 >>>

x = np.array([2,7,-3]) y = np.array([-2,1,1]) np.dot(x,y) x = np.array([2,3,-1]) y = np.array([-5,4,2]) np.dot(x,y)

2. Aufgabe >>> import numpy as np >>> x = np.array([5,9,-5]) >>> y = np.array([-3,0,6]) >>> abs_x = np.sqrt(np.dot(x,x)) >>> abs_y = np.sqrt(np.dot(y,y)) >>> cos_xy = np.dot(x,y) / (abs_x * abs_y) >>> angle = np.arccos(cos_xy) >>> angle 2.1970314908665638 >>> angle * 360 / np.pi / 2 125.88063188398918 >>>

415

416

33 Lösungen zu den Aufgaben

3. Aufgabe >>> A = >>> b = >>> x = >>> x array([ >>>

np.array([[-2,-2,2,1],[-3,4,0,-2],[-1,2,3,2],[3,2,3,2]]) np.array([-5, -1, 0, 4]) np.linalg.solve(A,b) 1.,

1., -1.,

1.])

4. Aufgabe Es sollten die Nullstellen für das Polynom f (x) = 4 · x 3 + 12 · x 2 − 7 · x − 5 berechnet werden: >>> import numpy as np >>> f = np.poly1d([4,12,-7,-5]) >>> np.roots(f) array([-3.40604443, 0.84193743, -0.43589299]) >>>

5. Aufgabe >>> import numpy as np >>> f = np.poly1d([2,-3,-2,-1]) >>> f.integ() poly1d([ 0.5, -1. , -1. , -1. , 0. ]) >>> f.deriv() poly1d([ 6, -6, -2], dtype=int32) >>>

Das Integral F von f lautet also: F (x) =

1 4 · x − 1 · x3 − 1 · x2 − 1 · x 2

Die Ableitung von f lautet: f ′ (x) = 2 · x 3 − 3 · x 2 − 2 · x − 1

Stichwortverzeichnis

∧ bei regulären Ausdrücken 269 . Match eines beliebigen Zeichens 266 $ bei regulären Ausdrücken 269

A abort 226 abspath 240 access 227 __add__ 403 Allgemeine Klasse → Basisklasse Anagramm – als Beispiel einer Permutation 309 Andrew 402 Angestelltenklasse → Personklasse append 75 Archivdatei erzeugen 251 Arrays 328 – flach machen → NumPy – konkatenieren → NumPy – ones() 332 – umdimensionieren → NumPy – zeros() 332 assertAlmostEqual 207 assertCountEqual 207 AssertEqual 206 assertEqual 207 assertFalse 208 assertGreater 208 assertGreaterEqual 208 assertIn 208 AssertionError 205 assertIs 208 assertIsInstance 209 assertIsNone 209 assertIsNot 209 assertItemsEqual 209 assertLess 208 assertLessEqual 208 assertListEqual 209

assertNotRegexpMatches 209 assertTrue 208 assertTupleEqual 209 assoziative Arrays 33 assoziatives Feld 33 atime 243 Attribute 162 – private 162 – protected 162 – public 162 AttributeError 172 Aufspalten von Zeichenketten 132 Auskellern 75 Ausnahme – StopIteration 306 Ausnahmebehandlung 149 – except 149 – finally 153 – try 149 Automatentheorie 261

B Barry 195 basename 241 Bash 212 Basisklasse 169 Bayes-Theorem 367 bedingte Anweisungen 55 Bibliothek 90 Bierdeckelarithmetik 191 Bierdeckelnotation 191 binäre Operatoren 187 Binärsystem 191 Boehm 195 Bourne-Shell 212 Bruch – kürzen 349 – Kürzungszahl 349

418

Stichwortverzeichnis

– Repräsentierung in Python|hyperpage 348 – vollständig gekürzte Form 349 Bruchklasse 347 Bruchrechnen 348 Bulls and Cows 358 bztar-Datei → Archivdatei erzeugen

Calendar-Klasse 177 CalendarClock-Klasse 177 Caret-Zeichen 267 Cast 24 Casting 24 CaveInt 403 → Neanderthal-Arithmetik center 144 chdir 227 chmod 227 Chomsky-Grammatik 117 chown 228 chroot 228 clear → Dictionary CLI 212 Clock-Klasse 177 close 218 Closure 318 Codeblock 59 combinatorics.py 359 commonprefix 241 copy 249 → Dictionary copy2 249 copyfile 249 copyfileobj 249 copymode 249 copystat 250 copytree 250 count 78, 139 ctime 243

Decorator → Dekorateur Definition einer Variablen 21 Dekorateur 318 – Beispiel Fakultätsfunktion 320 – Benutzung in Python 319 – zur Implementierung eine Memoisation 320 – zur Überprüfung von Funktionsargumenten 320 deriv 343 derivation 343 Destruktor 160 Dezimalsystem 191 Diamond-Problem 176 Dictionary 33 – aus Listen erzeugen 43 – clear 38 – copy 38 – flache Kopie 38, 101 – fromkeys 38 – get 39 – items 39 – keys 39 – nested Dictionaries 37 – pop 40 – popitem 40 – setdefault 40 – tiefe Kopie 38, 101 – update 41 – verschachtelte Dictionaries 37 dirname 241 doctest 200 Dokumentenklassifikation 365 Donald Michie → Memoisation Drei-Finger-Regel 340 dup 220 dynamische Attribute 168 dynamische Typdeklaration 23

D

E

Datei 69 – lesen 69 – öffnen 69 – schreiben 71 – zum Schreiben öffnen 71 Dateibaum – rekursiv durchwandern 240 Dateibearbeitung mit os 215 Dateideskriptor 218, 219 Dateigröße 244 Datenkapselung 157 Datentypen 21 Deadly Diamond of Death 176

Eingabeprompt 9 Einheitsmatrix 339 Einkellern 75 Einschubmethode → Hook-Methode endliche Automaten 261, 267 erben → Vererbung errare humanum est 195 erweiterte Zuweisungen 188 Euklidischer Algorithmus 349 except 149 execl 225 execle 226 execlp 225

C

Stichwortverzeichnis

execlpe 226 execv 224 execve 224 execvp 222 execvpe 223 exists 242 expandvars 242 explizite Typumwandlung 24 extend 76 extsep 228

F Fakultätsfunktion 117 – iterative Lösung 119 – rekursive Implementierung 118 – unter Benutzung eines Dekorateurs 320 Fehler – Semantik 196 – Syntax 195 Fehlerarten 195 fib → Fibonacci-Modul fiblist → Fibonacci-Modul Fibonacci 120 Fibonacci-Folge → Fibonacci-Zahlen – rekursive Berechnung 126 Fibonacci-Modul 197 – fib-Funktion 197 – fiblist-Funktion 197 Fibonacci-Zahlen – formale mathematische Definition 120 – Generator 314, 412 – Modul zur Berechnung der Fibonacci-Zahlen 197 – rekursive Berechnung mit Dekorateuren 318 – rekursive Funktion 119 filter 289 finally 153 find 78, 139 Finite State Machine 268 firstn-Generator 312 flache Kopie 38, 101 for-Schleife 62 – optionaler else-Teil 62 – StopIteration 306 – Unterschied zur while-Schleife 63 – Vergleich zu C 62 fork 228, 255 Forking 255 forkpty 228 formale Sprachen 261 fromkeys → Dictionary

Frosch – Aufgabe mit Summenbildung 66 – Straßenüberquerung 66 frozensets 49 fully qualified 90 functools 294 Funktion 103 Funktionsabschluss 318

G ganze Zahlen 22 Ganzzahlen 22 Generator – all_colours() 359 – Allgemein 305 – CLU 305 – Endlosschleife in 307 – firstn 312 – Icon 305 – isclice zur Ausgabe von unendlichen Generatoren 312 – k-Permuationen aus Zufallspermutation 359 – k-Permutationen 359 – k_permutations() 359 – pair_sum 315 – pendulum 314 – Permutationen 309, 359 – permutations() 359 – round_robin 314 – send-Methode 312 – Werte empfangen 312 – yield 307 – zur Erzeugung von Selektionen 310 Generator-Ausdrücke 313 Generator-Comprehension 302 Generatoren-Abstraktion 302 get → Dictionary get_archive_formats 251 getatime 243 getcwd 229, 247 getcwdb 229 getegid 229 getenv 213 getenvb 213 geteuid 229 get_exec_path 228 getgid 229 getgroups 229 getloadavg 229 getlogin 230 getmtime 243 getpgid 230

419

420

Stichwortverzeichnis

getpgrp 230 getpid 230 getppid 230 getresgid 230 getresuid 230 getsize 244 → os.path getter 166 getuid 230 get_unpack_formats 251 ggT 349 Girokonto 170 Gleichungssysteme 340 grafische Veranschaulichung von Matrixwerten → Hinton-Diagramm größter gemeinsamer Teiler 349 Gruppierungen 274 GUI 212 gztar-Datei → Archivdatei erzeugen

H Hash 33 Hinton-Diagramm 323 Hook-Methode 206 Hooks → Hook-Methode

isdir 245 isfile 245 isinstance 25 islice 312 – als Ersatz für firstn 312 islink 245 islower 145 ismount 246 isspace 145 istitle 145 isupper 145 itemgetter 84 items → Dictionary __iter__ 306 Iteration – for-Schleife 305 Iteratoren 305 – for-Schleife 305 itertools 312 – islice 312

J join 139, 246 Junit 204

I

K

if-Anweisung 55 ignore_patterns 251 implizite Typumwandlung 25 import-Anweisung 90 in – Dictionaries 36 index 78, 139 index-Funktionen 220 Inheritance → Vererbung __init__.py 95 inneres Produkt 335 insert 79 Instanz 155 Instanzattribute 168 integ 343 Integer 22 interaktive Shell – Befehlsstack 13 – Starten unter Linux 9 – Starten unter Windows 10 – Unterstrich 12 inverse Matrix 339 isabs 244 isalnum 145 isalpha 145 isdigit 145

kanonischer Pfad 247 Kapselung 157 Keller 75 Kellerspeicher 75, 188 KeyError 35 keys → Dictionary kill 230 killpg 231 Klasse 156 – Erzeugen von Objekten 158 – Kopf 158 – Körper 158 – minimale Klasse in Python 158 Klassenattribute 168 – Zähler zum zählen der Objekte 168 Kombination 310 Kombinatorik-Modul 359 Kompilierung von regulären Ausdrücken 280 Komponententest 197 Konstruktor 160 Kontoklasse 158, 170 – mit Unterklassen 170 Kontrollstrukturen 55 Kopieren – von Unterlisten 100 – von verschachtelten Listen 97

Stichwortverzeichnis

Kreuzprodukt 335, 340 Kuchenklasse 155 Kundenklasse → Personklasse Kürzen 349

L lambda 289 Länge eines Vektors 335 Leonardo von Pisa → Fibonacci-Zahlen lexists 246 Liber Abaci 120 Lieferant → Personklasse linalg → NumPy lineare Algebra 340 lineare Gleichungssysteme 340 linesep 231 link 231 Linux 212 – Python unter 9 list – append 75 – extend 76 – find 78 – index 78 – pop 75 – sort 82 List Comprehension 297 – Kreuzprodukt 299 – pythagoreisches Tripel 299 – Sieb des Eratosthenes 300 – Syntax 298 – Vergleich mit konventionellem Code 298 – Wandlung Celsius in Fahrenheit 299 – Weitere Beispiele 299 – zugrundeliegende Idee 299 list:count 78 list:insert 79 list:remove 77 listdir 231 list_iterator 306 Listen-Abstraktion 297 – Kreuzprodukt 299 – pythagoreisches Tripel 299 – Sieb des Eratosthenes 300 – Syntax 298 – Vergleich mit konventionellem Code 298 – Wandlung Celsius in Fahrenheit 299 – Weitere Beispiele 299 – zugrundeliegende Idee 299 ljust 144 Lösung von Gleichungssystemen 340 lower 143

lseek 219 lstat 231

M __main__ 197 major 231 make_archive 251 makedev 232 makedirs 232 map 289, 292 Mapping 33 Marvin 402 Mastermind 357 Match eines beliebigen Zeichens 266 Match-Objekte 274 Matching-Problem 264 – Over Matching 264 – Under Matching 264 MATLAB 323 Matrix-Klasse → NumPy Matrix-Produkt → NumPy Matrizenaddition 333 Matrizenmultiplikation 333, 337 Matrizensubtraktion 333 maxsplit – split 132 Mehrfachvererbung 174 – Beispiel CalendarClock 177 – Tiefensuche 174 Memoisation 123, 317 Memoization → Memoisation Memoize 319 memoize-Funktion 318 Menge 47 Mengen-Abstraktion 301 Mengenlehre 47 Methode 155, 159 – Overloading → Überladen – Overwriting → Überschreiben – Überladen 171 – Überschreiben 171 – Unterschiede zur Funktion 159 minor 231 mkdir 232 mkfifo 233 Modul – dir() 93 – Dokumentation 94 – dynamisch geladene C-Module 91 – eigene Module schreiben 93 – Inhalt 93 – lokal 90

421

422

Stichwortverzeichnis

– math 90 – Modularten 91 – Namensraum 90 – re 263 – Suchpfad 92 modulare Programmierung 89 modulares Design 89 Modularisierung 89 Modultest 197 Modultests – unter Benutzung von __name__ 197 Monty Python 34 move 252 mtime 243 __mul__ 403

N Naiver Bayes-Klassifikator 366, 367 __name__ 197 Namensraum – umbenennen 91 Neanderthal-Arithmetik 191 Nested Dictionaries 37 next 306 nice 233 Noam Chomsky 117 normcase 246 normpath 247 Nullstellen berechnen 343 Numarray → NumPy Numeric → NumPy Numeric Python → NumPy NumPy 323 – Array mit Nullen und Einsen initialisieren 332 – Array neue Dimension hinzufügen 332 – Arrays 325 – Arrays flach machen 327 – Arrays konkatenieren 330 – Arrays umdimensionieren 328 – deriv 343 – Drei-Finger-Regel 340 – Einheitsmatrix 339 – eye 339 – Gleichungssysteme 340 – inneres Produkt 335 – integ 343 – inv 339 – inverse Matrix 339 – Kreuzprodukt 335, 340 – Länge eines Vektors 335 – linalg 340

– – – – – – – – – – – – – – – – – – – – – – – –

lineare Algebra 340 lineare Gleichungssysteme 340 Lösung von Gleichungssystemen 340 Matrix-Klasse 336 Matrix-Produkt 337 Matrixarithmetik 333 Matrizenaddition 333 Matrizenmultiplikation 333, 337 Matrizensubtraktion 333 Nullstellen 341 Nullstellen berechnen 343 ones() 332 ones_like() 333 polynomial-Paket 341 Punktprodukt 335 Rechtssystem 340 roots 343 Skalarprodukt 335 Vektoraddition 334 Vektorprodukt 340 Vektorsubtraktion 334 Winkel zwischen Vektoren 335 zeros() 332 zeros_like() 333

O Oberklasse 169 object 169 Objekt 155 Objektorientierte Programmiersprache 155 Objektorientierte Programmierung 155 ones_like() 333 OOP 155 – Datenkapselung 157 – Kapselung 157 – Kuchenklasse 155 open 216 open() 69 openpty 219 operator-Modul 84 – itemgetter 84 Operator-Überladung 187 Operatoren überladen – binäre Operatoren 187 – erweiterte Zuweisungen 188 – unäre Operatoren 188 os – abort 226 – access 227 – chdir 227 – chmod 227 – chown 228

Stichwortverzeichnis

– – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

chroot 228 close 218 dup 220 execl 225 execle 226 execlp 225 execlpe 226 execv 224 execve 224 execvp 222 execvpe 223 extsep 228 fork 228 forkpty 228 getcwd 229 getcwdb 229 getegid 229 getenv 213 getenvb 213 geteuid 229 get_exec_path 228 getgid 229 getgroups 229 getloadavg 229 getlogin 230 getpgid 230 getpgrp 230 getpid 230 getppid 230 getresgid 230 getresuid 230 getuid 230 kill 230 killpg 231 linesep 231 link 231 listdir 231 lseek 219 lstat 231 major 231 makedev 232 makedirs 232 minor 231 mkdir 232 mkfifo 233 nice 233 open 216 openpty 219 popen 233 putenv 214 read 218 readlink 235

– remove 235 – removedirs 235 – rename 236 – renames 236 – rmdir 237 – sep 241 – setegid 237 – seteuid 237 – setgid 237 – setgroups 229 – setpgid 237 – setpgrp 237 – setregid 237 – setresgid 237 – setresuid 237 – setreuid 237 – setsid 237 – setuid 237 – stat 237 – stat_float_times 238 – strerror 238 – supports_bytes_environ 215 – symlink 238 – sysconf 239 – system 239 – times 239 – umask 239 – uname 239 – unlink 239 – unsetenv 215 – urandom 239 – utime 240 – wait 240 – wait3 240 – waitpid 240 – walk 240 – write 215 os-Modul 212, 226 os.path 240 os.python – abspath 240 – basename 241 – commonprefix 241 – dirname 241 – exists 242 – expandvars 242 – getatime 243 – getmtime 243 – getsize 244 – isabs 244 – isdir 245 – isfile 245

423

424

Stichwortverzeichnis

– islink 245 – ismount 246 – join 246 – lexists 246 – normcase 246 – normpath 247 – realpath 247 – relpath 247 – samefile 248 – split 248 – splitdrive 248 – splitext 248 Over Matching 264 Overloading → Überladen overloading 172 Overwriting → Überschreiben

P Paket 95 Paketkonzept 95 Parameterliste 104 partion 139 Pascalsches Dreieck 124 – Fibonacci-Zahlen 125 peek 75 Permutationen 309 – mit Wiederholungen 309 – ohne Wiederholungen 309 Permutations-Generator 309 Personklasse → Vererbung Pfadname – absolut 244 – relativ 244 Polymorphismus 185 Polynome 341 pop 75, 188 → Dictionary popen 233 popitem → Dictionary Private Attribute – lesender und schreibender Zugriff mittels Methoden 166 Programmiersprache – Unterschied zu Skriptsprache 17 Properties 166 Punktprodukt 335 push 75, 188 putenv 214

Q Quantoren 272

R random-Methode – sample 311 read 218 readlink 235 re-Modul 263 realpath 247 Rechtssystem 340 reduce 294 Regeln zur Interpretation von römischen Zahlen 385 register_archive_format 253 register_unpack_format 253 reguläre Ausdrücke 261 – Alternativen 279 – Anfang eines Strings matchen 269 – beliebiges Zeichen matchen 266 – compile 280 – Ende eines Strings matchen 269 – Kompilierung 280 – optionale Teile 271 – Qunatoren 272 – Teilstringsuche 263 – Überlappungen 263 – vordefinierte Zeichenklassen 268 – Wiederholungen von Teilausdrücken 272 – Zeichenauswahl 266 reguläre Auswahl – Caret-Zeichen, Zeichenklasse 267 reguläre Mengen 261 reguläre Sprachen 261 Rekursion 117 – Beispiel aus der natürlichen Sprache 117 rekursiv → Rekursion rekursive Funktion 117, 118 relpath 247 remove 77, 235 removedirs 235 rename 236 renames 236 replace 142 __repr__ 400 – Unterschied zu __str__ 400 rfind 139 rindex 139 rjust 144 rmdir 237 rmtree 253 Robot-Klasse 400 Roboterklasse 189 römische Zahlen 65 roots 343

Stichwortverzeichnis

Rossum, Guido van 289 round_robin 314 rsplit 136 – Folge von Trennzeichen 137 rstrip 143 Rückwärtsreferenzen 274

S samefile 248 sample 311 Schachbrett mit Weizenkörnern 66 Schaltjahre 58 Schleifen 55, 59 – Endekriterium 59 – for-Schleife 62 – Schleifendurchlauf 59 – Schleifenkopf 59 – Schleifenkörper 59 – Schleifenzähler 59 – while-Schleife 60 Schleifenzähler 59 Schlüssel – Dictionary 33 – zulässige Typen 36 Scientific Python → SciPy SciPy 323 Sekunden – Additon zu Uhrzeiten 58 Semantik 196 – Fehler 196 semantische Fehler 196 send-Methode 312 sep 241 set 47 set comprehension 301 setdefault → Dictionary setegid 237 seteuid 237 setgid 237 setgroups 229 setpgid 237 setpgrp 237 setregid 237 setresgid 237 setresuid 237 setreuid 237 sets – copy 50 – difference_update 51 – discard 51 – intersection 52 – isdisjoint 52

– issubset 53 – issuperset 53 – Operationen auf sets 49 – pop 53 – remove 52 setsid 237 setter 166 setuid 237 setUp-Methode 206 Shell 211 – Bash 212 – Bourne 212 – C-Shell 212 – CLI 212 – GUI 212 – Herkunft und Bedeutung des Begriffes 211 Shihram 66 shutil – copy 249 – copy2 249 – copyfile 249 – copyfileobj 249 – copymode 249 – copystat 250 – copytree 250 – get_archive_formats 251 – get_unpack_formats 251 – ignore_patterns 251 – make_archive 251 – move 252 – register_archive_format 253 – register_unpack_format 253 – rmtree 253 – unpack_archive 253 – unregister_archive 254 – unregister_unpack_format 254 shutil-Modul 249 Sieb des Eratosthenes 300 – Rekursive Berechnung 125 – Rekursive Funktion mit Mengen-Abstraktion 301 Simula 67 155 Skalarprodukt 335 Skriptsprache – Unterschied zu Programmiersprache 17 sort 82 – eigene Sortierfunktionen 83 – reverse 83 – Umkehrung der Sortierreihenfolge 83 sorted 82 Sparbuch 170 spezialisierte Klasse → Unterklasse

425

426

Stichwortverzeichnis

splice 192 split 132, 248 – Folge von Trennzeichen 137 – maxsplit 132 splitdrive 248 splitext 248 splitlines 138 Sprachfamilie 261 Stack 75 – Stapelspeicher 75 Standardbibliothek 90 Standardklassen als Basisklassen 188 Stapelspeicher 75, 188 stat 237 stat_float_times 238 statische Attribute 168 statische Typdeklaration 23 Stephen Cole Kleene 261 StopIteration 306 __str__ 349, 400 – Unterschied zu __repr__ 400 strerror 238 Strings – Suchen und Ersetzen 142 String-Tests 145 Stringmethoden – Alles in Großbuchstaben 143 – Alles in Kleinbuchstaben 143 – capitalize 143 – center 144 – count 139 – find 139 – index 139 – isalnum 145 – isalpha 145 – isdigit 145 – islower 145 – isspace 145 – istitle 145 – isupper 145 – ljust 144 – lower 143 – replace 142 – rfind 139 – rindex 139 – rjust 144 – rstrip 143 – String-Tests 145 – strip 143 – title 143 – upper 143 – zfill 144

strip 143 Strukturierungselement 103 __sub__ 403 Subklasse → Unterklasse Suchen und Ersetzen 142 Suchen von Teilstrings 139 Suchstrategie bei Mehrfachvererbung 174 Summe von n Zahlen 60 – Berechnung mit while-Schleife 60 SUnit 204 supports_bytes_environ 215 symlink 238, 245 syntaktische Fehler 195 Syntax 195 – Fehler 195 Syntaxfehler 195 sysconf 239 system 239 Systemprogrammierung 211

T tar-Datei → Archivdatei erzeugen TDD → test-driven development tearDown-Methode 206 Tests 195 test first development 204 test-driven development 204 TestCase 206 – Methoden 206 – setUp-Methode 206 – tearDown-Methode 206 testCase 204 Testgesteuerte Entwicklung 204 Testgetriebene Entwicklung 202, 204 Testmethoden 206 Textklassifikation 365 Textverarbeitung 131 Theoretische Informatik 261 tiefe Kopie 38, 101 Tiefensuche 174 times 239 __truediv__ 403 try 149 Tupel – leere 80 – mit einem Element 80 – Packing 80 – Unpacking 80 type 25 type conversion 24 TypeError 199 – unhashable type 37

Stichwortverzeichnis

Typumwandlung 24 – explizit 24 – implizit 25

U Überladen 171 – von Methoden 185 Überlappungen 263 Überschreiben 171 umask 239 Umgebungsvariablen 213 uname 239 unäre Operatoren 188 Unärsystem 191 Unary System → Unärsystem Under Matching 264 unhashable type 37 unittest 204 → Modultest Unix 212 unlink 239 unpack_archive 253 unregister_archive_format 254 unregister_unpack_format 254 unsetenv 215 Unterklasse 156, 169 Unterstrich – Bedeutung in der interaktiven Shell 12 update → Dictionary upper 143 urandom 239 utime 240

V Variablen 21 Variation 310 Vektoraddition 334 Vektorprodukt 340 Vektorsubtraktion 334

Vererbung 158, 169 – Beispiel Angestelltenklasse, die von Person erbt 169 – Beispiel Kundenklasse, die von Person erbt 169 – Beispiel Lieferantenklasse, die von Person erbt 169 – Beispiel Personenklasse 169 Vererbungsbaum 174 verschachtelte Dictionaries 37 Verzeichnis löschen 237 Verzweigungen 55 Vollstandige Rekursion 117

W wait 240 wait3 240 waitpid 240 walk 240 Weizenkornaufgabe 66 while-Schleife 60 – optionaler else-Teil 61 Winkel zwischen Vektoren 335 write 215

Y yield 307 – im Vergleich zur return-Anweisung 307

Z Zahlenratespiel 61 Zeichenauswahl 266 Zeitrechnung 58 zeros_like() 333 zfill 144 zip-Datei → Archivdatei erzeugen Zufallspermutation 359 Zustandsautomat 268 Zustandsmaschine 268 Zuweisung 21

427

EINFÜHRUNG IN PYTHON 3 // ■ Besonders geeignet für Programmieranfänger, aber auch für Umsteiger von anderen Sprachen wie C, C++ oder Perl ■ Systematische und praxisnahe Einführung in die Kunst der Programmierung ■ Praxisnahe Übungen mit ausführlich dokumentierten Musterlösungen zu jedem Kapitel

Kann man in einer Woche programmieren lernen? Genauer gesagt: Kann man in einer Woche mit Python programmieren lernen? Es ist möglich! Dies erfährt der Autor des Buches regelmäßig in seinen fünftägigen Python-Kursen, die sowohl von Programmier-Neulingen als auch von Programmierern mit Erfahrung in anderen Programmiersprachen besucht werden. Die wesentlichen Begriffe und Techniken der Programmierung und die zugrundeliegenden Ideen werden hier anschaulich erklärt. Zu den Problemstellungen werden typische Beispiele zur Verdeutlichung verwendet, die sich leicht auf andere Anwendungsfälle übertragen lassen. Und die Übungsaufgaben mit ausführlich dokumentierten Musterlösungen helfen nicht nur, den Stoff zu vertiefen, sondern zeigen vor allem auch exemplarische Vorgehensweisen, die in vielen anderen Bereichen Verwendung finden können. In diesem Buch werden alle grundlegenden Sprachelemente von Python 3 behandelt und auf dieser Basis auch weiterführende Themen wie Systemprogrammierung, Threads, Forks, Ausnahmebehandlung und Modultests. In interessanten Anwendungsfällen werden alle erlernten Konzepte zusammengeführt: Design einer Klasse »Bruch« für Bruchrechnung, Implementierung des Spieles Mastermind und eine komplette Einführung in die automatische Dokumentenklassifikation.

Der Diplom-Informatiker Bernd KLEIN ist Inhaber des international agierenden Schulungsanbieters Bodenseo. Er studierte Informatik an der Universität des Saarlandes. Im Laufe seines Studiums und seiner beruflichen Tätigkeit hat er sich mit vielen Programmiersprachen intensiv beschäftigt, in den letzten Jahren jedoch auf Python spezialisiert. Besondere Anerkennung finden seine Python-Webseiten www.python-kurs.eu und www.python-course.eu.

AUS DEM INHALT // ■ Erste Schritte mit Python ■ Datentypen: Listen, Tupel, Mengen und Dictionaries ■ Verzweigungen ■ Schleifen mit while und for ■ Modularisierung ■ Funktionen und rekursive Funktionen ■ Listen- und Mengenabstraktion im Vergleich zu »lambda«, »map« und »filter« ■ Text- und Dateiverarbeitung ■ Reguläre Ausdrücke ■ Ausnahmebehandlung ■ Objektorientierte Programmierung ■ Module ■ Anwendungsbeispiele: Bruchklasse, Mastermind und Textklassifikation

€ 24,99 [D] | € 25,70 [A] ISBN 978-3-446-43547-6

www.hanser-fachbuch.de/computer

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF