Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. bernd KLEIN EINFÜHRUNG IN PYTHON FÜR EIN- UND UMSTEIGER 3. Auflage Im Internet: Musterlösungen zu den Übungen 3 Klein Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Einführung in Python 3 Bleiben Sie auf dem Laufenden! Unser Computerbuch-Newsletter informiert Sie monatlich über neue Bücher und Termine. Profitieren Sie auch von Gewinnspielen und exklusiven Leseproben. Gleich anmelden unter www.hanser-fachbuch.de/newsletter Hanser Update ist der IT-Blog des Hanser Verlags mit Beiträgen und Praxistipps von unseren Autoren rund um die Themen Online Marketing, Webentwicklung, Programmierung, Softwareentwicklung sowie IT- und Projektmanagement. Lesen Sie mit und abonnieren Sie unsere News unter www.hanser-fachbuch.de/update Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Bernd Klein Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Bernd Klein Einführung Python Einführung ininPython 33 Für Ein- und Umsteiger Für Ein- und Umsteiger 3., überarbeitete Auflage 3., überarbeitete Auflage Der Autor: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Bernd Klein, bernd@python-kurs.eu Alle in diesem Buch enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autor und Verlag übernehmen infolgedessen keine juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen – oder Teilen davon – entsteht. Ebenso übernehmen Autor und Verlag keine Gewähr dafür, dass beschriebene Verfahren usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Buch berechtigt deshalb auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und MarkenschutzGesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Bibliografische Information der Deutschen Nationalbibliothek: Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar. Dieses Werk ist urheberrechtlich geschützt. Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren) – auch nicht für Zwecke der Unterrichtsgestaltung – reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden. © 2018 Carl Hanser Verlag München, www.hanser-fachbuch.de Lektorat: Brigitte Bauer-Schiewek Herstellung: Irene Weilhart Copy editing: Jürgen Dubau, Freiburg/Elbe Layout: le-tex publishing services, Leipzig Umschlagdesign: Marc Müller-Bremer, www.rebranding.de, München Umschlagrealisation: Stephan Rönigk Druck und Bindung: Kösel, Krugzell Ausstattung patentrechtlich geschützt. Kösel FD 351, Patent-Nr. 0748702 Printed in Germany Print-ISBN: 978-3-446-45208-4 E-Book-ISBN: 978-3-446-45387-6 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Inhalt Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XVII Danksagung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XVIII 1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1 Einfach und schnell zu lernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Zielgruppe des Buches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.3 Aufbau des Buches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.4 Programmieren lernen „interaktiv” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.5 Download der Beispiele und Hilfe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.6 Anregungen und Kritik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Kommandos und Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Teil I 2 2.1 2.2 Erste Schritte mit Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1.1 Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1.2 Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.1.3 macOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Herkunft und Bedeutung des Begriffes interaktive Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 9 Erste Schritte in der interaktiven Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.3 Verlassen der Python-Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.4 Benutzung von Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.5 Mehrzeilige Anweisungen in der interaktiven Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.6 Programme schreiben oder schnell mal der Welt “Hallo” sagen . . . . . . . . . . . . . . . . . . 13 3 Bytecode und Maschinencode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2 Unterschied zwischen Programmier- und Skriptsprachen . . . . . . . . . . . . . . . . . . . . . . . . 17 3.3 Interpreter- oder Compilersprache. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 VI Inhalt 4 Datentypen und Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.2 Variablennamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 4.3 4.2.1 Gültige Variablennamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.2.2 Konventionen für Variablennamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4.3.1 Ganze Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4.3.2 Fließkommazahlen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.3.3 Zeichenketten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.3.4 Boolesche Werte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.3.5 Komplexe Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.3.6 Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.4 Statische und dynamische Typdeklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4.5 Typumwandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.6 Datentyp ermitteln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5 Sequentielle Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 5.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 5.1.1 Zeichenketten oder Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 5.1.2 Listen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 5.1.3 Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 5.2 Indizierung von sequentiellen Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 5.3 Teilbereichsoperator. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 5.4 Die len-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 5.5 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 6 Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 6.1 Dictionaries und assoziative Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 6.2 Definition und Benutzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 6.3 Fehlerfreie Zugriffe auf Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 6.4 Zulässige Typen für Schlüssel und Werte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 6.5 Verschachtelte Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 6.6 Methoden auf Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 6.7 Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 6.8 Die zip-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 6.9 Dictionaries aus Listen erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 6.10 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Inhalt 7 Mengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 7.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 7.2 Mengen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 7.3 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 7.4 7.2.1 Sets erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 7.2.2 Mengen von unveränderlichen Elementen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 Frozensets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Operationen auf „set”-Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.4.1 add(element). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.4.2 clear() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 7.4.3 copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 7.4.4 difference() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 7.4.5 difference_update() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 7.4.6 discard(el) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 7.4.7 remove(el) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.4.8 intersection(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.4.9 isdisjoint() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.4.10 issubset() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 7.4.11 issuperset() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 7.4.12 pop() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 8 Eingaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 8.1 Eingabe mittels input. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 9 Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 9.1 Anweisungsblöcke und Einrückungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 9.2 Bedingte Anweisungen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 9.2.1 Einfachste if-Anweisung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 9.2.2 if-Anweisung mit else-Zweig. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 9.2.3 elif-Zweige . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 9.3 Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 9.4 Zusammengesetzte Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 9.5 Wahr oder falsch: Bedingungen in Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 9.6 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 10 Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 10.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 10.2 while-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 10.3 break und continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 10.4 die Alternative im Erfolgsfall: else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 10.5 For-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 10.6 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 VII Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. VIII Inhalt 11 Dateien lesen und schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 11.1 Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 11.2 Text aus einer Datei lesen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 11.3 Schreiben in eine Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 11.4 In einem Rutsch lesen: readlines und read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 11.5 with-Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 11.6 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 12 Formatierte Ausgabe und Strings formatieren . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 12.1 Wege, die Ausgabe zu formatieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 12.2 print-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 12.3 Stringformatierung im C-Stil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 12.4 Der pythonische Weg: Die String-Methode „format” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 12.5 Benutzung von Dictionaries beim Aufruf der „format”-Methode . . . . . . . . . . . . . . . . 102 12.6 Benutzung von lokalen Variablen in „format” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 12.7 Formatierte Stringliterale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 12.8 Weitere String-Methoden zum Formatieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 13 Flaches und tiefes Kopieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 13.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 13.2 Kopieren einer Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 13.3 Flache Kopien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 13.4 Kopieren mit deepcopy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 13.5 Deepcopy für Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 14 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 14.1 Allgemein . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 14.2 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 14.3 Docstring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 14.4 Standardwerte für Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 14.5 Schlüsselwortparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 14.6 Funktionen ohne oder mit leerer return-Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 14.7 Mehrere Rückgabewerte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 14.8 Lokale und globale Variablen in Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 14.9 Parameterübergabe im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 14.10 Effekte bei veränderlichen Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 14.11 Kommandozeilenparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 14.12 Variable Anzahl von Parametern / Variadische Funktionen . . . . . . . . . . . . . . . . . . . . . . . 126 14.13 * in Funktionsaufrufen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Inhalt 14.14 Beliebige Schlüsselwortparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 14.15 Doppeltes Sternchen im Funktionsaufruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 14.16 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 15 Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 15.1 Definition und Herkunft des Begriffs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 15.2 Definition der Rekursion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 15.3 Rekursive Funktionen in Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 15.4 Die Tücken der Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 15.5 Fibonacci-Folge in Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 15.6 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 16 Listen und Tupel im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 16.1 Stapelspeicher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 16.2 Stapelverarbeitung in Python: pop und append. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 16.3 extend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 16.4 ,+’-Operator oder append . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 16.5 Entfernen eines Wertes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 16.6 Prüfen, ob ein Element in Liste enthalten ist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 16.7 Finden der Position eines Elementes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 16.8 Einfügen von Elementen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 16.9 Besonderheiten bei Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 16.9.1 Leere Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 16.9.2 1-Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 16.9.3 Mehrfachzuweisungen, Packing und Unpacking . . . . . . . . . . . . . . . . . . . . . . . . . . 149 16.10 Die veränderliche Unveränderliche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 16.11 Sortieren von Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 16.11.1 „sort” und „sorted” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 16.11.2 Umkehrung der Sortierreihenfolge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 16.11.3 Eigene Sortierfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 16.12 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 17 Modularisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 17.1 Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 17.1.1 Namensräume von Modulen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 17.1.2 Namensräume umbenennen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 17.1.3 Modularten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 17.1.4 Suchpfad für Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 17.1.5 Inhalt eines Modules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 IX X Inhalt Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 17.2 17.1.6 Eigene Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 17.1.7 Dokumentation für eigene Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Pakete. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 17.2.1 Einfaches Paket erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 17.2.2 Komplexeres Paket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 17.2.3 Komplettes Paket importieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 18 Globale und lokale Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 18.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 18.2 Globale und lokale Variablen in Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 19 Alles über Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 19.1 ... fast alles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 19.2 Aufspalten von Zeichenketten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 19.2.1 split . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 19.2.2 Standardverhalten und „maxsplit” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 19.2.3 rsplit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 19.2.4 Folge von Trennzeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 19.2.5 splitlines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182 19.2.6 partition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 19.3 Zusammenfügen von Stringlisten mit join . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 19.4 Suchen von Teilstrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 19.4.1 „in” oder „not in” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 19.4.2 s.find(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 19.4.3 s.rfind(substring[, start[, end]]). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 19.4.4 s.index(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 19.4.5 s.rindex(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 19.4.6 s.count(substring[, start[, end]]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 19.5 Suchen und Ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 19.6 Nur noch Kleinbuchstaben oder Großbuchstaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 19.7 capitalize und title . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 19.8 Stripping Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 19.9 Strings ausrichten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 19.10 String-Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 19.11 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Inhalt 20 Ausnahmebehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 20.1 Abfangen mehrerer Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 20.2 except mit mehrfachen Ausnahmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 20.3 Die optionale else-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 20.4 Fehlerinformationen über sys.exc_info . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 20.5 Exceptions generieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 20.6 Finalisierungsaktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 Teil II Objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 21 Grundlegende Aspekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 21.1 Bibliotheksvergleich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 21.2 Objekte und Instanzen einer Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 21.3 Kapselung von Daten und Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 21.4 Eine minimale Klasse in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 21.5 Eigenschaften und Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 21.6 Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 21.7 Instanzvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 21.8 Die __init__-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 21.9 Destruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip . . . . . . . . . . . . . . . . . . . . . . . 212 21.10.1 Definitionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 21.10.2 Zugriffsmethoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 21.10.3 Properties. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 21.10.4 Public-, Protected- und Private-Attribute. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 21.10.5 Weitere Möglichkeiten der Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 21.10.6 Properties mit Dekorateuren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 21.11 Die __str__- und die __repr__-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 21.12 Klassenattribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 21.13 Statische Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 21.13.1 Einleitendes Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 21.13.2 Getter und Setter für private Klassenattribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 21.14 Public-Attribute statt private Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 21.15 Magische Methoden und Operatorüberladung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 21.15.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 21.15.2 Übersicht magische Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 21.15.3 Beispielklasse: Length . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 XI XII Inhalt 22 Bruchklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 22.1 Brüche à la 1001 Nacht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 22.2 Zurück in die Gegenwart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 22.3 22.4 Rechenregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 22.3.1 Multiplikation von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 22.3.2 Division von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 22.3.3 Addition von Brüchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 22.3.4 Subtraktion von Brüchen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 22.3.5 Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 Integer plus Bruch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 22.4.1 Die Bruchklasse im Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 22.5 Fraction-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 23 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 23.1 Oberbegriffe und Oberklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 23.2 Ein einfaches Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 23.3 Überladen, Überschreiben und Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253 23.4 Vererbung in Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 23.5 Klassenmethoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 23.6 Standardklassen als Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 24 Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 24.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 24.2 Beispiel: CalendarClock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264 24.3 Diamand-Problem oder „deadly diamond of death” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 24.4 super und MRO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 25 Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 25.1 Erzeugung von dynamischen Attributen verhindern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Dynamische Erzeugung von Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 26.1 Beziehung zwischen „class” und „type” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Metaklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 27.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.2 Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290 27.3 Definition von Metaklassen in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290 27.4 Singletons mit Metaklassen erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 279 281 285 Inhalt Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 27.5 Beispiel – Methodenaufrufe zählen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 27.5.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 27.5.2 Vorbereitungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 27.5.3 Ein Dekorateur, um Funktionsaufrufe zu zählen . . . . . . . . . . . . . . . . . . . . . . . . . . 295 27.5.4 Die Metaklasse „Aufrufzähler” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 28 Abstrakte Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297 29 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301 Teil III Fortgeschrittenes Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 30 lambda, map, filter und reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307 30.1 lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307 30.2 map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 30.3 Filtern von sequentiellen Datentypen mittels „filter” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312 30.4 reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 30.5 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314 31 Listen-Abstraktion/List Comprehension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315 31.1 Die Alternative zu Lambda und Co. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315 31.2 Syntax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316 31.3 Weitere Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316 31.4 Die zugrunde liegende Idee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 31.5 Anspruchsvolleres Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318 31.6 Mengen-Abstraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318 31.7 Rekursive Primzahlberechnung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 31.8 Generatoren-Abstraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 31.9 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320 32 Generatoren und Iteratoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323 32.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323 32.2 Iteration in for-Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323 32.3 Generatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325 32.4 Generatoren zähmen mit firstn und islice. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327 32.5 32.6 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328 32.5.1 Permutationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328 32.5.2 Variationen und Kombinationen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 Generator-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331 XIII Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. XIV Inhalt 32.7 return-Anweisungen in Generatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 32.8 send-Methode / Koroutinen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 32.9 Die throw-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334 32.10 Dekoration von Generatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336 32.11 yield from . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 32.12 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339 33 33.1 Dekorateure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 Einführung Dekorateure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 33.1.1 Verschachtelte Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 33.1.2 Funktionen als Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343 33.1.3 Funktionen als Rückgabewert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344 33.1.4 Fabrikfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345 33.2 Ein einfacher Dekorateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 33.3 @-Syntax für Dekorateure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 33.4 Anwendungsfälle für Dekorateure. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350 33.4.1 Überprüfung von Argumenten durch Dekorateure . . . . . . . . . . . . . . . . . . . . . . . 350 33.4.2 Funktionsaufrufe mit einem Dekorateur zählen. . . . . . . . . . . . . . . . . . . . . . . . . . . 351 33.5 Dekorateure mit Parametern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352 33.6 Benutzung von Wraps aus functools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 33.7 Klassen statt Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355 33.7.1 Die __call__-Methode. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355 33.8 Eine Klasse als Dekorateur benutzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357 33.9 Memoisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 33.9.1 Bedeutung und Herkunft des Begriffs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 33.9.2 Memoisation mit Dekorateur-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 33.9.3 Memoisation mit einer Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359 33.9.4 Memoisation mit functools.lru_cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 Teil IV Weiterführende Themen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 34 Tests und Fehler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 34.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 34.2 Modultests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367 34.3 Modultests unter Benutzung von __name__ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 34.4 doctest-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370 34.5 Testgetriebene Entwicklung oder „Im Anfang war der Test” . . . . . . . . . . . . . . . . . . . . . . . 373 34.6 unittest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375 34.7 Methoden der Klasse TestCase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377 34.8 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380 Inhalt 35 Daten konservieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381 35.1 Persistente Speicherung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381 Pickle-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382 35.2.1 Daten „einpökeln” mit pickle.dump . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 35.2 35.2.2 pickle.load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383 35.3 Ein persistentes Dictionary mit shelve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383 36 Reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387 36.1 Ursprünge und Verbreitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387 36.2 Stringvergleiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387 36.3 Überlappungen und Teilstrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389 36.4 Das re-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389 36.5 Matching-Problem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390 36.6 Syntax der regulären Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 36.6.1 Beliebiges Zeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 36.7 Zeichenauswahl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393 36.8 Endliche Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 36.9 Anfang und Ende eines Strings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 36.10 Vordefinierte Zeichenklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397 36.11 Optionale Teile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398 36.12 Quantoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399 36.13 Gruppierungen und Rückwärtsreferenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 36.13.1 Match-Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 36.14 Umfangreiche Übung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404 36.15 Alles finden mit findall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406 36.16 Alternativen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407 36.17 Compilierung von regulären Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407 36.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck . . . . . . . . . . . . . . . . . . . . . . 409 36.18.1 split-Methode der String-Klasse. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409 36.18.2 split-Methode des re-Moduls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410 36.18.3 Wörter filtern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412 36.19 Suchen und Ersetzen mit sub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 36.20 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414 37 Typanmerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417 37.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417 37.2 Einfaches Beispiel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 37.3 Variablenanmerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419 37.4 Listenbeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 37.5 typing-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421 XV XVI Inhalt 38 Systemprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 38.1 Systemprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 38.2 Häufig falsch verstanden: Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 38.3 os-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 38.3.1 Vorbemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427 38.3.2 Umgebungsvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427 38.3.3 Dateiverarbeitung auf niedrigerer Ebene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429 38.3.4 Die exec-„Familie” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434 38.3.5 Weitere Funktionen im Überblick. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441 38.3.6 os.path - Arbeiten mit Pfaden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455 38.4 shutil-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463 39 Forks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469 39.1 Fork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469 39.2 Fork in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469 Teil V Lösungen zu den Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473 40 Lösungen zu den Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475 40.1 Lösungen zu Kapitel 5 (Sequentielle Datentypen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475 40.2 Lösungen zu Kapitel 6 (Dictionaries) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478 40.3 Lösungen zu Kapitel 9 (Verzweigungen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480 40.4 Lösungen zu Kapitel 10 (Schleifen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482 40.5 Lösungen zu Kapitel 11 (Dateien lesen und schreiben) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485 40.6 Lösungen zu Kapitel 16 (Listen und Tupel im Detail) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487 40.7 Lösungen zu Kapitel 14 (Funktionen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490 40.8 Lösungen zu Kapitel 15 (Rekursive Funktionen) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495 40.9 Lösungen zu Kapitel 19 (Alles über Strings . . . ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499 40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502 40.11 Lösungen zu Kapitel 34 (Tests und Fehler) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512 40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512 40.13 Lösungen zu Kapitel 30 (lambda, map, filter und reduce) . . . . . . . . . . . . . . . . . . . . . . . . . 518 40.14 Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension) . . . . . . . . . . . . . . 519 40.15 Lösungen zu Kapitel 32 (Generatoren und Iteratoren) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 520 Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Vorwort Ist es wirklich so, dass Vorwörter – ähnlich wie Bedienungsanleitungen – meistens nicht gelesen werden? Auch wenn dies sicherlich für viele zutreffen mag, so gibt es Situationen, in denen gerade ein Vorwort wertvolle Dienste leisten kann. Zum Beispiel, um die Kaufentscheidung für ein Buch zu erleichtern. So stehen auch Sie jetzt vielleicht am Regal einer guten Buchhandlung und werden möglicherweise von zwei Fragen bewegt: Sie brauchen noch eine Bestätigung, dass Python die richtige Programmiersprache für Sie ist, und möchten wissen, wie Ihnen dieses Buch helfen wird, die Sprache schnell und effizient zu erlernen. Für Python spricht der traumhafte Anstieg der Bedeutung in Wissenschaft, Forschung und Wirtschaft in den letzten Jahren. Im renommierten PYPL-Index stand Python im Juli 2017 auf dem zweiten Platz hinter Java. Während Java im Vergleich zum Vorjahr jedoch -1,1 % Anteil verloren hatte, gewann Python +4,0 % hinzu. Weitere wichtige Gründe, Python zu lernen, sind: Python ist mit seiner einfachen natürlichen Syntax sehr einfach zu lernen. Python läuft auf allen Plattformen und erfreut sich auch beim Raspberry Pi größter Beliebtheit. Außerdem ist Python eine universell einsetzbare Programmiersprache, die sich bei den Aufgaben der Systemadministration ebenso effektiv einsetzen lässt wie im Maschinenbau, der Linguistik, der Pharmaindustrie, in der Banken- und Finanzwelt, der Physik und vielen anderen Bereichen. Weil Firmen wie Google Python lieben und unterstützen, wird Python auch kontinuierlich weiterentwickelt. Dieses Buch erscheint nun in der dritten, weitgehend überarbeiteten Ausgabe. Es eignet sich ebenso für Programmieranfänger als auch für Umsteiger von anderen Programmiersprachen. Behandelt werden nicht nur alle grundlegenden Sprachelemente von Python, sondern auch weiterführende Themen wie Generatoren, Dekorateure, Systemprogrammierung, Threads, Forks, Ausnahmebehandlungen und Modultests. Auf über Hundert Seiten wird anschaulich und mit zahlreichen Beispiele auf die vielfältigen Aspekte der Objektorientierung eingegangen. Brigitte Bauer-Schiewek, Lektorin Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Danksagung Zum Schreiben eines Buches benötigt es neben der nötigen Erfahrung und Kompetenz im Fachgebiet vor allem viel Zeit. Zeit außerhalb des üblichen Rahmens. Zeit, die vor allem die Familie mitzutragen hat. Deshalb gilt mein besonderer Dank meiner Frau Karola, die mich während dieser Zeit tatkräftig unterstützt hat. Außerdem danke ich den zahlreichen Teilnehmerinnen und Teilnehmern an meinen Python-Kursen, die mir geholfen haben, meine didaktischen und fachlichen Kenntnisse kontinuierlich zu verbessern. Ebenso möchte ich den Besucherinnen und Besuchern meiner Online-Tutorials unter www.python-kurs.eu und www.python-course.eu danken, vor allem jenen, die sich mit konstruktiven Anmerkungen bei mir gemeldet haben. Wertvolle Anregungen erhielt ich auch von denen, die das Buch vorab Korrektur gelesen hatten: Stefan Günther für die Erstauflage des Buches. Für die Hilfe zur zweiten – weitestgehend überarbeiteten – Auflage möchte ich im besonderen Maße Herrn Jan Lendertse und Herrn Vincent Bermel Dank sagen. Für die dritte – wiederum stark veränderte – Auflage danke ich Herrn Wilhelm Wall für seine wertvolle Hilfe, insbesondere Tests unter macOS. Zuletzt danke ich auch ganz herzlich dem Hanser Verlag, der dieses Buch – nun auch in der dritten Auflage – ermöglicht hat. Vor allem danke ich Frau Brigitte Bauer-Schiewek, Programmplanung Computerbuch, für die kontinuierliche ausgezeichnete Unterstützung. Für die technische Unterstützung bei LaTeX-Problemen danke ich Herrn Stephan Korell und Frau Irene Weilhart. Herrn Jürgen Dubau danke ich fürs Lektorat. Bernd Klein, Singen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 1 1.1 Einleitung Einfach und schnell zu lernen Python ist eine Programmiersprache, die in vielerlei Hinsicht begeistert. Ein riesiger Vorteil im Vergleich zu anderen Sprachen liegt in der leichten Erlernbarkeit der Sprache. In der ersten Auflage führte das Buch den Untertitel „In einer Woche programmieren lernen”. Kann man wirklich in so kurzer Zeit programmieren lernen? Das ist möglich! Vor allem mit Python. Wir erfahren es mehrmals im Monat in unseren meist fünftägigen Python-Kursen. Sie werden zum einen von totalen Programmieranfängern und zum anderen von Programmierern mit Erfahrungen in C, C++, Java, Perl und anderen Programmiersprachen besucht. Manche haben auch schon vorher Erfahrungen in Python gesammelt. Aber eines ist in allen Gruppen gleich: Wir haben es immer geschafft, dass jeder programmieren gelernt hat, und vor allen Dingen – was uns am wichtigsten ist – konnten wir immer die Begeisterung für die Sprache Python entfachen. So ist es auch eines der wichtigsten Ziele dieses Buches, die Leserinnen und Leser möglichst schnell und mit viel Freude zum selbständigen Programmieren zu bringen. Komplexe Sachverhalte werden in einfachen leicht verständlichen Diagrammen veranschaulicht und an kleinen Beispielen eingeübt, die sich im Laufe vieler Schulungen herausgebildet und bewährt haben. 1.2 Zielgruppe des Buches Beim Schreiben eines Buches hat man ein Gegenüber vor sich, eine Person, die das Geschriebene lesen, mögen und verstehen soll. Diese Person kann sowohl eine Frau als auch ein Mann sein, dennoch werden wir im Text meistens zum Beispiel nur vom „Benutzer”1 sprechen. Wörter wie „BesucherInnen” sehen unserer Meinung nach hässlich aus, und objektiv betrachtet erschweren sie die Lesbarkeit eines Textes. Ebenso hemmen Formulierungen der Art „als Benutzerin oder Benutzer gilt .... er oder sie .... sollten sein oder ihr ...” den Lesefluss und lenken vom eigentlichen Ziel ab, also Python schnell und problemlos 1 als Übersetzung des englischen Fachbegriffs user 2 1 Einleitung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. zu erlernen. Dennoch versuchen wir, wo immer möglich, geschlechtsspezifische Formulierungen zu vermeiden, weil wir niemanden ausschließen wollen. Natürlich hatten wir beim Schreiben aber nicht nur eine Person im Blickfeld, sondern eine ganze Leserschar. Da sind zum einen diejenigen, die noch nie programmiert haben und Sachverhalte erklärt haben wollen, die Leute mit Programmiererfahrung in anderen Sprachen vielleicht als „trivial” oder „selbstverständlich” bezeichnen würden. Aber hier ist ein Buch wohl dem Präsenzunterricht, also wenn Lehrende und Lernende am gleichen Ort zusammen sind, deutlich überlegen: Ist einem der Stoff eines Abschnitts oder sogar Kapitels bereits vertraut, kann man es einfach überspringen, bevor man sich zu langweilen beginnt. Ebenso können, falls keine Kenntnisse in anderen Programmiersprachen vorhanden sind, Abschnitte übersprungen werden, in denen wir Ähnlichkeiten, aber auch generelle Unterschiede in der Vorgehensweise von Python im Vergleich zu anderen Sprachen herausarbeiten. In der obigen Aufzählung fehlt aber noch eine wichtige Gruppe, nämlich diejenigen, die schon Erfahrungen mit Python haben. Dies ist eine Gruppe mit einer breiten Streuung: angefangen bei denjenigen, die bereits ein wenig reingeschnuppert haben, gefolgt von solchen, die bereits kleine oder auch größere Programme geschrieben haben, bis hin zu jenen, die sich als Experten bezeichnen. Unsere Erfahrungen zeigen, dass auch diese neue Einblicke und Sachverhalte in diesem Buch finden werden, die sich positiv auf deren zukünftige Programmierung mit Python auswirken. Ansonsten kann dieses Buch auch bestens als Nachschlagewerk benutzt werden. Der umfangreiche Index in diesem Buch macht das Auffinden besonders einfach und erlaubt es damit, dieses Buch außerdem als Referenz zu verwenden, auch wenn es keinesfalls unter diesem Aspekt geschrieben worden ist. 1.3 Aufbau des Buches Dieses Buch besteht aus vier Teilen: Teil I: In ersten Teil behandeln wir die Grundlagen der Sprache. Wir lernen Variablen und deren Besonderheit in Python kennen. In diesem Zusammenhang lernen wir auch die Datenstrukturen wie ganze Zahlen (Integers), Fließkommazahlen (Floats), Zeichenketten (Strings), Listen, Tupel, Dictionaries und Mengen kennen. Wie beschäftigen uns eingehend mit allen Anweisungsarten von Python, d.h. Zuweisungen, Kontrollstrukturen wie bedingten Anweisungen und Schleifen sowie Möglichkeiten der Ein- und Ausgabe. Außerdem behandeln wir eingehend Funktionen, Module und Pakete. Teil II: Den zweiten Teil des Buches haben wir – entsprechend ihrer Bedeutung – ganz der Objektorientierung gewidmet. In anschaulichen Beispielen führen wir sanft in das objektorientierte Denken und Programmieren ein. Danach fahren wir mit den fortgeschrittenen Themen der Objektorientierung fort und behandeln neben Vererbung, Mehrfachvererbung und Slots auch extrem fortgeschrittene Themen wie Metaklassen und abstrakte Klassen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 1.4 Programmieren lernen „interaktiv” Teil III: Im dritten Teil geht es um äußerst wichtige Konzepte der Programmiersprache Python, die alle kennen sollten, die Python wirklich verstehen wollen. Lernende können diese Kapitel jedoch erst mal ignorieren, weshalb wir sie in einem separaten Teil zusammengefasst haben. Die Listenabstraktionen bieten ebenso wie die lambda-, map-, filter- und reduce-Funktionen eine faszinierende Möglichkeit, die Programmierung auf ein abstrakteres Level zu bringen. Dadurch kann man komplexere Probleme mit geringerem Programmieraufwand lösen, was außerdem zu einer besseren Verständlichkeit und einfacheren Wartbarkeit der Programme führt. Dennoch kann man die gleichen Programme auch ohne diese Techniken schreiben. Eingehend befassen wir uns auch mit dem Themenkomplex „Generatoren und Iteratoren” sowie „Dekoration und Dekorateure”! Teil IV: In dem Teil mit dem Titel „Weiterführende Themen ” verlassen wir das „eigentliche” Python und wenden uns weiterführenden Themen der Programmierung zu. Dennoch dürften vor allen Dingen die ersten drei Kapitel dieses Buchteils von allgemeinem Interesse sein. Testverfahren und Debugging gehört zu den Fragestellungen, die alle Programmiererinnen und Programmierer bewegen sollten. Im Prinzip hätten wir dieses Kapitel ebenso gut in den ersten Teil nehmen können. Außerdem behandeln wir in einem Kapitel „Reguläre Ausdrücke”, die nahezu unerlässlich sind, wenn man Textverarbeitung effizient betreiben will. Man braucht sie auch, wenn man aus Log- oder Parameterdateien bestimmte Informationen herausfiltern will. Den ab Python 3.5 eingeführten Typanmerkungen (type annotations) haben wir auch ein eigenes Kapitel gewidmet. In einem weiteren Kapitel zeigen wir außerdem, wie man Daten strukturerhaltend übers Programmende hinaus konservieren kann. Das Kapitel zur Systemprogrammierung dürfte von besonderer Bedeutung für Systemprogrammierer sein, die ihre Systemprogramme zukünftig unter Python und nicht mehr mit Shell-Skripting verfassen wollen. Teil V: Programmieren lernen ist vor allen Dingen eine aktive Tätigkeit. Nur ein Buch zu lesen und Beispiele nachzuvollziehen genügt nicht. Deshalb finden Sie zu den meisten Kapiteln interessante und lehrreiche Übungsaufgaben, die den Stoff vertiefen. In diesem Teil des Buches finden Sie nun die ausführlichen Musterlösungen mit Erläuterungen zu diesen Aufgaben. 1.4 Programmieren lernen „interaktiv” Wie bereits erwähnt, floss in dieses Buch die jahrelange Erfahrung sowohl in der Theorie und Praxis des Programmierens allgemein, aber vor allem auch die Vermittlung des Stoffes in zahlreichen kleinen und großen Kursen mit unterschiedlichsten Besuchertypen ein. Aber ein Buch zu schreiben, stellt dennoch eine neue Herausforderung dar. Beim Buch fehlt leider die direkte Interaktion zwischen dem, der das Wissen vermittelt, und dem Lernenden. Vor Ort kann man sofort sehen, wenn sich bei einem Teilnehmer die Stirn runzelt, große Fragezeichen erscheinen oder wenn sich jemand aus Stress das Ohrläppchen zu reiben beginnt. Dann weiß man als erfahrener Dozent, dass es höchste Zeit ist, ein paar zusätzliche Beispiele und Analogien zu verwenden, oder dass man den Stoff nochmals in anderen – möglicherweise auch einfacheren – Worten erklären sollte. Beim Schreiben ei- 3 4 1 Einleitung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. nes Buches muss man diese möglichen Klippen vorhersehen und die nötigen zusätzlichen Übungen und Beispiele an den entsprechenden Stellen bereitstellen. Aber was bei vielen Lesern hilft, diese Klippen zu umgehen, führt bei anderen nun vielleicht zu Langeweile und Ungeduld, denn sie empfinden diese zusätzlichen Erklärungen, Übungen oder Beispiele möglicherweise als Zeit- oder Platzvergeudung. Das Grundproblem ist, dass es sich bei einem Buch nicht um ein interaktives Medium handelt. Aber dank des Internets können wir Ihnen diese Interaktivität dennoch bieten. Im nächsten Abschnitt finden Sie die Adressen, wo Sie Hilfe und zusätzliche Informationen zum Buch finden. 1.5 Download der Beispiele und Hilfe Alle im Buch verwendeten Beispiele finden Sie zum Download unter http://www.python-kurs.eu/buch/beispiele/ Auch wenn wir das Buch so geschrieben haben, dass Sie ohne zusätzliche Hilfe auskommen sollten, wird es dennoch hier und da mal ein Problem geben, wo Sie sich vielleicht festgebissen haben. Wenn das Glück es so will, dass Sie in einer Umgebung arbeiten, in der es andere Python-Programmierer gibt, haben Sie es natürlich gut. Aber viele Leserinnen und Leser genießen diesen Vorteil nicht. Dann könnte sich ein Besuch unserer Website besonders lohnen: http://www.python-kurs.eu/buch/ Dort finden Sie ein Korrekturverzeichnis, zusätzliche bzw. aktualisierte Übungen und sonstige Hilfestellungen. Ansonsten bleibt natürlich immer noch der Einsatz einer Suchmaschine! 1.6 Anregungen und Kritik Falls Sie glauben, eine Ungenauigkeit oder einen Fehler im Buch gefunden zu haben, können Sie auch gerne eine E-Mail direkt an den Autor schicken: klein@python-kurs.eu. Natürlich gilt dies auch, wenn Sie Anregungen oder Wünsche zum Buch geben wollen. Leider können wir jedoch – so gerne wir es auch tun würden – keine individuellen Hilfen zu speziellen Problemen geben. Wir werden versuchen, Fehler und Anmerkungen in kommenden Auflagen zu berücksichtigen. Selbstverständlich aktualisieren wir damit auch unsere Informationen unter http://www.python-kurs.eu/buch/ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Teil I Grundlagen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 2 2.1 2.1.1 Kommandos und Programme Erste Schritte Linux Bei den meisten Linux-Distributionen ist Python bereits vorinstalliert, und damit ist auch die interaktive Python-Shell direkt verfügbar. Der Interpreter wird üblicherweise im Verzeichnis /usr/bin/ (aber manchmal auch in /usr/local/bin) installiert, aber diese Information benötigen wir jetzt noch nicht. Der einfachste Weg, um mit Python unter Linux zu arbeiten, besteht darin, dass man zuerst ein Terminal startet wie beispielsweise ein „xterm” oder ein „gnome-terminal”. Bild 2.1 Gnome-Terminal In diesem Terminal – im Bild sehen Sie ein Gnome-Terminal – tippen Sie „python3” ein und drücken die Eingabetaste. Damit starten Sie die interaktive Python-Shell. Python meldet sich mit Informationen über die installierte Version. Der Interpreter steht nun mit dem sogenannten Eingabeprompt (>>>) für Eingaben bereit. Man kann jetzt beliebigen PythonCode eingeben, der nach dem Quittieren mit der Eingabetaste (Return-Taste) sofort ausgeführt wird. 8 2 Kommandos und Programme deeplearning@deep-learning-virtual-machine:~$ python3 Python 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Bitte beachten Sie, dass Sie keinesfalls die Ziffer 3 hinter „python” vergessen dürfen. Wenn Sie nur „python” eingeben, starten Sie Python in einer 2er-Version. deeplearning@deep-learning-virtual-machine:~$ python Python 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> Um die Beispiele in unserem Buch nachvollziehen zu können, benötigen Sie jedoch Python in einer 3er-Version.1 2.1.2 Windows Unter Windows muss Python erst installiert werden. Die neuesten Versionen findet man unter www.python.org/downloads. Bild 2.2 Python starten unter Windows 1 Also eine Version 3.x, d.h. Python 3.0, 3.1, 3.2, 3.3 usw. 2.2 Herkunft und Bedeutung des Begriffes interaktive Shell Nachdem Python installiert worden ist, findet man unter „Alle Programme” einen Eintrag „Python 3.5” oder „Python 3.6”2 , unter dem sich die Untereinträge „IDLE (Python GUI)” und „Python 3.6 (interaktive Python-Konsole, auch Shell genannt) befinden – siehe Bild 2.2. Um die nachfolgenden Beispiele und Übungen nachvollziehen zu können, starten Sie bitte die Python-Konsole, also „Python 3.6” (bzw. Python 3.5). Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Alternativ kann man auch mit IDLE arbeiten. Dabei handelt es sich um eine Mini-Entwicklungsumgebung, auf die wir hier jedoch nicht eingehen wollen. 2.1.3 macOS Die Installation von Python auf einem Macintosh unter macOS verläuft ähnlich wie bei anderen Unix- und Linux-Plattformen. Die neueren macOS-Versionen werden bereits mit einem vorinstallierten Python ausgeliefert. Allerdings handelt es sich dabei um eine 2.7Version. Für das Buch benötigen wir jedoch eine 3er-Version von Python. Die neueste Version kann man sich auch von der python.org-Website unter www.python.org/downloads herunterladen. Nach der Installation von Python3 kann man, wie bei Linux auch, ein Terminal starten und dort mit der Eingabe von python3 die interaktive Shell starten.3 Bild 2.3 Python starten unter macOS 2.2 Interaktive Shell Wenn die Aktivitäten des vorigen Kapitels erfolgreich waren, sollten Sie sich nun sowohl unter Linux als auch unter Windows in der interaktiven Python-Shell befinden. Der Begriff „interaktiv” kommt vom lateinischen „inter agere”. Das Verb „agere” bedeutet unter anderem „tun”, „treiben”, „betreiben”, „handeln”, und „inter” bezeichnet die zeitliche und örtliche Position zu Dingen und Ereignissen, also „zwischen” zwei oder „inmitten” 2 3 Je nach Installation könnte hier auch 3.4 oder eine ältere Version stehen! Wie man auf dem Bild sieht, haben wir das Anaconda-Paket der Firma Continuum installiert, da es noch über viele zusätzliche Pakete verfügt. Diese Pakete werden allerdings im Rahmen dieses Buches nicht benötigt. 9 10 2 Kommandos und Programme Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. (oder „unter”) vielen Objekten, Personen oder Ereignissen sein. Also heißt „inter agere” dazwischen handeln. In diesem Sinne steht die interaktive Shell zwischen dem Anwender und dem Betriebssystem (Linux, Unix, Windows oder anderen) bzw. dem zu interpretierenden Programm (z.B. Python). Die interaktive Shell steht aber auch zeitlich zwischen den einzelnen Aktionen, d.h. zwischen den einzelnen Befehlen und Kommandos. Der Benutzer gibt einen Befehl ein, die Shell führt diesen unverzüglich aus, liefert sofort das Ergebnis und wartet dann auf den nächsten Befehl. Im Englischen steht das Wort shell für eine Schale, einen Panzer oder ganz allgemein eine Hülle oder Schutzhülle. shell bezeichnet auch ein Schneckenhaus und das Gehäuse, das eine Muschel umschließt. Ebenso liegt eine Shell auch zwischen einem Betriebssystem und dem Benutzer. Wie eine Muschelschale schützt sie einerseits das Betriebssystem vor dem Benutzer, und andererseits erspart sie dem Benutzer die Benutzung der „primitiven” und schwer verständlichen Basisfunktionen, indem es ihm komfortable Befehle zur Kommunikation mit dem Computer zur Verfügung stellt. Auch die Programmiersprache Python bietet dem Anwender eine komfortable Kommandozeilenschnittstelle, die sogenannte Python-Shell, manchmal auch als interaktive Python-Shell bezeichnet. Man könnte meinen, dass es sich bei dem Begriff „interaktive Shell” um eine Tautologie handelt, da ja, so wie oben beschrieben, Shells immer interaktiv sind. Dies ist jedoch nicht so: Es gibt auch vor allem im Umfeld von Linux und Unix Shells, die als Subshell aufgerufen und nicht interaktiv ausgeführt werden. 2.2.1 Erste Schritte in der interaktiven Shell Wir nehmen nun an, dass Sie entweder unter Linux oder unter Windows vor einer lauffähigen Python-Shell sitzen, die mit blinkendem Cursor hinter dem Prompt „>>>” auf Ihre Eingabe wartet. Unsere Experimente mit der Python-Shell starten wir mit der Eingabe eines beliebigen arithmetischen Ausdrucks wie z.B. „4 * 5.2” oder „4 * 12 / 3”. Ein Ausdruck wird mit dem Drücken der Eingabetaste (Return) sofort ausgeführt, und das Ergebnis wird in der nächsten Zeile ausgegeben: >>> 4 * 5.3 21.2 >>> 4 * 12 / 3 16.0 Die interaktive Shell erlaubt eine Bequemlichkeit, die innerhalb eines Python-Skripts nicht zulässig ist. In einem Programm hätten wir das Ergebnis des obigen Ausdrucks nur mittels einer print-Anweisung ausgeben können: >>> print(4 * 5.3) 21.2 Python kennt wie die meisten anderen Programmiersprachen die Regel „Punktrechnung geht vor Strichrechnung”, wie wir im folgenden Beispiel sehen können: 2.3 Verlassen der Python-Shell Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> 1 + (42 * 2) 85 >>> 1 + 42 * 2 85 >>> >>> (1 + 42) * 2 86 Der jeweils letzte Ausgabewert wird vom Interpreter in einer speziellen Variablen automatisch gespeichert. Der Name der Variable ist einfach ein Unterstrich, also „_”. Das Ergebnis der letzten Berechnung kann man sich also damit wieder ausgeben lassen: >>> _ 86 Der Unterstrich kann im Prinzip wie eine normale Variable benutzt werden: >>> _ * 3 258 Dies gilt allerdings nicht in einem Python-Skript oder Python-Programm! In einem PythonProgramm hat der Unterstrich nicht diese Bedeutung. Auch Strings lassen sich mit oder ohne print in der interaktiven Shell ausgeben: >>> print("Hello World") Hello World >>> "Hello World" 'Hello World' Die Python-Shell bietet noch einen Komfort, den man von den Linux-Shells gewöhnt ist. Es gibt einen Befehlsstack, in dem alle Befehle der aktuellen Sitzung gespeichert werden. Mit den Tasten „↑” und „↓” kann man sich in diesem Stack nach oben („↑”), also zu älteren Befehlen, und nach unten („↓”), also wieder in Richtung neuester Befehl, bewegen. 2.3 Verlassen der Python-Shell Wir haben zwar gerade erst damit begonnen, in der Python-Shell „herumzuspielen”, dennoch wollen sicherlich einige das Programm bereits verlassen. Ctrl-C – was vielen Linux- und Unix-Benutzern häufig als Erstes einfällt – funktioniert nicht! Den Python-Interpreter kann man per Kontrollsequenz nur mit Ctrl-D wieder verlassen. Alternativ dazu kann man die Python-Shell mittels Eingabe der Funktion exit() verlassen. Die Klammern hinter exit sind ab Python 3 zwingend notwendig. Bild 2.4 Emergency Exit 11 12 2 Kommandos und Programme Alle Einstellungen, wie beispielsweise Variablen oder eingegebene Funktionsdefinitionen, gehen beim Verlassen der Python-Shell verloren und sind beim nächsten Start nicht mehr vorhanden. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 2.4 Benutzung von Variablen In der Python-Shell kann man auch ganz einfach Variablen benutzen. Wenn Sie genau wissen wollen, was es mit Variablen und Datentypen auf sich hat, empfehlen wir, in Kapitel 4 (Datentypen und Variablen) weiterzulesen. Das folgende Beispiel wendet sich an Benutzer, die bereits ein wenig Erfahrung mit Programmierung haben. Man kann also ganz einfach Werte in Variablen speichern. Variablennamen bedürfen keiner besonderen Kennzeichnung wie in anderen Sprachen wie zum Beispiel das Dollarzeichen in Perl: >>> maximal = 124 >>> breite = 94 >>> print(maximal - breite) 30 2.5 Mehrzeilige Anweisungen Bis jetzt haben wir noch nicht über mehrzeilige Anweisungen gesprochen. Anfänger werden hier vielleicht Probleme haben, da noch einige Grundlagen zum Verständnis fehlen, und sollten deshalb besser mit dem folgenden Kapitel weitermachen. Wir demonstrieren, wie die interaktive Shell mit mehrzeiligen Anweisungen wie zum Beispiel For-Schleifen umgeht. >>> l = ["A",42,78,"Just a String"] >>> for character in l: ... print(character) ... A 42 78 Just a String Achtung: Nachdem man die Zeile „for character in l:” eingetippt hat, wartet die interaktive Shell im Folgenden auf eingerückte Zeilen. Dies teilt uns die Shell mit einem neuen Prompt mit: Statt dem gewohnten „>>>” erhalten wir nun eine Folge von drei Punkten „...”. Wir müssen also alle Anweisungen, die mit der Schleife ausgeführt werden sollen, um die gleiche Anzahl von Leerzeichen einrücken, mindestens jedoch ein Leerzeichen! Wenn wir fertig sind, müssen wir nach der letzten Code-Zeile zweimal die Eingabetaste drücken, bevor die Schleife wirklich ausgeführt wird. 2.6 Programme schreiben oder schnell mal der Welt “Hallo” sagen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 2.6 Programme schreiben Wir haben bisher ein wenig mit der interaktiven Python-Shell herumprobiert, aber noch kein richtiges Programm geschrieben. Bei der Programmierung ist es generell wichtig, möglichst in kleinen Schritten vorzugehen. Deshalb wird unser erstes Programm in Python auch nur sehr wenig tun. Das Programm soll lediglich einen kurzen Text ausgeben, nachdem es gestartet worden ist. Für diesen Text wird in fast allen Einführungen zu Programmiersprachen die Zeichenkette4 „Hallo Welt” oder auf Englisch „Hello World” verwendet. Wir wollen dieser Tradition folgen. Bild 2.5 Hallo Welt Im Folgenden wollen wir zeigen, was in der Programmiersprache C++ nötig ist, um ein so kleines Programm zum Laufen zu bringen. Sie können diesen kurzen Exkurs in die Programmiersprache C++ bedenkenlos überspringen. Wir bringen dieses Beispiel nur, damit man später erkennen kann, wie leicht es ist, in Python im Vergleich zu C++ zu programmieren. Folgender Programmcode ist in C++ nötig, um ein Programm zu schreiben, was nach dem Start nur den Text „Hello World” in einer Zeile ausgibt: #include <iostream> using namespace std; int main() { cout << "Hello World!\n"; return 0; } Wenn man dieses Programm in einer Datei gespeichert hat, also z.B. „hello.cpp”, muss man das Programm compilieren, um ein lauffähiges Programm zu erhalten.5 Dies geschieht mit folgendem Befehl, der ein lauffähiges Programm unter dem Namen „hello” erzeugt: bernd@saturn:~/bodenseo/python/beispiele$ g++ -o hello hello.cpp Im Folgenden starten wir das Programm „hello” und erhalten die gewünschte Ausgabe: bernd@saturn:~/bodenseo/python/beispiele$ ./hello Hello World! bernd@saturn:~/bodenseo/python/beispiele$ 4 5 Eine Folge von Zeichen wird als „Zeichenkette” oder meistens als „String” bezeichnet. Näheres zu Compiler, Interpreter, Maschinen und Skriptsprachen erfahren Sie in Kapitel 3 (Bytecode und Maschinencode). 13 14 2 Kommandos und Programme Deutlich einfacher können wir ein solches Programm unter Python erstellen. Schreiben Sie in einem Editor6 Ihrer Wahl die folgende Zeile und speichern Sie sie unter dem Namen „hello.py” ab: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. print("Hello World") Unter Linux oder Unix starten wir dieses Programm am einfachsten, wenn wir uns in einem Terminal in das Verzeichnis begeben, in dem wir die Datei hello.py abgespeichert haben. Dort können wir dann das Programm mit Kommando „python3 hello.py” ausführen. Im Folgenden sehen wir dieses Vorgehen inklusive Ergebnis. Dabei nehmen wir an, dass Sie die Datei in einem Unterverzeichnis python_beispiele Ihres Homeverzeichnisses abgespeichert hatten: bernd@venus:~$ cd ~/python_beispiele/ bernd@venus:~/python_beispiele$ python3 hello.py Hallo Welt Unter Windows können wir ähnlich vorgehen. Wir starten erst das Programm „Eingabeaufforderung”. Dort bewegen wir uns in unser Verzeichnis mit den Python-Programmen, in das wir unser hello.py abgespeichert haben. Bild 2.6 Hallo Welt unter Python in der Windows-Eingabeaufforderung Wir haben gesehen, dass die nötige Syntax7 unter Python deutlich geringer ist als unter C++, d.h. man muss lediglich den Text mit einer print-Funktion aufrufen. Anschließend ist auch kein explizites Compilieren8 nötig. Man kann das Programm einfach so starten. 6 7 8 Unter Windows könnte das beispielsweise „Notepad” sein, unter Linux oder einem anderem Unix-System empfiehlt sich der vi, emacs, kate oder gedit. Etymologisch leitet sich das Wort Syntax aus dem altgriechischen Wort „syntaksis” ab. „syn” bedeutet „zusammen”, und „taksis” steht für „Reihenfolge” oder „Ordnung”. Unter Syntax versteht man in der Linguistik die Lehre vom Satzbau, also sozusagen die „zusammen”- oder „gesamt”-Reihenfolge der einzelnen Satzteile. In der Informatik versteht man unter der Syntax einer Programmiersprache die Regeln oder das System von Regeln, mit denen man gültige Programme der Sprache beschreiben kann. Man beachte das Wort „explizit”. Beim Start des Programmes wird nämlich implizit gegebenenfalls eine Compilierung vorgenommen. Auf diese gehen wir im nächsten Kapitel näher ein. 2.6 Programme schreiben oder schnell mal der Welt “Hallo” sagen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Fragen zum Verständnis: ■ Wie kann man ein Python-Programm schreiben? ■ Wie startet man ein Python-Programm? ■ Wie kann man das Produkt aus 3.5 und 7.8 am einfachsten mit Python bezeichnen? 15 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 3 3.1 Bytecode und Maschinencode Einführung Dieses Kapitel können Sie beim Einstieg in die Materie auch gerne überspringen, da das Verständnis für die folgenden Kapitel nicht dringend notwendig ist. Hier geht es um allgemeine Fragen: Handelt es sich bei Python um eine Programmier- oder eine Skriptsprache? Wird ein Python-Programm übersetzt oder interpretiert? Kann der gleiche Python-Code auf verschiedenen Rechnern oder Betriebssystemen laufen? Worin liegt der Zusammenhang zwischen Python, Jython oder Java? 3.2 Unterschied zwischen Programmierund Skriptsprachen Die Frage, worin der Unterschied zwischen Programmier- und Skriptsprachen liegt, lässt sich nicht einfach beantworten. Sehr häufig werden die Begriffe Skript und Programm nahezu synonym verwendet. Es gibt Fälle, in denen es eindeutig ist: Hat man beispielsweise ein Problem in C gelöst, so wird allgemein das Ergebnis als „Programm” bezeichnet. Kaum jemand würde ein C-Programm als Skript bezeichnen. Bei Shells wie z.B. der Bourne- oder der Bash-Shell spricht man nahezu immer von Skripten und nicht von Programmen. Würde man von einem Bourne-Shell-Programm sprechen, so klingt das mindestens so falsch, als bezeichnete man ein Fahrrad als Moped. Worin der Unterschied zwischen einem ShellSkript und einem Programm liegt, wird in Tabelle 3.1 auf der nächsten Seite gezeigt. 3.3 Interpreter- oder Compilersprache Unter einer Compilersprache versteht man eine Programmiersprache, deren Programme vor ihrer Ausführung vollständig in Maschinencode, also Binärcode, übersetzt werden. Dies kann leicht zu Verwechslungen führen, da manchmal auch die Sprache, in der ein Compiler geschrieben wird, als Compilersprache bezeichnet wird. Nehmen wir an, dass 18 3 Bytecode und Maschinencode Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Tabelle 3.1 Unterschiede zwischen Skripten und Programmen Skripte Programme Ein Skript besteht typischerweise nur aus „wenigen” Zeilen Code, häufig weniger als 100 Zeilen. Programme können sehr klein sein, also beispielsweise weniger als Hundert Zeilen Code, aber sie können auch aus mehreren Tausend oder gar Millionen Codezeilen bestehen. Skripte werden unmittelbar ausgeführt. Sie werden nicht übersetzt (bzw. compiliert), sondern interpretiert. Programme in Programmiersprachen wie C und C++ müssen immer in Maschinencode übersetzt werden, bevor sie ausgeführt werden können. wir eine Programmiersprache XY1 hätten, für die es einen Compiler cXY gibt. Dann bezeichnet man XY als eine Compilersprache, weil sie mittels cXY in Maschinencode übersetzt wird. Wurde nun der Compiler2 cXY in der Programmiersprache C geschrieben, dann sagt man „C ist die Compilersprache von XY”. Bei einer Interpretersprache werden die Programme Befehl für Befehl interpretiert und ausgeführt. Die Programmiersprachen C und C++ sind reine Compilersprachen, d.h. jedes Programm muss zuerst in Maschinencode übersetzt werden, bevor es ausgeführt werden kann. Sprachen wie Basic und VB werden interpretiert. Man liest leider allzu häufig, dass es sich bei Python um eine interpretierte Sprache handelt. Dies ist nicht richtig. Aber bei Python handelt es sich auch nicht um eine Compilersprache im „klassischen” Sinne. Python-Programme werden sowohl übersetzt als auch interpretiert. Startet man ein Python-Programm, wird zuerst der Programmcode in Bytecode übersetzt. Bei Bytecode handelt es sich um Befehle für eine virtuelle Maschine. Bei Python spricht man von der PVM (Python Virtual Machine).3 Dieser Code ist unabhängig von realer Hardware, aber kommt dem Code von Maschinensprachen schon relativ nahe. Dieser Bytecode wird dann für einen bestimmten Maschinencode interpretiert. Das bedeutet, dass der Bytecode plattformunabhängig ist. Bild 3.1 Python: Compiler und Interpreter Importiert man ein Python-Modul, so wird dieses nicht nur für diesen Lauf kompiliert, sondern auch dauerhaft gespeichert. Dadurch kann Python in einem späteren Lauf auf bereits compilierten Code zurückgreifen. Dies natürlich nur, falls sich der Quellcode des Moduls nicht verändert hat. Nun stellt sich die Frage, wo – also in welchem Verzeichnis – Python 1 2 3 Nach unserem Kenntnisstand gibt es keine Programmiersprache mit diesem Namen. Beim Compiler handelt es sich auch um ein Programm. Dieses Konzept entspricht der Vorgehensweise bei Java. Auch dort wird der Code zuerst in Bytecode übersetzt, d.h. Code für die JVM (Java Virtual Machine). 3.3 Interpreter- oder Compilersprache den Bytecode speichert. Sie werden in dem Verzeichnis, in dem sich Ihr Modul befindet, ein Verzeichnis __pycache__ finden. Innerhalb dieses Programms finden Sie dann die compilierten Module, die Sie an der Endung .pyc erkennen können.4 Es ist einfach, obiges selbst zu testen. Dazu erzeugen wir eine Datei „beispiel.py” mit folgender Zeile als Inhalt: print("Irgendetwas muss ich halt sagen!") Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Geben Sie dann im Python-Interpreter folgende Zeile ein: deeplearning@deep-learning-virtual-machine:~/tmp$ python3 Python 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import beispiel Irgendetwas muss ich halt sagen! In dem Unterverzeichnis __pycache__ befindet sich nun eine Datei beispiel.cpython-35.pyc. 4 In den Python 2.x-Versionen wurden die compilierten Versionen im gleichen Verzeichnis wie die Module abgespeichert! 19 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 4 4.1 Datentypen und Variablen Einführung Sie glauben, weil Sie bereits in Sprachen wie C und C++ programmiert haben, dass Sie bereits genug über Datentypen und Variablen wissen? Vorsicht ist geboten! Sie wissen sicherlich viel, aber wahrscheinlich nicht genug, wenn es um Python geht. Deshalb lohnt es sich auf jeden Fall, hier weiterzulesen. Es gibt gravierende Unterschiede zwischen Python und anderen Programmiersprachen in der Art, wie Variablen behandelt werden. Vertraute Datentypen wie Ganzzahlen (Integer), Fließkommazahlen (floating point numbers) und Strings sind zwar in Python vorhanden, aber auch hier gibt es wesentliche Unterschiede zu C/C++ und Java. Dieses Kapitel ist also sowohl für Anfänger als auch für fortgeschrittene Programmierer zu empfehlen. Eine Variable ist ein Name bzw. ein Bezeichner, mit dem man auf ein Objekt zugreifen kann.1 Ein Objekt steht zwar an einem bestimmten Speicherplatz, aber in Python spricht und denkt man nicht in physikalischen Speicherplätzen. Eine Variable ist nicht einem festen Objekt oder Speicherplatz zugeordnet. Während des Programmablaufs können einem Variablennamen neue beliebige Objekte zugewiesen werden, d.h. die Variable referenziert nach jeder Zuweisung in den meisten Bild 4.1 Variablen sind Referenzen auf Objekte Fällen einen neuen Speicherort. Diese Objekte können beliebige Datentypen sein. Eine Variable referenziert also ein Objekt in Python. 1 In den meisten Programmiersprachen ist es so, dass eine Variable einen festen Speicherplatz bezeichnet, in dem Werte eines bestimmten Datentyps abgelegt werden können. Während des Programmlaufs kann sich der Wert der Variable ändern, aber die Wertänderungen müssen vom gleichen Typ sein. Also kann man nicht in einer Variable zu einem bestimmten Zeitpunkt eine Integer-Zahl gespeichert haben und dann diesen Wert durch eine Fließkommazahl überschreiben. Ebenso ist der Speicherort der Variablen während des gesamten Laufs konstant, kann also nicht mehr geändert werden. 22 4 Datentypen und Variablen Im Folgenden kreieren wir eine Variable x in der interaktiven Python-Shell, indem wir ihr einfach den Wert 42 unter Verwendung eines Gleichheitszeichens zuweisen: >>> x = 42 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Man bezeichnet dies als Zuweisung oder auch als Definition einer Variablen. Genau genommen müsste man sagen, dass wir ein Integer-Objekt „42” erzeugen und es mit dem Namen x referenzieren. Anmerkung: Obige Anweisung darf man nicht als mathematisches Gleichheitszeichen sehen, sondern als „der Variablen x wird der Wert 42 zugewiesen”, d.h. der Inhalt von x ist nach der Zuweisung 42. Man kann die Anweisung also wie folgt „lesen”: „x soll sein 42”. Wir können nun diese Variable zum Beispiel benutzen, indem wir ihren Wert mit der Funktion print ausgeben lassen: >>> print("Wert von x: ", x) Wert von x: 42 Wir können sie aber auch auf der rechten Seite einer Zuweisung verwenden: >>> y = x Beide Variablen zeigen nun auf das gleiche Objekt, d.h. das int-Objekt 42: Woher wissen wir oder noch besser, wie kann man beweisen, dass beide Variablen auf das gleiche Objekt zeigen? Dazu bietet sich die id-Funktion an. Dies ist eine Funktion, die für ein Objekt die Identität eines Objektes zurückgibt. Erhält man für zwei Variablennamen die gleiche Identität, so weiß man, dass sie das gleiche Objekt referenzieren. Wir demonstrieren dies im Folgenden: >>> x = "Ich bin ein String" >>> y = "Ich auch und ich habe nichts mit x zu tun!" >>> id(x) 139774036111128 >>> id(y) 139774048560016 >>> >>> y = x >>> id(y) 139774036111128 4.2 Variablennamen Auf der rechten Seite einer Zuweisung kann natürlich auch ein zu berechnender Ausdruck stehen, dessen Ergebnis dann der Variablen auf der linken Seite zugewiesen wird: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> y = y + 36 >>> print(y) 78 Wie man im letzten Beispiel sehen kann, ist es möglich, eine Variable gleichzeitig auf der linken und rechten Seite einer Zuweisung zu benutzen. Für die Schreibweise „y = y + 36” verwendet man üblicherweise die sogenannte erweiterte Zuweisung „y += 36”. Vom Ergebnis her gesehen sind die beiden Möglichkeiten identisch. Allerdings gibt es einen Implementierungsunterschied. Bei der erweiterten Zuweisung muss die Referenz der Variablen y nur einmal ausgewertet werden.2 Erweiterte Zuweisungen gibt es auch für die meisten anderen Operatoren wie „-”, „*”, „/”, „**”, „//” (ganzzahlige Division) und „%” (Modulo). 4.2 Variablennamen Wenn wir uns beim Programmieren Variablennamen ausdenken, muss man zweierlei beachten: Zum einen muss der Name den syntaktischen Anforderungen der Programmiersprache genügen, und zum anderen muss er den üblichen Gepflogenheiten und Konventionen entsprechen. 4.2.1 Gültige Variablennamen Variablennamen müssen mit einem Buchstaben oder Unterstrich „_” beginnen. Die folgenden Zeichen dürfen sich aus einer beliebigen Folge von Buchstaben, Ziffern und dem Unterstrich zusammensetzen. Variablennamen sind case-sensitive.3 Dies bedeutet, dass Python zwischen Groß- und Kleinschreibung unterscheidet: 2 3 Bei Datentypen wie Listen kann es zu extremen Laufzeitproblemen kommen, wenn man keine erweiterte Zuweisung verwendet. Darauf gehen wir jedoch erst später ein. Für diesen Begriff gibt es keine deutsche Übersetzung. 23 24 4 Datentypen und Variablen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> person_42 = "Marvin" >>> Person_42 = "Arthur" >>> print(person_42) Marvin >>> print(Person_42) Arthur 4.2.2 Konventionen für Variablennamen Man bevorzugt in der Python-Community Kleinschreibung bei Variablennamen, z.B. minimum statt Minimum. Außerdem werden Variablennamen, die aus mehreren Wörtern bestehen, mittels Unterstrich (_) separiert, also beispielsweise minimale_breite. Variablennamen mit Binnenversalien – bei Programmierern besser als „camel case” oder „CamelCase” bekannt – sind in der Python-Welt nicht beliebt: MinimaleBreite oder minimaleBreite. Ansonsten zeichnet sich ein guter Programmierstil dadurch aus, dass man möglichst sprechende Variablennamen verwendet. Also beispielsweise aktueller_kontostand oder maximale_beschleunigung statt nichtssagender Namen wie ak oder mb. 4.3 4.3.1 Datentypen Ganze Zahlen Ganze Zahlen werden in der Informatik üblicherweise als Integer bezeichnet. Es handelt sich dabei um die Zahlen ... -3, -2, -1, 0, 1, 2, 3, ... Während Integer-Zahlen in den meisten Programmiersprachen durch einen Maximalwert begrenzt sind, sind sie in Python unbegrenzt, d.h. sie können beliebig groß bzw. beliebig klein werden. Die Variable unsigned_long_max bezeichnet in der folgenden interaktiven Python-Session den maximalen Integer-Wert in C für „unsigned long”. Ein Wert, der in C für diesen Datentyp nicht überschritten werden kann. Wir sehen, dass wir in Python diesen Wert sogar ohne Fehler beispielsweise quadrieren können. >>> unsigned_long_max = 4294967295 >>> unsigned_long_max * unsigned_long_max 18446744065119617025 >>> x = 7876473829887890878787823666656543423341430089091000654 >>> x * x 6203883999290881978134855892120878218991606511525246666222414202883 0910197615930689481485927796837531028427716 Bei Integer-Zahlen muss man beachten, dass sie nicht mit einer 0 beginnen dürfen, wenn es sich um eine Zahl im Dezimalsystem handeln soll: 4.3 Datentypen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> 42 42 >>> 042 File "<stdin>", line 1 042 ^ SyntaxError: invalid token Die führende Null wird benötigt, wenn man Binär-, Oktal- oder Hexadezimalzahlen schreiben möchte. Literale für Binärzahlen beginnen mit der Sequenz „0b” oder „0B” und werden dann von der eigentlichen Binärzahl gefolgt. Die Binärzahl „101010” schreibt man also wie folgt: >>> x = 0b101010 >>> x 42 Man sieht, dass dies keine Auswirkung auf die Interndarstellung der Zahl hat. Literale für Oktalzahlen beginnen mit der Sequenz „0o” oder „0O” und werden dann von der eigentlichen Oktalzahl gefolgt: >>> x = 0o10 >>> x 8 Literale für Hexadezimalzahlen beginnen mit der Sequenz „0x” oder „0X” und werden dann von der eigentlichen Hexzahl gefolgt: >>> >>> 16 >>> >>> 26 x = 0x10 x x = 0x1A x Mit den Funktionen hex, bin, oct kann man eine Integer-Zahl in einen String wandeln, der der Stringdarstellung der Zahl in der entsprechenden Basis entspricht: >>> x = hex(19) >>> x '0x13' >>> type(x) <class 'str'> >>> bin(65) '0b1000001' >>> oct(65) '0o101' >>> oct(0b101101) '0o55' 25 26 4 Datentypen und Variablen 4.3.2 Fließkommazahlen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Fließkommazahlen4 entsprechen dem Datentyp float in Python. Es handelt sich dabei um Zahlen der Art 2.34, 27.87878 oder auch 3.15e2: >>> x = 2.34 >>> y = 3.14e2 >>> y 314.0 Achtung: In deutschsprachigen Ländern werden die Nachkommastellen im täglichen Leben mit einem Komma eingeleitet. So schreibt man beispielsweise 1,99, wenn ein Artikel 1,99 € kostet. In Python und in anderen Programmiersprachen wird statt des Kommas immer ein Punkt, der sogenannte Dezimalpunkt, benutzt! Wie man aus obigem Beispiel ersehen kann, entspricht die Zahl „3.14e2” dem Ausdruck 3.14 · 102 . 4.3.3 Zeichenketten Wir werden im nachfolgenden Kapitel weiter auf Strings eingehen. 4.3.4 Boolesche Werte Bei den booleschen Werten handelt es sich um einen Datentyp, der nur die zwei Werte True5 (wahr) oder False (falsch) annehmen kann. Allerdings entspricht das im Prinzip den numerischen Werten 1 für True und 0 für False. Damit lässt sich mit booleschen Werten auch „numerisch” rechnen, auch wenn das nicht immer gutem Programmierstil entspricht: >>> x = True >>> x * 4 4 Für zwei boolesche Variablen x und y gilt Folgendes: Tabelle 4.1 Logisches UND und ODER 4 5 Operator Erklärung not y Negierung von y x and y Logisches UND von x und y x or y Logisches ODER von x und y Sie werden manchmal auch als Gleitpunktzahlen bezeichnet. Ein beliebter Anfangsfehler besteht darin, dass man nicht auf die Großschreibung der beiden Werte achtet! 4.3 Datentypen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispielanwendungen: >>> x = True >>> not x False >>> y = False >>> x and y False >>> x or y True >>> x and not y True 4.3.5 Komplexe Zahlen Python bietet einen Datentyp „complex” für komplexe Zahlen, den man bei den meisten Programmiersprachen vergeblich sucht. Allerdings würden ihn die meisten Anwender von Python wohl auch nicht vermissen. Wenn Sie komplexe Zahlen nicht kennen oder nichts mit ihnen zu tun haben, können Sie das Folgende gerne überspringen. Mathematisch gesehen erweitern die komplexen Zahlen die reellen Zahlen derart, dass die Gleichung x 2 +1 = 0 lösbar wird. In der Mathematik werden komplexe Zahlen meist in der Form a + b · i dargestellt, wobei a und b reelle Zahlen sind und i die imaginäre Einheit ist. In Python benutzt man, der Konvention in der Elektrotechnik folgend, ein „j” als imaginäre Einheit. >>> x = 3 + 4j >>> y = 2 - 4.5j >>> x + y (5-0.5j) >>> x * y (24-5.5j) 4.3.6 Operatoren „//” bezeichnet die ganzzahlige Division, d.h. 11 // 4 ergibt nicht den exakten Floatwert 2.75 , sondern den Integer-Wert 2, d.h. es wird immer auf die nächste ganzzahlige Integer „abgerundet”. Dies gilt allerdings nur für den Fall, dass beide Operanden ganze Zahlen (int) sind! Ist mindestens einer der Operanden eine float-Zahl, wird auch auf die nächste ganzzahlige Integer-Zahl abgerundet, aber dann das Ergebnis in float gewandelt: >>> 2 >>> 3 >>> 4 >>> 4.0 8 // 3 11 // 3 12 // 3 12.0 // 3 27 28 4 Datentypen und Variablen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Tabelle 4.2 Erklärung zu Operatoren Operator Erklärung x+y Summe von x und y x-y Differenz von x und y x*y Produkt von x und y x/y Quotient von x und y x // y Ganzzahlige Division x%y Modulo- oder Restdivision abs(x) Betrag von x x ** y Potenzieren, also x hoch y Mit % lässt sich der Rest bei der Integerdivison bestimmen: So ergibt 11 % 4 den Wert 3 und 10 % 4 den Wert 2. >>> 8 % 3 2 >>> 9 % 3 0 >>> 8.0 % 3 2.0 Es gilt folgender Zusammenhang zwischen der ganzzahligen Division und dem ModuloOperator: >>> x = 24 >>> y = 7 >>> x == (x // y) * y + (x % y) True 4.4 Statische und dynamische Typdeklaration Wer Erfahrungen mit C, C++, Java oder ähnlichen Sprachen hat, hat dort gelernt, dass man einer Variablen einen Typ zuordnen muss, bevor man sie verwenden darf. Der Datentyp muss im Programm festgelegt werden – und zwar, bevor die Variable zum ersten Mal benutzt oder definiert wird. Dies sieht dann beispielsweise in einem C-Programm so aus: int i, j; float x; x = i / 3.0 +5.8; Während des Programmlaufs können sich dann die Werte für i, j und x ändern, aber ihre Typen sind für die Dauer des Programmlaufs auf int im Falle von i und j und float für 4.4 Statische und dynamische Typdeklaration Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. die Variable x festgelegt. Dies bezeichnet man als „statische Typdeklaration”, da bereits der Compiler den Typ festlegt und während des Programmablaufs keine Änderungen des Typs mehr vorgenommen werden können. In Python sieht dies anders aus, wie wir bereits die ganze Zeit gesehen haben. Zunächst einmal bezeichnen Variablen in Python keinen bestimmten Typ, und deshalb benötigt man in Python keine Typdeklaration. Benötigt man im Programm beispielsweise eine Variable i mit dem Wert 42 und dem Typ Integer, so erreicht man dies einfach mit der Anweisung „i = 42”. Bild 4.2 Unterschiedliche Typen Damit hat man automatisch ein Objekt vom Typ Integer angelegt, welches dann von der Variablen i referenziert wird. Man kann dann im weiteren Verlauf des Programms der Variablen i auch andere Objekte mit anderen Datentypen zuweisen: >>> i = 42 >>> i = "Hallo" >>> i = [3,9,17] Dennoch ordnet Python in jedem Fall einen speziellen Typ oder genauer gesagt eine spezielle Klasse der Variablen zu. Dieser Datentyp wird aber automatisch von Python erkannt. Diese automatische Typzuordnung, die auch während der Laufzeit erfolgen kann, bezeichnet man als „dynamische Typdeklaration”. Mit der Funktion type können wir uns den jeweiligen Typ ausgeben lassen: >>> i = 42 >>> type(i) <class 'int'> >>> i = "Hallo" >>> type(i) <class 'str'> >>> i = [3,9,17] >>> type(i) <class 'list'> Während sich bei statischen Typdeklarationen, also bei Sprachen wie C und C++, nur der Wert, aber nicht der Typ einer Variablen während eines Laufs ändert, kann sich bei dynamischer Typdeklaration, also in Python, sowohl der Wert als auch der Typ einer Variablen ändern. Aber Python achtet auf Typverletzungen zur Laufzeit eines Programms. Man spricht von einer Typverletzung6 , wenn Datentypen beispielsweise aufgrund fehlender Zuweisungskompatibilität nicht regelgemäß verwendet werden. In Python kann es nur bei Ausdrücken zu Problemen kommen, da man ja einer Variablen einen beliebigen Typ zuordnen kann. Eine Typverletzung liegt beispielsweise vor, wenn man eine Variable vom Typ int zu einer Variablen vom Typ str addieren will. Es wird in diesem Fall ein TypeError generiert: 6 engl. type conflict 29 30 4 Datentypen und Variablen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> x = "Ich bin ein String" >>> y = 42 >>> z = x + y Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't convert 'int' object to str implicitly Allerdings kann man Integer- und Float-Werte – auch wenn es sich um unterschiedliche Datentypen handelt – in einem Ausdruck mischen. Der Wert des Ausdrucks wird dann ein Float. >>> x = 12 >>> y = 3.5 >>> z = x * y >>> z 42.0 >>> type(x) <class 'int'> >>> type(y) <class 'float'> >>> type(z) <class 'float'> 4.5 Typumwandlung In der Programmierung bezeichnet man die Umwandlung eines Datentyps in einen anderen als Typumwandlung7 . Man benötigt Typumwandlungen beispielsweise, wenn man Strings und numerische Werte zu einem Ausgabestring zusammenpacken will: >>> first_name = "Henry" >>> last_name = "Miller" >>> age = 20 >>> print(first_name + " " + last_name + ": " + str(age)) Henry Miller: 20 In dem Beispiel haben wir den Wert von „age” explizit mit der Funktion str in einen String gewandelt. Man bezeichnet dies als explizite Typumwandlung. Hätten wir in obigem Beispiel nicht den Integer-Wert age in einen String gewandelt, hätte Python einen TypeError generiert: >>> print(first_name + " " + last_name + ": " + age) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't convert 'int' object to str implicitly 7 engl. type conversion oder cast 4.6 Datentyp ermitteln Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Der Text der Fehlermeldung besagt, dass Python keine implizite Typumwandlung von „int” nach „str” vornehmen kann. Prinzipiell unterstützt Python keine impliziten Typumwandlungen, wie sie in Perl oder PHP möglich sind. Dennoch gibt es Ausnahmen so wie unser Beispiel, in dem wir Integer- und Float-Werte in einem Ausdruck gemischt hatten. Dort wurde der Integer-Wert implizit in einen Float-Wert gewandelt. 4.6 Datentyp ermitteln In vielen Anwendungen muss man für ein Objekt bestimmen, um welchen Typ bzw. um welche Klasse es sich handelt. Dafür bietet sich meistens die eingebaute Funktion „type” an. Man übergibt ihr als Argument einen Variablennamen und erhält den Typ des Objekts zurück, auf das die Variable zeigt: >>> l = [3,5,4] >>> type(l) <class 'list'> >>> x = 4 >>> type(x) <class 'int'> >>> x = 4.5 >>> type(x) <class 'float'> >>> x = "Ein String" >>> type(x) <class 'str'> Als Alternative zur Funktion „type” gibt es noch die eingebaute Funktion „isinstance”, die einen Wahrheitswert „True” oder „False” zurückgibt. isinstance(object, ct) „object” ist das Objekt, das geprüft werden soll, und „ct” entspricht der Klasse oder dem Typ, auf den geprüft werden soll. Im folgenden Beispiel prüfen wir, ob es sich bei dem Objekt x um ein Tupel handelt: >>> x = (3,89,67) >>> isinstance(x, tuple) True In Programmen kommt es häufig vor, dass wir für ein Objekt wissen wollen, ob es sich um einen von vielen Typen handelt. Also zum Beispiel die Frage, ob eine Variable ein Integer oder ein Float ist. Dies könnte man mit der Verknüpfung „or” lösen: >>> x = 4 >>> isinstance(x, int) or isinstance(x, float) True >>> x = 4.8 >>> isinstance(x, int) or isinstance(x, float) True 31 32 4 Datentypen und Variablen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Allerdings bietet isinstance hierfür eine bequemere Möglichkeit. Statt eines Typs gibt man als zweites Argument ein Tupel von Typen an: >>> x = 4.8 >>> isinstance(x, True >>> x = (89, 123, >>> isinstance(x, True >>> isinstance(x, False (int, float)) 898) (list, tuple)) (int, float)) Möchte man in einem Programm auf einen bestimmten Typ prüfen, so kann man obiges Vorgehen wählen, aber häufig lässt sich dies eleganter mit Ausnahmehandlungen lösen, die wir in Kapitel Ausnahmebehandlung, Seite 20 behandelt. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 5 5.1 Sequentielle Datentypen Übersicht Auch andere Programmiersprachen wie beispielsweise Perl und Java kennen Datentypen für Strings und Listen, aber die Leistung von Python besteht darin, dass man solche Datentypen zu einer logischen Basisklasse „Sequentielle Datentypen” zusammenfasst. Eines haben nämlich Strings, Listen und Tupel gemeinsam: Es handelt sich jeweils um Datentypen, deren Elemente fortlaufend, also sequentiell, angeordnet sind. Bei Strings handelt es sich dabei um gleichartige Objekte, d.h. Zeichen, und bei Listen oder Tupel handelt es sich um beliebige Objekte. In Python gibt es deshalb gleichnamige Methoden oder Zugriffsmethoden für sequentielle Datentypen, wenn eine solche Operation für einen Typ möglich ist. Beispielsweise kann man, wie wir in diesem Kapitel kennenlernen, mit obj[0] auf das Bild 5.1 Fotos aus dem Film „The Kiss” von erste Element eines sequentiellen Daten- 1896 typs zugreifen, egal ob es sich um einen String, eine Liste oder ein Tupel handelt. Aber man kann obj[0] nicht verändern, falls „obj” ein String ist. Python stellt die folgenden sequentiellen Datentypen zur Verfügung: 34 5 Sequentielle Datentypen 5.1.1 Zeichenketten oder Strings Neben numerischen Datentypen kennt Python auch Zeichenketten, die häufig mit dem englischen Ausdruck „Strings” bezeichnet werden. Zeichenketten bestehen aus einer unveränderlichen Folge – also Sequenz – von beliebigen Zeichen. String-Literale kann man auf verschiedene Arten schreiben: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ mit einzelnen Anführungszeichen (’): ’Dies ist ein String mit einfachen Quotes’ ■ mit doppelten Anführungszeichen ("): "Mayers’ Dackel heißt Waldi" ■ mit dreifachen Anführungszeichen (’’’) oder ("""): ’’’Er sagte "Herr Meyers’ Dackel bellt immer!"’’’ Zur Generierung von mehrzeiligen Strings gibt es zwei Möglichkeiten: ■ Die Zeile mit dem unvollendeten String mit einem Rückwärtsschrägstrich1 „\” beenden: >>> ... ... >>> Ich s = "Ich bin über mehrere Zeilen \ definiert, genau gesagt \ vier Zeilen" print(s) bin über mehrere Zeilen definiert, genau gesagt vier Zeilen Man kann erkennen, dass es nicht zu Zeilenumbrüchen kommt. Dazu muss man ein „\n” verwenden. ■ Strings können auch mit einem Paar von dreifachen Anführungszeichen umgeben werden, also """ oder ’’’. Hierbei gibt es jedoch einen Unterschied zum vorigen Vorgehen. Jetzt werden Newlines in den String übernommen, wenn wir in eine neue Zeile wechseln. Will man dies nicht, muss man einen Backslash verwenden: >>> s = """Erste Zeile ... Zweite Zeile ... Dritte Zeile \ ... und ich gehöre auch noch zur 3. Zeile""" >>> print(s) Erste Zeile Zweite Zeile Dritte Zeile und ich gehöre auch noch zur 3. Zeile Diese Form von Zeichenketten findet vor allen Dingen bei der Dokumentation von Funktionen, Methoden und Modulen Verwendung, wie wir in späteren Kapiteln kennenlernen werden. 1 Wir benutzen hier die vielen wohl ungewohnte deutsche Übersetzung für Backslash. 5.1 Übersicht Wir haben vorhin gesagt, dass man einen Zeilenvorschub mit der Zeichenfolge „\n” erzeugen kann: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> s = "Eine Zeile\nund noch eine" >>> print(s) Eine Zeile und noch eine Es gibt noch weitere Escape-Zeichen. Einige wichtige haben wir in der folgenden Liste zusammengefasst: Sequenz Beschreibung \\ Ersatzdarstellung für ein Backslash \’ Hochkomma \" Anführungszeichen \b Rückschritt (backspace) \f Seitenumbruch (form feed) \n Zeilenumbruch (line feed) \NNAME Unicode-Zeichen NAME Beispiel: print(”\N{GREEK SMALL LETTER PI}”) druckt das griechische „pi” \t Horizontaler Tabulator \uXXXX 16-Bit-Unicode-Zeichen \uXXXXXXXX 32-Bit-Unicode-Zeichen \v Vertikaler Tabulator \ooo ASCII-Zeichen oktal \xhh ASCII-Zeichen hexadezimal Manchmal, wie zum Beispiel bei regulären Ausdrücken, will man nicht, dass Python diese Escape-Zeichen auswertet. Dies kann man dadurch verhindern, dass man einen String als „raw”-String definiert. Im folgenden Beispiel definieren wir erst einen „normalen” String, der Zeilenumbrüche enthält. Dann definieren wir den gleichen String als raw-String. Wir schreiben den RawString einmal mit kleinem und einmal mit großem R, um zu demonstrieren, das es keinen Unterschied gibt: >>> s = "\nEine Zeile\nund noch eine" >>> print(s) Eine Zeile und noch eine >>> s = r"\nEine Zeile\nund noch eine" >>> print(s) \nEine Zeile\nund noch eine >>> s = R"\nEine Zeile\nund noch eine" >>> print(s) \nEine Zeile\nund noch eine 35 36 5 Sequentielle Datentypen 5.1.2 Listen Listen stellen wie Strings eine sequentielle Anordnung dar. Während Strings jedoch aus einer Sequenz von beliebigen Zeichen aufgebaut sind, bestehen Listen aus einer Sequenz von beliebigen Objekten, also z.B. Integer-Zahlen, Float-Zahlen, Strings oder wieder Listen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiele: x = [3, 99, "Ein Text"] y = [42, 65 , [45, 89], 88] Wie wir in dem Beispiel gesehen haben, werden Listen mit eckigen Klammern generiert. Die Elemente einer Liste werden durch Kommata getrennt. Im Beispiel können wir auch erkennen, dass nach und vor der eckigen Klammer auch Leerzeichen stehen können, ebenso wie vor und nach einem Komma. Listen können verschachtelt sein, das heißt, dass sie andere Listen als Unterlisten enthalten können. Im Folgenden werden wir sehen, dass sie auch beliebige andere Objekte enthalten können, beispielsweise Tupel und Dictionaries. 5.1.3 Tupel Rein äußerlich unterscheiden sich Listen und Tupel durch die Art der Klammerung, d.h. Listen werden von eckigen Klammern und Tupel von runden Klammern eingefasst. Die vorigen Listenbeispiele sehen in Tupelschreibweise wie folgt aus: x = (3, 99, "Ein Text") y = (42, 65 , [45, 89], 88) Der wesentliche Unterschied zwischen Listen und Tupel besteht jedoch darin, dass Tupel nicht mehr verändert werden können. Formal kann man sagen, dass Tupel aus einer mit Kommata getrennten Folge von Referenzen auf Python-Objekte bestehen. Es können keine Referenzen aus dieser Folge entfernt, verändert oder hinzugefügt werden. Bei der Definition eines Tupels können die runden Klammern auch weggelassen werden: >>> x = 3, 99, "Ein Text" >>> x (3, 99, 'Ein Text') Tupel können auch entpackt werden, d.h. statt eines Variablennamens führt man auf der linken Seite einer Zuweisung genauso viele Variablennamen auf, wie das Tupel Elemente hat. Dies entspricht dann einer Mehrfachzuweisung: >>> minimum, maximum, text = 3, 99, "Ein Text" >>> minimum 3 >>> maximum 99 >>> text 'Ein Text' 5.2 Indizierung von sequentiellen Datentypen 5.2 Indizierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Betrachten wir den String "Hello World": Man sieht, dass die Zeichen eines Strings von links nach rechts mit 0 beginnend nummeriert sind. Von hinten (rechts) beginnt man mit -1 zu zählen. Jedes Zeichen eines Strings kann so eindeutig angesprochen werden. Dazu werden eckige Klammern benutzt, wie man im folgenden Beispiel sehen kann: >>> text = "Hallo Kaa" >>> print(text[0]) H >>> print(text[6]) K Bild 5.2 Indizierung eines Strings Mithilfe von negativen Indizes kann man die Zeichen auch von hinten aus ansprechen, also -1 für das letzte Zeichen, -2 für das vorletzte usw. >>> text = "Es geht auch von hinten!" >>> text[-1] '!' >>> text[-2] 'n' Dies funktioniert bei allen Objekten von sequentiellen Datentypen, also auch bei Listen und Tupel: >>> l = [42, 65 , [45, 89], 88] >>> l[0] 42 >>> l[2] [45, 89] >>> l[2][1] 89 >>> l[0] = "Ein neuer Wert" >>> l ['Ein neuer Wert', 65, [45, 89], 88] >>> >>> >>> t = (42, 65 , (45, 89), 88) >>> t[0] 42 >>> t[2][0] 45 >>> t[0] = "Tupel lassen sich nicht ändern!" 37 38 5 Sequentielle Datentypen Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im obigen Beispiel konnten wir sehen, dass wir nur Listen, aber keine Tupel ändern können. 5.3 Teilbereichsoperator Man kann auch Teile eines sequentiellen Datentyps ausschneiden. Im Falle eines Strings erhält man dann einen Teilstring oder bei Listen wieder eine Liste. Im Englischen wird dieses Ausschneiden als „slicing” bezeichnet. Der Begriff „Slicing” wird auch in der deutschen Python-Literatur häufig benutzt. Deshalb werden wir die beiden Begriffe im Folgenden synonym benutzen. Wie bei der Indizierung benutzt der Slicing-Operator eckige Klammern, aber nun werden statt eines Werts mindestens zwei Werte erwartet: Anfangswert und Endwert. Anfangs- und Endwert werden durch einen Doppelpunkt getrennt. Man versteht dies am besten an einem Beispiel: >>> txt = "Hello World" >>> txt[1:5] 'ello' >>> txt[0:5] 'Hello' Lässt man den Anfangswert weg (z.B. [:5] ), beginnt das Ausschneiden am Anfang des Strings (oder der Liste). Analog kann man auch den Endwert weglassen, um alles bis zum Ende zu übernehmen ( z.B. [6:] ). Lässt man Anfangs- und Endwert weg, erhält man den ganzen String (oder entsprechend die ganze Liste oder Tupel) zurück: 'Hello' >>> txt[0:-6] 'Hello' >>> txt[:5] 'Hello' >>> txt[6:] 'World' >>> txt[:] 'Hello World' Das folgende Beispiel zeigt, wie sich dies bei Listen auswirkt: >>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot'] >>> einkaufsliste[1:3] ['Bananen', 'Butter'] >>> einkaufsliste[2:] 5.3 Teilbereichsoperator ['Butter', 'Äpfel', 'Brot'] >>> einkaufsliste[:2] ['Käse', 'Bananen'] Der Slicing-Operator funktioniert auch mit drei Argumenten. Das dritte Argument gibt dann an, das wievielte Argument jedes Mal genommen werden soll, d.h. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. s[Anfang, Ende, Schrittweite]2 Ausgegeben wird eine Liste mit den folgenden Elementen von s: s[Anfang], s[Anfang + 1 * Schrittweite], . . . s[Anfang + i * Schrittweite], solange (Anfang + i * Schrittweite) < Ende ist. txt[::3] gibt jeden dritten Buchstaben eines Strings txt aus. Beispiele: >>> w = [3, 8, 12, 4, 9, 14, 23, 7, 21, 37] >>> w[::3] [3, 4, 23, 37] >>> >>> txt = "Python ist ganz toll" >>> txt[2:15:3] 'tnsgz' >>> txt[::3] 'Ph ta l' Man kann auch einen negativen Wert für die Schrittweite wählen. In diesem Fall ändert sich auch die Bedeutung der beiden ersten Werte wie folgt: s[Ende, Anfang, negative Schrittweite] Dann werden alle Werte vom Index Ende (inklusive) bis zum Index Anfang (exklusive) in dieser Reihenfolge ausgegeben. Werden für Ende und Anfang keine Werte angegeben, wird die Liste oder der String in umgekehrter Reihenfolge ausgegeben. Beispiele für negative Schrittweite: >>> x = [3, 6, 9, 12, 7] >>> x[3:1:-1] [12, 9] >>> x[4:1:-2] [7, 9] >>> x[::-1] [7, 12, 9, 6, 3] >>> s = "To be or not to be" >>> s[::-1] 'eb ot ton ro eb oT' 2 In englischer Notation: s[begin, end, step] 39 40 5 Sequentielle Datentypen >>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot'] >>> einkaufsliste[::-1] ['Brot', 'Äpfel', 'Butter', 'Bananen', 'Käse'] Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im ersten Beispiel kann man erkennen, dass die Ergebnisliste mit dem Endwert-Index „3”, also dem Wert 12, startet, aber der Startwert x[1] ist nicht im Ergebnis enthalten, da der Startwert laut Definition nie dabei ist. Auf diese Art lässt sich auch eine flache Kopie einer Liste erzeugen. Wir erkennen dies daran, dass im folgenden Beispiel einkaufsliste und einkaufsliste2 verschiedene ids haben: >>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot'] >>> einkaufsliste2 = einkaufsliste[::-1] >>> einkaufsliste2 ['Brot', 'Äpfel', 'Butter', 'Bananen', 'Käse'] >>> id(einkaufsliste), id(einkaufsliste2) (139688189461128, 139688189461064) 5.4 Die len-Funktion Eine wichtige Größe gilt es häufig zu bestimmen, egal ob es sich um Strings, Listen oder Tupel handelt: die Anzahl der Elemente. Man könnte auch sagen: die Länge eines sequentiellen Datentyps. Dazu bietet Python die len-Funktion. Wendet man diese auf einen beliebigen sequentiellen Datentyp an, liefert sie die Anzahl der Elemente zurück. Bei Strings erhalten wir damit die Anzahl der Zeichen zurück und bei Listen und Tupel die Anzahl der Elemente. >>> >>> 18 >>> >>> 4 >>> >>> 3 >>> >>> >>> 3 >>> hamlet = "to be or not to be" len(hamlet) cities = ["Berlin", "München", "Hamburg", "Köln"] len(cities) coordinates = (4,8,10) len(coordinates) mischmasch = [(3,8,"Blabla"), "Eine Zeichenkette", [(3.8, 9), 19]] len(mischmasch) 5.5 Aufgaben 5.5 Aufgaben 1. Aufgabe: Welche sequentiellen Datentypen haben Sie kennengelernt? Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 475 2. Aufgabe: Wie erhält man das erste Element einer Liste? Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 475 3. Aufgabe: Wie kann man am einfachsten das letzte Zeichen einer Liste ausgeben? Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 476 4. Aufgabe: Überlegen Sie sich, welche der folgenden Anweisungen korrekt sind. Wir haben die Ergebnisse, die von der interaktiven Python-Shell ausgeben wurden, gelöscht: >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> t = (4,7,9) s = "Ich bin ein String" l = [45,98,"787",[3,4]] t2 = (4,8,[45,98]) t[0] t[3] t(3) s[4] s[4] = "x" l[2][0] = "g" l[3][0] = "g" l t2[2][0] = 42 Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 476 41 42 5 Sequentielle Datentypen 5. Aufgabe: Welche zwei Sätze verbergen sich hinter dem folgenden scheinbaren Buchstabensalat: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> s = "DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre lsbiscthh inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg Mmaaxcihmte.." >>> Hinweis: Benutzen Sie Slicing! Beachten Sie bitte außerdem, dass sich zwischen manchen Wörtern mehr als ein Leerzeichen befindet! Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 477 6. Aufgabe: Was erhalten wir als Ergebnis, wenn wir die Funktion len auf die folgenden Objekte anwenden? Versuchen Sie, es zuerst durch Überlegen zu bestimmen. Danach können Sie es in der interaktiven Shell durch Eintippen überprüfen. >>> >>> >>> >>> >>> x = [4, 8, 9] title = "The Name of the Rose" address = ["Frank Fitmann", "Feldstr. 19", "89000 München"] nested = [(4,8,9)] book = [title, "Umberto Eco"] Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 478 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 5 5.1 Sequentielle Datentypen Übersicht Auch andere Programmiersprachen wie beispielsweise Perl und Java kennen Datentypen für Strings und Listen, aber die Leistung von Python besteht darin, dass man solche Datentypen zu einer logischen Basisklasse „Sequentielle Datentypen” zusammenfasst. Eines haben nämlich Strings, Listen und Tupel gemeinsam: Es handelt sich jeweils um Datentypen, deren Elemente fortlaufend, also sequentiell, angeordnet sind. Bei Strings handelt es sich dabei um gleichartige Objekte, d.h. Zeichen, und bei Listen oder Tupel handelt es sich um beliebige Objekte. In Python gibt es deshalb gleichnamige Methoden oder Zugriffsmethoden für sequentielle Datentypen, wenn eine solche Operation für einen Typ möglich ist. Beispielsweise kann man, wie wir in diesem Kapitel kennenlernen, mit obj[0] auf das Bild 5.1 Fotos aus dem Film „The Kiss” von erste Element eines sequentiellen Daten- 1896 typs zugreifen, egal ob es sich um einen String, eine Liste oder ein Tupel handelt. Aber man kann obj[0] nicht verändern, falls „obj” ein String ist. Python stellt die folgenden sequentiellen Datentypen zur Verfügung: 34 5 Sequentielle Datentypen 5.1.1 Zeichenketten oder Strings Neben numerischen Datentypen kennt Python auch Zeichenketten, die häufig mit dem englischen Ausdruck „Strings” bezeichnet werden. Zeichenketten bestehen aus einer unveränderlichen Folge – also Sequenz – von beliebigen Zeichen. String-Literale kann man auf verschiedene Arten schreiben: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ mit einzelnen Anführungszeichen (’): ’Dies ist ein String mit einfachen Quotes’ ■ mit doppelten Anführungszeichen ("): "Mayers’ Dackel heißt Waldi" ■ mit dreifachen Anführungszeichen (’’’) oder ("""): ’’’Er sagte "Herr Meyers’ Dackel bellt immer!"’’’ Zur Generierung von mehrzeiligen Strings gibt es zwei Möglichkeiten: ■ Die Zeile mit dem unvollendeten String mit einem Rückwärtsschrägstrich1 „\” beenden: >>> ... ... >>> Ich s = "Ich bin über mehrere Zeilen \ definiert, genau gesagt \ vier Zeilen" print(s) bin über mehrere Zeilen definiert, genau gesagt vier Zeilen Man kann erkennen, dass es nicht zu Zeilenumbrüchen kommt. Dazu muss man ein „\n” verwenden. ■ Strings können auch mit einem Paar von dreifachen Anführungszeichen umgeben werden, also """ oder ’’’. Hierbei gibt es jedoch einen Unterschied zum vorigen Vorgehen. Jetzt werden Newlines in den String übernommen, wenn wir in eine neue Zeile wechseln. Will man dies nicht, muss man einen Backslash verwenden: >>> s = """Erste Zeile ... Zweite Zeile ... Dritte Zeile \ ... und ich gehöre auch noch zur 3. Zeile""" >>> print(s) Erste Zeile Zweite Zeile Dritte Zeile und ich gehöre auch noch zur 3. Zeile Diese Form von Zeichenketten findet vor allen Dingen bei der Dokumentation von Funktionen, Methoden und Modulen Verwendung, wie wir in späteren Kapiteln kennenlernen werden. 1 Wir benutzen hier die vielen wohl ungewohnte deutsche Übersetzung für Backslash. 5.1 Übersicht Wir haben vorhin gesagt, dass man einen Zeilenvorschub mit der Zeichenfolge „\n” erzeugen kann: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> s = "Eine Zeile\nund noch eine" >>> print(s) Eine Zeile und noch eine Es gibt noch weitere Escape-Zeichen. Einige wichtige haben wir in der folgenden Liste zusammengefasst: Sequenz Beschreibung \\ Ersatzdarstellung für ein Backslash \’ Hochkomma \" Anführungszeichen \b Rückschritt (backspace) \f Seitenumbruch (form feed) \n Zeilenumbruch (line feed) \NNAME Unicode-Zeichen NAME Beispiel: print(”\N{GREEK SMALL LETTER PI}”) druckt das griechische „pi” \t Horizontaler Tabulator \uXXXX 16-Bit-Unicode-Zeichen \uXXXXXXXX 32-Bit-Unicode-Zeichen \v Vertikaler Tabulator \ooo ASCII-Zeichen oktal \xhh ASCII-Zeichen hexadezimal Manchmal, wie zum Beispiel bei regulären Ausdrücken, will man nicht, dass Python diese Escape-Zeichen auswertet. Dies kann man dadurch verhindern, dass man einen String als „raw”-String definiert. Im folgenden Beispiel definieren wir erst einen „normalen” String, der Zeilenumbrüche enthält. Dann definieren wir den gleichen String als raw-String. Wir schreiben den RawString einmal mit kleinem und einmal mit großem R, um zu demonstrieren, das es keinen Unterschied gibt: >>> s = "\nEine Zeile\nund noch eine" >>> print(s) Eine Zeile und noch eine >>> s = r"\nEine Zeile\nund noch eine" >>> print(s) \nEine Zeile\nund noch eine >>> s = R"\nEine Zeile\nund noch eine" >>> print(s) \nEine Zeile\nund noch eine 35 36 5 Sequentielle Datentypen 5.1.2 Listen Listen stellen wie Strings eine sequentielle Anordnung dar. Während Strings jedoch aus einer Sequenz von beliebigen Zeichen aufgebaut sind, bestehen Listen aus einer Sequenz von beliebigen Objekten, also z.B. Integer-Zahlen, Float-Zahlen, Strings oder wieder Listen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiele: x = [3, 99, "Ein Text"] y = [42, 65 , [45, 89], 88] Wie wir in dem Beispiel gesehen haben, werden Listen mit eckigen Klammern generiert. Die Elemente einer Liste werden durch Kommata getrennt. Im Beispiel können wir auch erkennen, dass nach und vor der eckigen Klammer auch Leerzeichen stehen können, ebenso wie vor und nach einem Komma. Listen können verschachtelt sein, das heißt, dass sie andere Listen als Unterlisten enthalten können. Im Folgenden werden wir sehen, dass sie auch beliebige andere Objekte enthalten können, beispielsweise Tupel und Dictionaries. 5.1.3 Tupel Rein äußerlich unterscheiden sich Listen und Tupel durch die Art der Klammerung, d.h. Listen werden von eckigen Klammern und Tupel von runden Klammern eingefasst. Die vorigen Listenbeispiele sehen in Tupelschreibweise wie folgt aus: x = (3, 99, "Ein Text") y = (42, 65 , [45, 89], 88) Der wesentliche Unterschied zwischen Listen und Tupel besteht jedoch darin, dass Tupel nicht mehr verändert werden können. Formal kann man sagen, dass Tupel aus einer mit Kommata getrennten Folge von Referenzen auf Python-Objekte bestehen. Es können keine Referenzen aus dieser Folge entfernt, verändert oder hinzugefügt werden. Bei der Definition eines Tupels können die runden Klammern auch weggelassen werden: >>> x = 3, 99, "Ein Text" >>> x (3, 99, 'Ein Text') Tupel können auch entpackt werden, d.h. statt eines Variablennamens führt man auf der linken Seite einer Zuweisung genauso viele Variablennamen auf, wie das Tupel Elemente hat. Dies entspricht dann einer Mehrfachzuweisung: >>> minimum, maximum, text = 3, 99, "Ein Text" >>> minimum 3 >>> maximum 99 >>> text 'Ein Text' 5.2 Indizierung von sequentiellen Datentypen 5.2 Indizierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Betrachten wir den String "Hello World": Man sieht, dass die Zeichen eines Strings von links nach rechts mit 0 beginnend nummeriert sind. Von hinten (rechts) beginnt man mit -1 zu zählen. Jedes Zeichen eines Strings kann so eindeutig angesprochen werden. Dazu werden eckige Klammern benutzt, wie man im folgenden Beispiel sehen kann: >>> text = "Hallo Kaa" >>> print(text[0]) H >>> print(text[6]) K Bild 5.2 Indizierung eines Strings Mithilfe von negativen Indizes kann man die Zeichen auch von hinten aus ansprechen, also -1 für das letzte Zeichen, -2 für das vorletzte usw. >>> text = "Es geht auch von hinten!" >>> text[-1] '!' >>> text[-2] 'n' Dies funktioniert bei allen Objekten von sequentiellen Datentypen, also auch bei Listen und Tupel: >>> l = [42, 65 , [45, 89], 88] >>> l[0] 42 >>> l[2] [45, 89] >>> l[2][1] 89 >>> l[0] = "Ein neuer Wert" >>> l ['Ein neuer Wert', 65, [45, 89], 88] >>> >>> >>> t = (42, 65 , (45, 89), 88) >>> t[0] 42 >>> t[2][0] 45 >>> t[0] = "Tupel lassen sich nicht ändern!" 37 38 5 Sequentielle Datentypen Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im obigen Beispiel konnten wir sehen, dass wir nur Listen, aber keine Tupel ändern können. 5.3 Teilbereichsoperator Man kann auch Teile eines sequentiellen Datentyps ausschneiden. Im Falle eines Strings erhält man dann einen Teilstring oder bei Listen wieder eine Liste. Im Englischen wird dieses Ausschneiden als „slicing” bezeichnet. Der Begriff „Slicing” wird auch in der deutschen Python-Literatur häufig benutzt. Deshalb werden wir die beiden Begriffe im Folgenden synonym benutzen. Wie bei der Indizierung benutzt der Slicing-Operator eckige Klammern, aber nun werden statt eines Werts mindestens zwei Werte erwartet: Anfangswert und Endwert. Anfangs- und Endwert werden durch einen Doppelpunkt getrennt. Man versteht dies am besten an einem Beispiel: >>> txt = "Hello World" >>> txt[1:5] 'ello' >>> txt[0:5] 'Hello' Lässt man den Anfangswert weg (z.B. [:5] ), beginnt das Ausschneiden am Anfang des Strings (oder der Liste). Analog kann man auch den Endwert weglassen, um alles bis zum Ende zu übernehmen ( z.B. [6:] ). Lässt man Anfangs- und Endwert weg, erhält man den ganzen String (oder entsprechend die ganze Liste oder Tupel) zurück: 'Hello' >>> txt[0:-6] 'Hello' >>> txt[:5] 'Hello' >>> txt[6:] 'World' >>> txt[:] 'Hello World' Das folgende Beispiel zeigt, wie sich dies bei Listen auswirkt: >>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot'] >>> einkaufsliste[1:3] ['Bananen', 'Butter'] >>> einkaufsliste[2:] 5.3 Teilbereichsoperator ['Butter', 'Äpfel', 'Brot'] >>> einkaufsliste[:2] ['Käse', 'Bananen'] Der Slicing-Operator funktioniert auch mit drei Argumenten. Das dritte Argument gibt dann an, das wievielte Argument jedes Mal genommen werden soll, d.h. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. s[Anfang, Ende, Schrittweite]2 Ausgegeben wird eine Liste mit den folgenden Elementen von s: s[Anfang], s[Anfang + 1 * Schrittweite], . . . s[Anfang + i * Schrittweite], solange (Anfang + i * Schrittweite) < Ende ist. txt[::3] gibt jeden dritten Buchstaben eines Strings txt aus. Beispiele: >>> w = [3, 8, 12, 4, 9, 14, 23, 7, 21, 37] >>> w[::3] [3, 4, 23, 37] >>> >>> txt = "Python ist ganz toll" >>> txt[2:15:3] 'tnsgz' >>> txt[::3] 'Ph ta l' Man kann auch einen negativen Wert für die Schrittweite wählen. In diesem Fall ändert sich auch die Bedeutung der beiden ersten Werte wie folgt: s[Ende, Anfang, negative Schrittweite] Dann werden alle Werte vom Index Ende (inklusive) bis zum Index Anfang (exklusive) in dieser Reihenfolge ausgegeben. Werden für Ende und Anfang keine Werte angegeben, wird die Liste oder der String in umgekehrter Reihenfolge ausgegeben. Beispiele für negative Schrittweite: >>> x = [3, 6, 9, 12, 7] >>> x[3:1:-1] [12, 9] >>> x[4:1:-2] [7, 9] >>> x[::-1] [7, 12, 9, 6, 3] >>> s = "To be or not to be" >>> s[::-1] 'eb ot ton ro eb oT' 2 In englischer Notation: s[begin, end, step] 39 40 5 Sequentielle Datentypen >>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot'] >>> einkaufsliste[::-1] ['Brot', 'Äpfel', 'Butter', 'Bananen', 'Käse'] Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im ersten Beispiel kann man erkennen, dass die Ergebnisliste mit dem Endwert-Index „3”, also dem Wert 12, startet, aber der Startwert x[1] ist nicht im Ergebnis enthalten, da der Startwert laut Definition nie dabei ist. Auf diese Art lässt sich auch eine flache Kopie einer Liste erzeugen. Wir erkennen dies daran, dass im folgenden Beispiel einkaufsliste und einkaufsliste2 verschiedene ids haben: >>> einkaufsliste = ['Käse', 'Bananen', 'Butter', 'Äpfel', 'Brot'] >>> einkaufsliste2 = einkaufsliste[::-1] >>> einkaufsliste2 ['Brot', 'Äpfel', 'Butter', 'Bananen', 'Käse'] >>> id(einkaufsliste), id(einkaufsliste2) (139688189461128, 139688189461064) 5.4 Die len-Funktion Eine wichtige Größe gilt es häufig zu bestimmen, egal ob es sich um Strings, Listen oder Tupel handelt: die Anzahl der Elemente. Man könnte auch sagen: die Länge eines sequentiellen Datentyps. Dazu bietet Python die len-Funktion. Wendet man diese auf einen beliebigen sequentiellen Datentyp an, liefert sie die Anzahl der Elemente zurück. Bei Strings erhalten wir damit die Anzahl der Zeichen zurück und bei Listen und Tupel die Anzahl der Elemente. >>> >>> 18 >>> >>> 4 >>> >>> 3 >>> >>> >>> 3 >>> hamlet = "to be or not to be" len(hamlet) cities = ["Berlin", "München", "Hamburg", "Köln"] len(cities) coordinates = (4,8,10) len(coordinates) mischmasch = [(3,8,"Blabla"), "Eine Zeichenkette", [(3.8, 9), 19]] len(mischmasch) 5.5 Aufgaben 5.5 Aufgaben 1. Aufgabe: Welche sequentiellen Datentypen haben Sie kennengelernt? Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 475 2. Aufgabe: Wie erhält man das erste Element einer Liste? Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 475 3. Aufgabe: Wie kann man am einfachsten das letzte Zeichen einer Liste ausgeben? Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 476 4. Aufgabe: Überlegen Sie sich, welche der folgenden Anweisungen korrekt sind. Wir haben die Ergebnisse, die von der interaktiven Python-Shell ausgeben wurden, gelöscht: >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> t = (4,7,9) s = "Ich bin ein String" l = [45,98,"787",[3,4]] t2 = (4,8,[45,98]) t[0] t[3] t(3) s[4] s[4] = "x" l[2][0] = "g" l[3][0] = "g" l t2[2][0] = 42 Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 476 41 42 5 Sequentielle Datentypen 5. Aufgabe: Welche zwei Sätze verbergen sich hinter dem folgenden scheinbaren Buchstabensalat: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> s = "DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre lsbiscthh inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg Mmaaxcihmte.." >>> Hinweis: Benutzen Sie Slicing! Beachten Sie bitte außerdem, dass sich zwischen manchen Wörtern mehr als ein Leerzeichen befindet! Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 477 6. Aufgabe: Was erhalten wir als Ergebnis, wenn wir die Funktion len auf die folgenden Objekte anwenden? Versuchen Sie, es zuerst durch Überlegen zu bestimmen. Danach können Sie es in der interaktiven Shell durch Eintippen überprüfen. >>> >>> >>> >>> >>> x = [4, 8, 9] title = "The Name of the Rose" address = ["Frank Fitmann", "Feldstr. 19", "89000 München"] nested = [(4,8,9)] book = [title, "Umberto Eco"] Lösung: Lösungen zu Kapitel 5 (Sequentielle Datentypen), Seite 478 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 6 6.1 Dictionaries Dictionaries und assoziative Felder In den vorigen Kapiteln haben wir die sequentiellen Datentypen wie Listen, Strings und Tupel eingeführt. Nun wollen wir eine weitere Kategorie von Datentypen genauer untersuchen, den Datentyp „Mapping”. In dieser Kategorie gibt es allerdings zur Zeit nur einen implementierten Typ, das Dictionary. Beim Dictionary handelt es sich um ein assoziatives Feld. Assoziative Felder werden in verschiedenen ProgramBild 6.1 Dictionary miersprachen mit unterschiedlichen Namen versehen. So spricht man in Java und C++ von einer Map, in Perl und Ruby von einem Hash und wie in Python bezeichnen auch Smalltalk, Objective-C und C# assoziative Arrays als Dictionaries. Ein Dictionary besteht aus Schlüssel-Werte-Paaren. Man könnte es sich als eine Menge von Schlüssel-Werte-Paaren vorstellen. Jedem Schlüssel-Objekt ist ein Wert-Objekt zugeordnet. Mathematisch kann man auch sagen, dass die Schlüssel auf die Werte-Objekte abgebildet werden, also eine Abbildung aus der Menge der Schlüssel in die Menge der Werte. So kann man sich auch den Kategoriennamen „Mapping” erklären, denn das englische Wort für Abbildung lautet „Mapping”. Dictionaries gehören zu den wichtigsten Datentypen von Python. Kaum ein Programm oder Skript kommt ohne Dictionaries aus. Wie Listen können Dictionaries leicht verändert werden, außerdem können sie während der Laufzeit beliebig wachsen und schrumpfen. In diesem Kapitel geht es aber nicht nur um Dictionaries, sondern am Ende beschäftigen wir uns auch noch mit dem Zusammenhang zwischen Listen und Dictionaries, d.h. wir zeigen, wie man aus Dictionaries Listen erzeugt und umgekehrt aus bestimmten Listen Dictionaries. 44 6 Dictionaries 6.2 Definition und Benutzung Wir beginnen mit dem wohl einfachsten Fall eines Dictionarys, dem leeren: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> empty = {} >>> empty {} Zu Ehren des „Paten” von Python, „Monty Python”, definieren wir nun ein spezielles kulinarisches Dictionary. Was wäre Python und auch das Internet ohne „ham”, „egg” und „spam”? >>> food = {"ham" : "yes", "egg" : "yes", "spam" : "no"} >>> print(food) {'egg': 'yes', 'ham': 'yes', 'spam': 'no'} >>> food["ham"] 'yes' >>> food["spam"] 'no' >>> food["spam"] = "yes" >>> print(food) {'egg': 'yes', 'ham': 'yes', 'spam': 'yes'} Im vorigen Beispiel können wir ein paar interessante Dinge über Dictionaries lernen. Ein Dictionary wird mit geschweiften Klammern eingegeben. Ein Schlüssel-Wert-Paar wird mit Doppelpunkt zwischen Schlüssel und Wert eingegeben. Die verschiedenen Paare werden mit Komma getrennt eingegeben. Man kann sich ein ganzes Dictionary mit print ausgeben lassen. Dabei ist es zunächst einmal irritierend, dass die Reihenfolge – so wie auch in unserem Beispiel – meist nicht mit der Reihenfolge in der Definition des Dictionary übereinstimmt. In unserer Definition stand als erstes „ham”, während unsere Ausgabe mit „egg” beginnt. Dies ist in Ordnung, da auf dem Dictionary keine Ordnung definiert ist. Python kann also die Schlüssel-Werte-Paare in einer beliebigen Reihenfolge ausgeben. Als Nächstes erkennen wir, dass wir zum Zugriff auf einen bestimmten Schlüsselwert eckige Klammern benutzen. So liefert food["ham"] den Wert für den Schlüssel "ham" zurück, d.h. ’yes’. Die Zuweisung eines neuen Werts zu einem bestehenden Schlüssel erfolgt ähnlich wie bei den Listen, nur dass wir keinen Index, sondern einen Schlüssel benutzen. So weisen wir mit der Anweisung food["spam"] = "yes" dem Schlüssel "spam" den neuen Wert ’yes’ zu. Im nächsten Beispiel kreieren wir ein einfaches deutsch-englisches Wörterbuch als Dictionary: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow ":"gelb"} >>> print(en_de) {'blue': 'blau', 'green': 'grün', 'yellow': 'gelb', 'red': 'rot'} >>> 6.2 Definition und Benutzung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> en_de["blue"] 'blau' >>> en_de["green"] 'grün' In obigem Dictionary können wir nur über die englischen Schlüssel zugreifen, also beispielsweise en_de["blue"]. Möchten wir beispielsweise wissen, wie „grün” auf Englisch heißt, so geht dies nicht.1 Um nun auch über deutsche Schlüssel zuzugreifen, müssen wir ein neues Dictionary definieren, in dem die deutschen Wörter als Schlüssel gespeichert sind und die englischen als Werte: >>> de_en = {"rot" : "red", "grün" : "green", "blau" : "blue", "gelb ":"yellow"} Wie wäre es mit einem weiteren Dictionary, zum Beispiel in Deutsch-Französisch? Versuchen Sie es doch einmal zur Übung. Falls Sie kein Französisch können: Die Farben lauten: „rouge”, „vert”, „bleu” und „jaune”. Ein deutsch-französisches Dictionary sieht nun wie folgt aus: >>> de_fr = {"rot" : "rouge", "grün" : "vert", "blau" : "bleu", "gelb ":"jaune"} Nun sind wir in der Lage, mit der Verknüpfung der beiden Dictionaries de_fr und en_de von Englisch nach Französisch zu übersetzen, obwohl wir gar kein Englisch-FranzösischWörterbuch haben. So gibt uns de_fr[en_de["red"]] das französische Wort für „red”, also „rouge”: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow ":"gelb"} >>> de_fr = {"rot" : "rouge", "grün" : "vert", "blau" : "bleu", "gelb ":"jaune"} >>> print("The French word for 'red' is " + de_fr[en_de['red']]) The French word for 'red' is rouge Was passiert, wenn wir auf eine Farbe zugreifen wollen, die gar nicht existiert? Wir probieren dies im Folgenden: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow ":"gelb"} >>> en_de["brown"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'brown' Wir generieren also einen Fehler, den sogenannten KeyError, wenn wir auf einen nicht existierenden Schlüssel zuzugreifen versuchen. Um solche Fehler zu vermeiden, werden häufig bedingte Anweisungen verwendet. Auf diese gehen wir erst in Kapitel 9 (Verzweigungen) ein. Dennoch möchten wir Ihnen das 1 Nicht ohne Programmierung jedenfalls 45 46 6 Dictionaries typische Vorgehen hier nicht vorenthalten. Anfänger, die noch nie programmiert haben, können die folgenden Abschnitte getrost übergehen, damit sie keine Verständnisprobleme haben. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 6.3 Fehlerfreie Zugriffe auf Dictionaries Mit dem Schlüsselwort „in” kann geprüft werden, ob ein Index in einem Dictionary vorkommt: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"} >>> "red" in en_de True >>> "brown" in en_de False Damit kann man mittels einer bedingten Anweisung prüfen, ob eine Farbe bereits als Schlüssel enthalten ist oder nicht. In dem folgenden kleinen Programm benutzen wir noch eine weitere Anweisung, auf die wir auch erst später wieder eingehen: die inputAnweisung. Das Programm bleibt bei einer input-Anweisung stehen und druckt den Text aus, der als Argument angegeben wird, also in unserem ersten Aufruf „Farbe?”. Dann kann der Benutzer des Programms eine Eingabe vornehmen, die von input als String zurückgeliefert wird. In unserem Beispiel speichern wir den String in der Variablen „colour”: en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"} colour = input("Farbe? ") if colour in en_de: print("Die Farbe " + colour + " ist ein Schlüssel") print("Der deutsche Wert für " + colour + " ist " + en_de[colour]) else: print("Die Farbe " + colour + " ist noch kein Schlüssel") colour_de = input("Deutsch für " + colour + "? ") en_de[colour] = colour_de print("Nun kennen wir auch " + colour + ":") print(en_de) Wir starten dieses Programm zweimal: einmal mit einer Farbe, die bereits im Dictionary enthalten ist, und einmal mit einer, die noch nicht enthalten ist: $ python3 guarded_dictionary_access.py Farbe? red Die Farbe red ist ein Schlüssel Der deutsche Wert für red ist rot $ python3 guarded_dictionary_access.py 6.4 Zulässige Typen für Schlüssel und Werte Farbe? brown Die Farbe brown ist noch kein Schlüssel Deutsch für brown? braun Nun kennen wir auch brown: {'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'red': 'rot'} Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Alternativ zu obigem Vorgehen, also dem Zugriff auf einen Schlüssel über den Zugriffsoperator, kann man auch die Listen-Methode get benutzen. Ruft man D.get(key[, default])2 für ein Dictionary D auf, wird D[key] zurückgeliefert, falls key ein Schlüssel von D ist. Falls key kein Schlüssel von D ist, wird der Default-Wert „default” zurückgeliefert, falls get mit einem Default-Wert aufgerufen worden ist, ansonsten wird der spezielle Wert None zurückgeliefert. Dies bedeutet, dass man bei der Benutzung der Methode get keinen Fehler erzeugt, falls ein Schlüssel nicht existiert. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow ":"gelb"} >>> en_de.get("red") 'rot' >>> en_de.get("brown") >>> en_de.get("brown","spam") 'spam' Dictionaries, deren Schlüssel aus Strings bestehen, die den Konventionen für Variablennamen entsprechen – also nur aus Buchstaben, Ziffern oder dem Unterstrich („_”) bestehen, aber nicht mit einer Ziffer beginnen –, können auch einfacher definiert werden. Wir zeigen dies an dem vorher definierten Dictionary „en_de”: >>> en_de = dict(red="rot", green="grün", blue="blau", yellow="gelb") >>> en_de {'yellow': 'gelb', 'blue': 'blau', 'green': 'grün', 'red': 'rot'} 6.4 Zulässige Typen für Schlüssel und Werte In unseren bisherigen Beispielen zu Dictionaries haben wir nur Strings als Schlüssel verwendet, sodass leicht der Eindruck entstehen könnte, dass nur Strings als Schlüssel zulässig sind. Als Schlüssel können jedoch Instanzen aller unveränderlichen (immutable) Datentypen verwendet werden, d.h. Integer, Floats, Strings oder Tupel, jedoch keine veränderlichen Objekte wie beispielsweise Listen oder Dictionaries. 2 Die eckigen Klammen haben in dieser Definition nichts mit Listenbegrenzern zu tun. Vielmehr bedeuten sie, dass der Parameter „default” optional ist. In anderen Worten: „get” kann man entweder mit einem oder zwei Argumenten aufrufen, d.h. D.get(key) oder D.get(key, default). 47 48 6 Dictionaries >>> staedte_GPS = { (52.520007, 13.404954):"Berlin", (47.767097, 8.872239):"Singen am Hohentwiel" } >>> ports = {21:"File Transfer Protocol (FTP)", 22:"Secure Shell (SSH) ", 23:"Telnet remote login service"} >>> adressen = {"Henry":[ ("Henry", "Peterson"), 20016, "Hamburg"]} Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Verwendet man veränderliche Werte als Schlüssel, erhält man eine Fehlermeldung: >>> dic = { [1,2,3]:"abc"} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' Tupel als Schlüssel sind in Ordnung, wie wir im folgenden Beispiel sehen: >>> dic = { (1,2,3):"abc", 3.1415:"abc"} >>> dic {(1, 2, 3): 'abc'} 6.5 Verschachtelte Dictionaries Wir zeigen nun, wie man Dictionaries verschachtelt. Verschachtelte Dictionaries werden häufig auch mit ihrem englischen Begriff als „Nested Dictionaries” bezeichnet. Um dieses Verfahren zu demonstrieren, peppen wir unsere Wörterbücher für natürliche Sprachen noch ein wenig auf. Dazu definieren wir ein Dictionary von Dictionaries: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow ":"gelb"} >>> de_fr = {"rot" : "rouge", "grün" : "vert", "blau" : "bleu", "gelb ":"jaune"} >>> >>> dictionaries = {"en_de" : en_de, "de_fr" : de_fr } >>> >>> print(dictionaries["de_fr"]["blau"]) bleu 6.6 Methoden auf Dictionaries Im Folgenden bezeichnet D ein Dictionary, wenn wir es nicht anders vermerkt haben. clear(...) Löscht alle Einträge eines Dictionarys. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", " yellow":"gelb"} 6.6 Methoden auf Dictionaries >>> en_de.clear() >>> en_de {} Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. copy(...) D.copy() erzeugt eine flache Kopie von D.3 Anfänger machen häufig den Fehler zu glauben, dass die Zuweisung eines Dictionarys an eine Variable bereits einer Kopie entspricht. Dass dies nicht so ist, demonstrieren wir im folgenden Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", " yellow":"gelb"} >>> d = en_de >>> d["yellow"] = "gelblich" >>> d {'blue': 'blau', 'green': 'grün', 'yellow': 'gelblich', 'red': 'rot '} >>> en_de {'blue': 'blau', 'green': 'grün', 'yellow': 'gelblich', 'red': 'rot '} Wäre „d” in obigem Beispiel eine Kopie, hätte sich der Wert von „en_de” nicht ändern dürfen. Eine flache Kopie erhalten wir mit der Methode copy, wie wir im folgenden Beispiel sehen: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", " yellow":"gelb"} >>> d = en_de.copy() >>> d["yellow"] = "gelblich" >>> d {'blue': 'blau', 'green': 'grün', 'red': 'rot', 'yellow': 'gelblich '} >>> en_de {'blue': 'blau', 'green': 'grün', 'yellow': 'gelb', 'red': 'rot'} fromkeys(...) dict.fromkeys(S[,v]) liefert ein Dictionary zurück mit den Werten des unveränderlichen sequentiellen Datentyps S als Schlüssel und dem optionalen Wert „v”, der jedem Schlüssel automatisch zugeordnet wird. Wird „v” nicht angegeben, werden alle Werte auf None gesetzt: Beispiel: >>> food = ("ham", "eggs", "spam") >>> d = dict.fromkeys(food) >>> d {'eggs': None, 'ham': None, 'spam': None} >>> d = dict.fromkeys(food, "enjoy") >>> d {'eggs': 'enjoy', 'ham': 'enjoy', 'spam': 'enjoy'} 3 Auf die Unterschiede zwischen flacher und tiefer Kopie gehen wir in Kapitel 13 (Flaches und tiefes Kopieren) ein. 49 50 6 Dictionaries items(...) D.items() liefert ein Mengen-ähnliches Objekt vom Type „dict_item” zurück, was einer View der Schlüssel-Werte-Paare entspricht. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", " yellow":"gelb"} >>> x = en_de.items() >>> type(x) <class 'dict_items'> >>> x dict_items([('blue', 'blau'), ('green', 'grün'), ('yellow', 'gelb'), ('red', 'rot')]) keys(...) D.keys() liefert ein Mengen-ähnliches Objekt vom Type „dict_item” zurück, was einer View der Schlüssel entspricht. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", " yellow":"gelb"} >>> x = en_de.keys() >>> type(x) <class 'dict_keys'> >>> x dict_keys(['blue', 'green', 'yellow', 'red']) pop(...) D.pop(k[,d]) entfernt den Schlüssel „k” zusammen mit seinem Wert aus dem Dictionary. Die Methode liefert den Wert D[k] zurück. Entspricht „k” keinem Schlüssel, dann wird ein KeyError generiert, außer pop wurde mit dem optionalen Parameter „d” aufgerufen. In diesem Fall liefert D.pop(k,d) den Wert „d” zurück. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", " yellow":"gelb"} >>> x = en_de.pop("red") >>> x 'rot' >>> en_de {'blue': 'blau', 'green': 'grün', 'yellow': 'gelb'} >>> x = en_de.pop("brown") Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'brown' >>> x = en_de.pop("brown","spam") >>> x 'spam' popitem(...) D.popitem() wird ohne Parameter aufgerufen und liefert ein beliebiges Schlüssel-Wert-Paar (k, v) zurück, was dann aus dem Dictionary entfernt wird. Falls das Dictionary leer ist, wird ein KeyError generiert. 6.6 Methoden auf Dictionaries Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", " yellow":"gelb"} >>> en_de.popitem() ('blue', 'blau') >>> en_de.popitem() ('green', 'grün') >>> en_de.popitem() ('yellow', 'gelb') >>> en_de.popitem() ('red', 'rot') >>> en_de.popitem() Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'popitem(): dictionary is empty' setdefault(...) D.setdefault(k[,d]) setzt D[k] auf den Wert d, falls der Schlüssel k noch nicht in D enthalten ist. Falls k bereits in D enthalten ist, verändert diese Methode das Dictionary D nicht. Falls der optionale Parameter „d” nicht angeben wird, wird D[k] auf den Wert None gesetzt, falls der Schlüssel k noch nicht in D enthalten ist. Die Methode liefert den Wert von D[k] zurück. >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"} >>> en_de.setdefault("brown", "braun") 'braun' >>> en_de {'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'red': 'rot'} >>> en_de.setdefault("green", "verde") 'grün' >>> en_de {'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'red': 'rot'} >>> en_de.setdefault("yellow") >>> en_de {'blue': 'blau', 'brown': 'braun', 'green': 'grün', 'yellow': None, 'red': 'rot'} update(...) Fügt ein Dictionary d2 zu d hinzu und überschreibt gegebenenfalls die Werte von bereits vorhandenen Schlüsseln. Beispiel: >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"} >>> >>> en_de2 = {"yellow":"gelb", "red":"rötlich"} >>> en_de.update(en_de2) >>> en_de {'blue': 'blau', 'green': 'grün', 'yellow': 'gelb', 'red': 'rötlich '} 51 52 6 Dictionaries Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau"} >>> en_de2 = {"yellow":"gelb", "red":"rötlich"} >>> en_de2.update(en_de) >>> en_de2 {'blue': 'blau', 'green': 'grün', 'red': 'rot', 'yellow': 'gelb'} 6.7 Operatoren auf Dictionaries Auch auf Dictionaries ist eine „Länge” definiert. Die Methode len() liefert die Anzahl der Schlüssel-Werte-Paare zurück, was der Anzahl der verschiedenen Schlüssel entspricht. >>> en_de = {"red" : "rot", "green" : "grün", "blue" : "blau", "yellow ":"gelb"} >>> len(en_de) 4 6.8 Die zip-Funktion Diese sehr nützliche Funktion4 kann dazu genutzt werden, aus Listen mit bestimmten Eigenschaften Dictionaries zu erzeugen. Ebenso kann sie dazu benutzt werden, bestimmte Dictionaries umzukehren. Wir werden dies in diesem Kapitel demonstrieren. zip wird auf eine beliebige Folge von iterierbaren Objekten, also beispielsweise Strings, Listen und Tupel, angewandt. zip liefert einen Iterator zurück, der Tupel liefert. Zuerst ein Tupel mit den ersten Elementen der Eingabeargumente, dann die zweiten, dritten usw. zip stoppt, sobald eines der iterierbaren Objekte aufgebraucht ist. Am besten kann man sich die Arbeitsweise von zip mit folgendem Bild veranschaulichen: Beispiele: >>> namen = ["Peter", "Sarah", "Maria", "Frank", "Eddie"] >>> obst = ["Äpfel", "Birnen", "Bananen", "Kirschen", "Orangen"] >>> >>> for name, frucht in zip(namen, obst): 4 Eigentlich handelt es sich bei zip um eine Klasse! Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 6.8 Die zip-Funktion ... print(name + " liebt " + frucht) ... Peter liebt Äpfel Sarah liebt Birnen Maria liebt Bananen Frank liebt Kirschen Eddie liebt Orangen >>> >>> for zaehler, name, frucht in zip(range(1, 6), namen, obst): ... print(str(zaehler) + ". " + name + " liebt " + frucht) ... 1. Peter liebt Äpfel 2. Sarah liebt Birnen 3. Maria liebt Bananen 4. Frank liebt Kirschen 5. Eddie liebt Orangen Für Leute mit Kenntnissen in linearer Algebra ist es meist sehr hilfreich zu wissen bzw. zu erkennen, dass zip gewissermaßen eine Matrix transponiert. Dazu stellt man sich vor, dass die iterierbaren Objekte der Eingaben die Zeilen einer Matrix darstellen: >>> z1 = [11, 12, 13] >>> z2 = [21, 22, 23] >>> z3 = [31, 32, 33] >>> >>> T = zip(z1, z2, z3) >>> T <zip object at 0x911ffac> >>> list(T) [(11, 21, 31), (12, 22, 32), (13, 23, 33)] Aus dem obigen zip-Objekt T kann man nur einmal eine solche Liste erzeugen. Danach ist das zip-Objekt gewissermaßen „aufgebraucht”, d.h. wir haben alle Elemente iteriert. Versucht man nochmals, eine Liste zu erzeugen, erhält man deshalb eine leere Liste: >>> list(T) [] Eigentlich hatten wir anfangs gesagt, dass man mit zip eine Matrix transponieren kann. Wir hatten jedoch drei Vektoren an zip als Parameter übergeben. Man könnte sich auch eine Liste „Z” mit den obigen drei Vektoren z1, z2 und z3 vorstellen. Das entspräche dann einer Matrix, und die drei Vektoren könnten als Zeilenvektoren aufgefasst werden. >>> >>> Z = [ [11, 12, 13], ... [21, 22, 23], ... [31, 32, 33]] 53 54 6 Dictionaries Den *-Operator beim Funktionsaufruf werden wir erst später erläutern5 , aber im folgenden Beispiel bewirkt er ein Entpacken der Liste, d.h. zip wird mit den drei Unterlisten (den Zeilenvektoren der Matrix) als Parameter aufgerufen. >>> TZ = zip(*Z) >>> list(TZ) [(11, 21, 31), (12, 22, 32), (13, 23, 33)] Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. zip(*Z) entspricht dem Aufruf zip([11, 12, 13], [21, 22, 23], [31, 32, 33]) Besonders interessant im Zusammenhang mit Dictionaries ist der Fall, in dem wir zwei flache Listen oder Tupel miteinander verknüpfen. >>> l1 = [11, 12, 13, 14] >>> l2 = [21, 22, 23, 24] >>> list(zip(l1,l2)) [(11, 21), (12, 22), (13, 23), (14, 24)] Sind die Argumente von zip verschieden lang, dann arbeitet zip nur auf den Argumenten bis zur kleinsten Länge. Alle darüber hinaus existierenden Elemente werden ignoriert. >>> l1 = [11, 12, 13] >>> l2 = [21, 22, 23, 24] >>> list(zip(l1,l2)) [(11, 21), (12, 22), (13, 23)] 6.9 Dictionaries aus Listen erzeugen Betrachten wir die beiden folgenden Listen: >>> gerichte = ["Pizza", "Sauerkraut", "Paella", "Hamburger"] >>> laender = ["Italien","Deutschland","Spanien","USA"] Auch wenn mittlerweile Hamburger weltweit verbreitet sind, wenn vielleicht mehr Pizzen in Deutschland als in Italien gegessen werden und Sauerkraut nicht mehr zu den deutschen Lieblingsgerichten gehört, bilden wir dennoch ein Dictionary, was alte Stereotypen bedient: Bei der Eingabe eines Landes als Schlüssel soll als Wert das landesspezifische Gericht erscheinen. Schauen wir uns zunächst einmal an, was passiert, wenn wir obige Listen mit zip verknüpfen: >>> list(zip(laender, gerichte)) [('Italien', 'Pizza'), ('Deutschland', 'Sauerkraut'), ('Spanien', ' Paella'), ('USA', 'Hamburger')] Wenn Sie im vorigen Kapitel unter der Dictionary-Methode item nachschauen, werden Sie erkennen, dass die von item zurückgelieferte Liste von 2er-Tupel obiges Aussehen hat. Des- 5 Auf diesen Mechanismus gehen wir in Kapitel Funktionen, Seite 128, näher ein. 6.10 Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. halb ist es nicht verwunderlich, dass ein dict-Casting auf das obige Ergebnis das gewünschte Dictionary zurückliefert: >>> landesueblich = dict(zip(laender, gerichte)) >>> landesueblich {'Spanien': 'Paella', 'Italien': 'Pizza', 'Deutschland': 'Sauerkraut', 'USA': 'Hamburger'} >>> landesueblich["Deutschland"] 'Sauerkraut' >>> landesueblich["USA"] 'Hamburger' 6.10 Aufgaben 1. Aufgabe: Bild 6.2 Dateimenü Stellen Sie sich vor, wir wollen in einem Programm die Bezeichnungen für ein Dateimenü internationalisieren, also z.B. in Deutsch, Englisch, Französisch und Italienisch anbieten. Wir wollen ein verschachteltes Dictionary verwenden, in dem wir die Übersetzung für die Menüpunkte zum Speichern, Speichern unter neuem Namen, Neue Datei öffnen, Öffnen und Drucken angeben. Zum Übersetzen der Begriffe: Englisch – Deutsch – Französisch – Italienisch File – Datei – Fichier – File New – Neu – Nouveau – Nuovo Open – Öffnen – Ouvrir – Apri Save – Speichern – Enregistrer – Salva Save as – Speichern unter – Enregistrer sous – Salva come 55 56 6 Dictionaries Print Preview – Druckansicht – Apercu avant impressioner – Anteprima di stampa Print – Drucken – Imprimer – Stampa Close – Schließen – Fermer – Chiudi Exit – Verlassen – Quitter – Esci Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Lösung: Lösungen zu Kapitel 6 (Dictionaries), Seite 478 2. Aufgabe: Bild 6.3 Schachbrett Überlegen Sie sich eine Darstellung des Schachbretts als Dictionary. Die Schlüssel dieses Dictionaries sollen die Felder des Schachbretts sein und die Werte die Information, welche Figur sich auf einem Feld befindet. Das Ergebnis könnte also beispielsweise von der Form („König”, „schwarz”) sein. Ein Schachbrett besteht bekanntlich aus 64 Feldern, die in 8 Zeilen und 8 Spalten angeordnet sind. Ein Feld des Schachbretts kann mit einem Tupel bezeichnet werden. Die erste Komponente dieses Tupels entspricht den Spalten des Spielfeldes. Diese werden mit Buchstaben zwischen „a” und „h” von links nach rechts gekennzeichnet. Die zweite Komponente stellt eine Zahl zwischen 1 und 8 dar. Lösung: Lösungen zu Kapitel 6 (Dictionaries), Seite 479 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 7 7.1 Mengen Übersicht Auch wenn die Mengenlehre über lange Zeit heftig kritisiert wurde und immer noch kritisiert wird, ist sie ein wesentliches Gebiet der Mathematik. Die heutige Mathematik ist in der Terminologie der Mengenlehre formuliert und baut auf deren Axiomen auf. Die Mengenlehre ist noch ein recht junges Gebiet der Mathematik. Der deutsche Mathematiker Georg Cantor (1845 - 1918) begründete die Mengenlehre mit seinem 1874 erschienenen Artikel „Über eine Eigenschaft des Inbegriffes aller reellen algebraischen Zahlen”. Anfangs, also bis ins Jahr 1877, bezeichnete er übrigens die Mengenlehre noch als „Mannigfaltigkeitslehre”. Bild 7.1 Mengen-Diagramme 1895 gab er folgende Definition einer Menge: „Unter einer ,Menge’ verstehen wir jede Zusammenfassung M von bestimmten wohlunterschiedenen Objekten m unserer Anschauung oder unseres Denkens (welche die ,Elemente’ von M genannt werden) zu einem Ganzen.” Eine Menge kann beliebige Elemente enthalten: zum Beispiel Zahlen, Zeichen, Buchstaben, Wörter, Namen oder sogar andere Mengen. Eine Menge wird in der Mathematik üblicherweise mit einem Großbuchstaben bezeichnet. 7.2 Mengen in Python Der Datentyp „set”, der ein sogenannter „collection”-Typ ist, ist in Python seit Version 2.4. enthalten. Ein set enthält eine ungeordnete Sammlung von einmaligen und unveränderlichen Elementen. In anderen Worten: Ein Element kann in einem set-Objekt nicht mehrmals vorkommen, was bei Listen und Tupel jedoch möglich ist. Beim Datentyp set handelt es sich um die Python-Implementierung von Mengen, wie sie aus der Mathematik bekannt sind. 58 7 Mengen 7.2.1 Sets erzeugen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Will man eine Menge erzeugen, so ist dies in Python sehr einfach. Man bedient sich der gewohnten mathematischen Schreibweise. >>> staedte = {'Hamburg', 'München', 'Frankfurt', 'Berlin'} >>> print(staedte) {'München', 'Berlin', 'Frankfurt', 'Hamburg'} >>> 'Berlin' in staedte True >>> 'Köln' in staedte False Im obigen Beispiel mag vielleicht verwundern, dass wir die geschweiften Klammern für Mengen verwenden, da wir diese ja bisher für Dictionaries verwendet hatten. Nun wollen wir zeigen, was passiert, wenn wir ein Tupel mit wiederholt auftretenden Elementen an die set-Funktion übergeben – in unserem Beispiel tritt die Stadt Paris mehrmals auf: >>> cities = set(("Paris", "Lyon", "London","Berlin","Paris"," Birmingham")) >>> cities {'Paris', 'Birmingham', 'Lyon', 'London', 'Berlin'} >>> Wie erwartet, gibt es keine doppelten Einträge in unserer resultierenden Menge. Im folgenden Beispiel wird mit dem Casting-Operator set ein String in seine Zeichen vereinzelt, um die Menge der im String enthaltenen Buchstaben zu erhalten: >>> x = set("A Python Tutorial") >>> x {'A', ' ', 'i', 'h', 'l', 'o', 'n', 'P', 'r', 'u', 't', 'a', 'y', 'T'} >>> type(x) <class 'set'> >>> 7.2.2 Mengen von unveränderlichen Elementen Sets sind so implementiert, dass sie keine veränderlichen (mutable) Objekte erlauben. Das folgende Beispiel demonstriert, dass wir beispielsweise keine Listen als Elemente haben können: >>> cities = set((("Python","Perl"), ("Paris", "Berlin", "London"))) >>> cities = set((["Python","Perl"], ["Paris", "Berlin", "London"])) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' 7.3 Frozensets 7.3 Frozensets Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Auch wenn sets keine veränderlichen Elemente enthalten können, sind sie selbst veränderlich. Wir können zum Beispiel neue Elemente einfügen: >>> cities = {"Frankfurt", "Basel","Freiburg"} >>> cities.add("Strasbourg") >>> cities {'Freiburg', 'Frankfurt', 'Basel', 'Strasbourg'} Frozensets sind wie sets, aber sie können nicht verändert werden. Sie sind also unveränderlich (immutable): >>> cities = frozenset(["Frankfurt", "Basel","Freiburg"]) >>> cities.add("Strasbourg") Traceback (most recent call last): File "<stdin&module>", line 1, in <module> AttributeError: 'frozenset' object has no attribute 'add' >>> 7.4 7.4.1 Operationen auf „set”-Objekten add(element) Mit der Methode add fügt man ein Objekt in eine Menge als neues Element ein, falls es noch nicht vorhanden ist. Es gilt zu beachten, dass es sich dabei um ein unveränderliches Element handelt. Im folgenden Beispiel fügen wir einen String ein: >>> colours = {"red","green"} >>> colours.add("yellow") >>> colours {'green', 'yellow', 'red'} >>> colours.add(["black","white"]) Traceback (most recent call last): File "<stdin&module>", line 1, in <module> TypeError: unhashable type: 'list' >>> Selbstverständlich wird ein Objekt nur dann als neues Element eingefügt, wenn es noch nicht enthalten ist. Ist es bereits enthalten, hat der Aufruf der Methode keine Auswirkungen. 59 60 7 Mengen 7.4.2 clear() Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Alle Elemente einer Menge werden entfernt. Die Menge ist also anschließend leer, wie wir im folgenden Beispiel sehen: >>> cities = {"Stuttgart", "Konstanz", "Freiburg"} >>> cities.clear() >>> cities set() >>> 7.4.3 copy copy erzeugt eine flache Kopie einer Menge, die zurückgeliefert wird. Wir demonstrieren die Benutzung anhand eines Beispiels: >>> more_cities = {"Winterthur","Schaffhausen","St. Gallen"} >>> cities_backup = more_cities.copy() >>> more_cities.clear() >>> cities_backup {'St. Gallen', 'Winterthur', 'Schaffhausen'} >>> Nur für diejenigen, die glauben, dass eine einfache Zuweisung auch genügen könnte: >>> more_cities = {"Winterthur","Schaffhausen","St. Gallen"} >>> cities_backup = more_cities >>> more_cities.clear() >>> cities_backup set() >>> Die Zuweisung „cities_backup = more_cities” erzeugt nur einen Pointer, d.h. einen weiteren Namen für das gleiche Objekt. 7.4.4 difference() Diese Methode liefert die Differenz von zwei oder mehr Mengen zurück. Wir illustrieren dies wie immer an einem Beispiel: >>> x = {"a","b","c","d","e"} >>> y = {"b","c"} >>> z = {"c","d"} >>> x.difference(y) set(['a', 'e', 'd']) >>> x.difference(y).difference(z) {'a', 'e'} >>> 7.4 Operationen auf „set”-Objekten Statt die Methode difference zu benutzen, hätten wir auch den Operator „-” benutzen können: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> x {'a', >>> x {'a', >>> 7.4.5 - y 'e', 'd'} - y - z 'e'} difference_update() Die Methode difference_update entfernt alle Elemente einer anderen Menge aus einer Menge. „x.difference_update()” ist gleichbedeutend mit „x = x - y” >>> x = {"a","b","c","d","e"} >>> y = {"b","c"} >>> x.difference_update(y) >>> x {'a', 'e', 'd'} >>> >>> x = {"a","b","c","d","e"} >>> y = {"b","c"} >>> x = x - y >>> x {'a', 'e', 'd'} >>> 7.4.6 discard(el) Das Element el wird aus einer Menge entfernt, falls es enthalten ist. Falls el nicht in der Menge enthalten ist, passiert nichts. >>> x = {"a","b","c","d","e"} >>> x.discard("a") >>> x {'c', 'b', 'e', 'd'} >>> x.discard("z") >>> x {'c', 'b', 'e', 'd'} >>> 61 62 7 Mengen 7.4.7 remove(el) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Funktioniert wie discard(), aber falls el nicht in der Menge enthalten ist, wird ein Fehler generiert, d.h. ein KeyError: >>> x = {"a","b","c","d","e"} >>> x.remove("a") >>> x {'c', 'b', 'e', 'd'} >>> x.remove("z") Traceback (most recent call last): File "<stdin&module>", line 1, in <module> KeyError: 'z' >>> 7.4.8 intersection(s) Liefert die Schnittmenge von s und der Instanzmenge zurück. >>> x = {"a","b","c","d","e"} >>> y = {"c","d","e","f","g"} >>> x.intersection(y) {'c', 'e', 'd'} >>> Dies kann auch mit dem „&”-Zeichen formuliert werden: >>> x = {"a","b","c","d","e"} >>> y = {"c","d","e","f","g"} >>> x & y {'c', 'e', 'd'} >>> 7.4.9 isdisjoint() Diese Methode liefert True zurück, wenn zwei Mengen eine leere Schnittmenge haben. >>> x = {"a","b"} >>> y = {"c","d"} >>> z = {"b","c"} >>> x.isdisjoint(y) True >>> x.isdisjoint(z) False 7.4 Operationen auf „set”-Objekten 7.4.10 issubset() Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. x.issubset(y) liefert True zurück, falls x eine Untermenge von y ist. „<=” kann statt des Aufrufs der Methode verwendet werden. „<” prüft, ob es sich um eine echte Untermenge handelt: Wenn x < y gilt, dann enthält y mindestens ein Element, das nicht in x enthalten ist. >>> x = {"a","b","c","d","e"} >>> y = {"c","d"} >>> x.issubset(y) False >>> y.issubset(x) True >>> x < y False >>> y < x # y ist eine echte Untermenge von x True >>> x < x # eine Menge kann nie eine echte Untermenge ihrer selbst sein. False >>> x <= x True >>> 7.4.11 issuperset() x.issuperset(y) liefert True zurück, falls x eine Obermenge von y ist. „>=” kann statt des Aufrufs der Methode verwendet werden. Ein einfaches Größer-Zeichen „>” prüft, ob es sich um eine echte Obermenge handelt: Wenn x > y gilt, dann enthält x mindestens ein Element, das nicht in y enthalten ist. >>> x = {"a","b","c","d","e"} >>> y = {"c","d"} >>> x.issuperset(y) True >>> x > y True >>> x >= y True >>> x >= x True >>> x > x False >>> x.issuperset(x) True >>> 63 64 7 Mengen 7.4.12 pop() Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. pop() liefert ein beliebiges Element der Menge zurück. Dieses Element wird dabei aus der Menge entfernt. Die Methode erzeugt einen KeyError, falls die Menge leer ist. >>> x = {"a","b","c","d","e"} >>> x.pop() 'a' >>> x.pop() 'c' Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 8 8.1 Eingaben Eingabe mittels input Es gibt kaum Programme, die ohne jegliche Eingaben auskommen. Eingaben können über viele Wege erfolgen, so zum Beispiel aus einer Datenbank, von einem anderen Rechner im lokalen Netzwerk oder auch über das Internet. Die einfachste und wohl auch häufigste Eingabe erfolgt jedoch über die Tastatur. Für diese Form der Eingabe bietet Python die Funktion input (Eingabeprompt). Kommt es zum Aufruf der Funktion input während eines Programmlaufs, wird der Programmablauf solange gestoppt, bis die Benutzerin oder der Benutzer eine Eingabe über die Tastatur tätigt und diese mit der Return-Taste abschließt. Damit der User auch weiß, was er einzugeben hat, wird der String des Parameters „Eingabeprompt” ausgegeben, sofern ein solcher String existiert. Der Parameter „Eingabeprompt” von input() ist optional. Der Eingabestring des Benutzers wird von input() nicht interpretiert, d.h. input() liefert immer einen String zurück: >>> eingabe = input("Ihre Eingabe? ") Ihre Eingabe? 34 >>> eingabe '34' >>> eingabe = input("Ihre Eingabe? ") Ihre Eingabe? [3,5,8] >>> eingabe '[3,5,8]' >>> type(eingabe) <class 'str'> >>> Bild 8.1 Eingabe 66 8 Eingaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Meistens wird auf den Ergebnisstring von input sofort der entsprechende cast-Operator angewendet, um den für die Weiterverarbeitung benötigten Datentyp zu erhalten: >>> alter = int(input("Ihr Alter? ")) Ihr Alter? 42 >>> alter, type(alter) (42, <class 'int'>) >>> >>> preis = float(input("Artikelpreis? ")) Artikelpreis? 33.99 >>> preis, type(preis) (33.99, <class 'float'>) >>> Eine unangenehme Überraschung erlebt manche Anfängerin oder Anfänger, wenn man eine Liste einlesen will und diese entsprechend mittels „list” umwandelt: >>> farben = list(input("Farben? ")) Farben? ["rot", "grün", "blau"] >>> farben ['[', '"', 'r', 'o', 't', '"', ',', ' ', '"', 'g', 'r', 'ü', 'n', '"', ',', ' ', '"', 'b', 'l', 'a', 'u', '"', ']'] >>> Eigentlich ist es klar und logisch, was passiert ist. Python wandelt den String in eine Liste, indem jedes einzelne Zeichen zu einem Listenelement wird. Wir hätten aber gerne die einzelnen Farben als Elemente. Dazu müssen wir die Funktion eval benutzen. eval interpretiert die Eingabe und liefert den entsprechenden Datentyp zurück. Dies funktioniert nicht nur für Listen, sondern auch für andere Datentypen wie beispielsweise Tupel und Dictionaries. >>> farben = eval(input("Farben? ")) Farben? ["rot", "grün", "blau"] >>> farben, type(farben) (['rot', 'grün', 'blau'], <class 'list'>) >>> >>> haeufigkeiten = eval(input("Häufigkeiten? ")) Häufigkeiten? {"a":5, "b":7, "c":3} >>> haeufigkeiten, type(haeufigkeiten) ({'a': 5, 'c': 3, 'b': 7}, <class 'dict'>) >>> Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 9 9.1 Verzweigungen Anweisungsblöcke und Einrückungen Eigentlich geht es in diesem Kapitel um Verzweigungen. Allerdings müssen wir dazu wissen, wie man mehrere Anweisungen zu einem Block, einem Anweisungsblock, zusammenfasst. Außerdem lassen sich Blöcke sehr gut im Zusammenhang mit Verzweigungen erklären. Wir kennen das Problem der Blockbildung und der Kennzeichnung der Zusammengehörigkeit auch aus der Umgangssprache: Wenn es morgen regnet, werde ich den Keller entrümpeln. Dann werde ich die Schränke aufräumen und die Fotos sortieren. Im obigen Text wird eine Folge von Aktionen be- Bild 9.1 Mainzer Psalter schrieben, die in einer zeitlichen Abfolge durchgeführt werden sollen. Eigentlich scheint der Text eindeutig zu sein. Eine gewisse Unsicherheit bleibt jedoch: Ist das Aufräumen der Schränke und die Sortierung der Fotos auch an den Regen gekoppelt? Was, wenn es nicht regnet? Der folgende Text gibt darüber Auskunft: Wenn es morgen regnet, werde ich den Keller entrümpeln. Dann werde ich die Schränke aufräumen und die Fotos sortieren. Ansonsten werde ich schwimmen gehen. Abends gehe ich mit meiner Frau ins Kino. Im Text haben wir bereits eine bedingte Anweisung bzw. Verzweigung in umgangssprachlicher Form. Kann sich nun seine Frau auf den Kinobesuch freuen oder muss sie dafür auch auf Regen hoffen? Obigen Text können wir auch in einer Python nachempfundenen Form schreiben, um dadurch eine größere Eindeutigkeit zu schaffen. Wenn es morgen regnet, dann werde ich Folgendes tun: den Keller entrümpeln. die Schränke aufräumen die Fotos sortieren. Ansonsten, also wenn es nicht regnet, werde ich Folgendes tun: schwimmen gehen. Abends gehe ich mit meiner Frau ins Kino. 68 9 Verzweigungen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Einen solchen bedingten Arbeitsablauf formuliert man für Algorithmen meistens mit sogenannten Programmablaufplänen bzw. Flussdiagrammen: Die Strukturierung von Programmen mittels Anweisungsblöcken ist ein Charakteristikum von Programmiersprachen. So kann man bereits ein komplettes Programm als einen Block sehen, in den – in der Regel – viele weitere Blöcke verschachtelt sind. Es gibt verschiedene Methoden, solche Blöcke syntaktisch zusammenzufassen. Sprachen wie Algol 60 und Pascal benutzen dazu begin und end. C, C++ und Java fassen ihre Blöcke durch geschweifte Klammern, also „{” und ”}”, zusammen. In manchen Skriptsprachen wie beispielsweise Bash werden Blöcke mittels do ... done, if ... fi oder case ... esac zusammengefasst. Einen Nachteil haben alle diese Methoden: Für den Interpreter oder Compiler einer Sprache mag der Code eindeutig sein, aber Programmierer schreiben den Code auf eine Art, dass er schlecht strukturiert ist. Wir wollen dies an einem „pseudo” C-Code-Schnipsel verdeutlichen: if ( raining ) { keller_entruempeln(); schraenke_aufraeumen(); fotos_sortieren(); } else schwimmen_gehen(); kino_gehen(ehefrau); Stellen Sie sich vor, das Programm stünde wie folgt im Editor: if ( raining ) { keller_entruempeln(); schraenke_aufraeumen(); fotos_sortieren(); } else 9.1 Anweisungsblöcke und Einrückungen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. schwimmen_gehen(); kino_gehen(ehefrau); Nun sieht es so aus, als gehöre der Aufruf „kino_gehen(ehefrau)” auch zum „else”-Fall, d.h. der Kinobesuch findet also nur statt, falls es nicht regnet. Für C ist es aber egal, ob die Zeile eingerückt ist oder nicht. Die Bedeutung ist immer gleich: Der Kinobesuch findet unabhängig vom Regen statt! Anders ausgedrückt: Es ist möglich, C-Code unübersichtlich oder verwirrend zu schreiben. Im obigen Fall suggeriert die Anordnung der Zeilen, also die Einrückungen, ein falsches Verständnis. In Python ist dies komplett anders. Durch Einrückungen wird die Zugehörigkeit zu Blöcken festgelegt. Die erste, also die „richtige” Version unseres C-Programms sieht in PseudoPython-Code wie folgt aus: if raining: keller_entruempeln() schraenke_aufraeumen() fotos_sortieren() else: schwimmen_gehen() kino_gehen(ehefrau) Der Kinobesuch mit Ehefrau ist in obigem Programm unabhängig vom Regen „vorprogrammiert”. Rückt man „kino_gehen(ehefrau)” auf das Niveau von „schwimmen_gehen()” ein, findet der Kinobesuch nur noch statt, wenn es nicht regnet. Die Einrückung von Zeilen und die Benutzung von Leerzeichen am Anfang von Zeilen dienen also bei Python als Strukturierungselement. Programmierer werden dadurch „gezwungen”, übersichtlichen und unmissverständlichen Code zu schreiben, zumindest was die Blockzugehörigkeit betrifft. In unserem Beispiel haben wir gesehen, dass nach dem „if” und nach dem „else” ein Doppelpunkt folgt. Dieser Doppelpunkt nach einem sogenannten Anweisungskopf leitet immer einen eingerückten Block ein. Das heißt, es folgen beliebig viele, aber mindestens eine Anweisung, die alle um die gleiche Anzahl von Leerzeichen eingerückt sind. Apropos Leerzeichen: Man kann auch Tabulatoren statt Leerzeichen verwenden. Man sollte aber auf jeden Fall vermeiden, Tabulatoren und Leerzeichen bei den Einrückungen zu mischen, weil es bei der gleichzeitigen Benutzung verschiedener Editoren, IDE oder Betriebssysteme zu Problemen kommen kann, da die Anzahl der Leerzeichen pro Tabulator unterschiedlich eingestellt sein kann. Allgemein sieht es also so aus: Anweisungskopf: Anweisung Anweisung Anweisung 69 70 9 Verzweigungen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 9.2 Bedingte Anweisungen in Python Im vorigen Abschnitt haben wir bereits gesehen, wie man den rein linearen Programmablauf durch bedingte Anweisungen beeinflussen und kontrollieren kann. Neben den bedingten Anweisungen, also den Verzweigungen, gibt es noch die Schleifen als weitere Kontrollstrukturen. In diesem Kapitel beschäftigen wir uns mit bedingten Anweisungen. Mit ihnen wird sichergestellt, dass manche Codeteile nur unter bestimmten BeBild 9.2 Verzweigungen dingungen ausgeführt werden. Sind die Bedingungen nicht erfüllt, wird dieser Code nicht ausgeführt. Anders ausgedrückt: Eine Verzweigung legt fest, welcher von zwei (oder auch mehr) Programmteilen (Alternativen) in Abhängigkeit von einer (oder mehreren) Bedingungen ausgeführt wird. Bedingte Anweisungen und Verzweigungen werden in Programmiersprachen (ebenso wie die Schleifen) den Kontrollstrukturen zugerechnet, weil mit ihrer Hilfe ein Programm auf verschiedene Zustände reagieren kann, die sich aus Eingaben und Berechnungen ergeben. 9.2.1 Einfachste if-Anweisung Die einfachste Form einer if-Anweisung sieht in Python wie folgt aus: if bedingung: anweisungen Der eingerückte Codeblock wird nur ausgeführt, wenn die Bedingung „bedingung” wahr, also True ist. Im Folgenden zeigen wir ein kleines Beispielprogramm zur Prüfung der Altersbeschränkung im Kino: alter = int(input("Dein Alter? ")) if alter < 12: print("Sorry, der Film ist erst ab 12!") Im Folgenden wird das Programm zweimal gestartet: $ python3 alterscheck.py Dein Alter? 11 Sorry, der Film ist erst ab 12! $ python3 alterscheck.py Dein Alter? 14 9.2 Bedingte Anweisungen in Python 9.2.2 if-Anweisung mit else-Zweig Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir sehen, dass wir keine Ausgabe erhalten, wenn ein potenzieller Besucher alt genug für den Film ist. Dazu können wir unsere if-Anweisung um einen else-Zweig erweitern. Der else-Zweig wird immer dann ausgeführt, wenn das Alter größer oder gleich 12 ist, d.h. wenn die Bedingung nicht erfüllt ist: $ python3 alterscheck.py Dein Alter? 11 Sorry, der Film ist erst ab 12! $ python3 alterscheck.py Dein Alter? 14 Okay, viel Spaß! 9.2.3 elif-Zweige Wir schreiben nun ein Programm für einen Kinderfilm, der keine Altersbeschränkungen hat. Wir gehen aber davon aus, dass Kinder unter 4 ihn nicht verstehen werden und Jugendliche über 16 ihn zu albern finden werden. Aber ab 12 mag der Genuss schon fraglich sein. In diesem Programm benützen wir verschachtelte „if”-Anweisungen: alter = int(input("Dein Alter? ")) if alter < 4: print("Film ist zu kompliziert!") else: if alter < 12: print("Okay, viel Spaß!") else: if alter < 16: print("Bist du sicher, ob das der richtige Film ist?") else: print("Wollen Sie sich das wirklich antun?") So würde allerdings kaum jemand programmieren. Statt der verschachtelten „if”-Anweisungen können wir „elif” benutzen. „elif” stellt wie der Name andeutet eine Abkürzung für „else if” dar: alter = int(input("Dein Alter? ")) if alter < 4: print("Film ist zu kompliziert!") elif alter < 12: print("Okay, viel Spaß!") elif alter < 16: print("Bist du sicher, ob das der richtige Film ist?") else: print("Wollen Sie sich das wirklich antun?") 71 72 9 Verzweigungen 9.3 Vergleichsoperatoren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. In den obigen Beispielen hatten wir bereits Vergleichsoperatoren benutzt. Wir stellen sie hier mit weiteren Vergleichsoperatoren in einer Tabelle zusammen: Operator Erklärung Beispiel Wahrheitswert == Prüfung auf Gleichheit 42 == 42 oder [3, 4, 8] == [3, 4, 8] True != Prüfung auf Ungleichheit 42 != 43 oder [3, 4, 8] != 17 True < Prüfung auf „kleiner” 4 < 12 oder "Tisch" < "Tischbein" True <= Prüfung auf „kleiner gleich” 4 <= 12 oder "Tischbein" <= "Tischbein" True > Prüfung auf „größer” 4 > 12 oder "Tisch" > "Tischbein" False >= Prüfung auf „größer gleich” 40 >= 12 oder "Tischbein" >= "Tischbein" True 9.4 Zusammengesetzte Bedingungen Man kann auch Bedingungen mittels der logischen Operatoren „and”, „or” und „not” zusammenfügen. Beispiel: >>> a = 47 >>> 20 < a and a < 100 True 9.5 Wahr oder falsch Eine Bedingung kann auch aus einer einzigen Variablen bestehen wie im folgenden Beispiel: x = int(input("Zahl: ")) if x: print("Die eingegebene Zahl ist ungleich Null") else: print("Die eingegebene Zahl ist gleich Null") 9.6 Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Eine Variable kann ein beliebiges Objekt referenzieren. Es stellt sich dann die Frage, welchen Wahrheitswert wir diesem Objekt zuordnen, wenn die Variable als Bedingung benutzt wird. Es funktioniert ähnlich wie im Gesetz: „Was nicht verboten ist, ist erlaubt”. Dadurch wird vermieden, dass es Lücken im Gesetz gibt. Ähnlich verhält es sich auch mit den Bedingungen in Python. Alles, was nicht False ist, hat den Wert True. Wir brauchen also nur zu definieren, was „falsch”, also False ist: ■ numerische Null-Werte(0, 0L, 0.0, 0.0+0.0j), ■ der boolesche Wert False, ■ leere Zeichenketten, ■ leere Listen, leere Tupel, ■ leere Dictionaries ■ sowie der spezielle Wert None. Alle anderen Werte betrachtet Python als „wahr”, also True. Beispiel: >>> liste = [] >>> if liste: ... print("Liste enthält Elemente!") ... else: ... print("Liste ist leer!") ... Liste ist leer! 9.6 Aufgaben 1. Aufgabe: Kinder- und Hundeliebhaber stellen sich häufig die Frage, wie alt ihr Hund wohl wäre, wenn er kein Hund, sondern ein Mensch wäre. Landläufig rechnet man Hundejahre in Menschenjahre um, indem man das Alter des Hundes mit 7 multipliziert. Je nach Hundegröße und Rasse sieht die Umrechnung jedoch etwas komplizierter aus, z.B.: ■ Ein einjähriger Hund entspricht in etwa einem 14-jährigen Menschen. ■ 2 Jahre eines Hundes entsprechen 22 Jahre eines Menschen. ■ Ab dann entspricht ein Hundejahr jeweils 5 Menschenjahren. Schreiben Sie ein Programm, das das Alter eines Hundes erfragt und dann nach obiger Methode berechnet, welchem Alter in Menschenjahren das entspricht. Lösung: Lösungen zu Kapitel 9 (Verzweigungen), Seite 480 73 74 9 Verzweigungen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 2. Aufgabe: Ein Kalenderjahr hat bekanntlich 365 oder 366 Tage. Nach dem Gregorianischen Kalenderjahr dauert ein Jahr exakt 365.2425 Tage, also 365 Tage, 5 Stunden, 49 Minuten, 12 Sekunden oder anders ausgedrückt: 31.556.952 Sekunden. Man sieht, dass ein Jahr also grob gesprochen einen Viertel Tag länger ist als 365 Tage. Um diesen Unterschied zu beheben, hat man Schalttage eingefügt. Alle vier Jahre wird mit dem 29. Februar ein Schalttag eingefügt. Allerdings machen wir damit einen neuen kleinen „Fehler”, denn nun haben wir einen Hundertertstel Tag zuviel. Aus diesem Grunde wird alle Hundert Jahre – und zwar, wenn die Jahreszahl durch Hundert teilbar ist – auf einen Schalttag verzichtet. So war beispielsweise das Jahr 1900 kein Schaltjahr, obwohl es durch vier teilbar war. Man benötigt jedoch noch alle 400 Jahre ein weiteres Korrektiv, dann wird ein Schalttag eingefügt, obwohl die Jahreszahl durch Hundert teilbar ist. Nach dieser Regel war das Jahr 2000 ein Schaltjahr. Schreiben Sie nun ein Python-Programm, das berechnet, ob eine gegebene Jahreszahl ein Schaltjahr ist oder nicht. Lösung: Lösungen zu Kapitel 9 (Verzweigungen), Seite 480 3. Aufgabe: Auch bei dieser Aufgabe bleiben wir bei der Zeitrechnung. Zu einer gegebenen Zeit in Stunden, Minuten und Sekunden sollen Sie eine Sekunde hinzuzählen. Aus der Uhrzeit 12:59:59 wird beispielsweise nach der Addition von einer Sekunde die Zeit 13:00:00 Uhr. Lesen Sie Stunden-, Minuten- und Sekundenwerte der Uhrzeit in drei Integer-Werte ein. Das Programm soll die neue Uhrzeit in der obigen Form ausgeben, also hh:mm:ss. Lösung: Lösungen zu Kapitel 9 (Verzweigungen), Seite 481 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 10 10.1 Schleifen Übersicht Schleifen werden benötigt, um einen Codeblock, also eine oder mehrere PythonAnweisungen, wiederholt auszuführen. Einen solchen Codeblock bezeichnet man auch als Schleifenkörper oder Body. In Python gibt es zwei Schleifentypen: die while-Schleife und die for-Schleife. Die meisten Schleifenarten, die in Programmiersprachen Verwendung finden, enthalten einen Zähler oder ganz allgemein Variablen, die im Verlauf der Berechnungen innerhalb des Schleifenkörpers ihre Werte ändern. Außerhalb, das heißt noch vor Beginn der Schleife, werden diese Variablen initialisiert. Vor jedem Schleifendurchlauf wird geprüft, ob ein Ausdruck, in dem diese Variable oder Variablen vorkommen, wahr ist. Dieser Ausdruck bestimmt das Endekriterium der Schleife. Solange die Berechnung dieses Ausdrucks wahr ist, d.h. „True” liefert, wird der Rumpf Bild 10.1 Karussell der Schleife ausgeführt. Nachdem alle Anweisungen des Schleifenkörpers durchgeführt worden sind, springt die Programmsteuerung automatisch zum Anfang der Schleife, also zur Prüfung des Endekriteriums zurück und prüft wieder, ob diese nochmals erfüllt ist. Wenn ja, geht es wie oben beschrieben weiter, ansonsten wird der Schleifenkörper nicht mehr ausgeführt, und es wird mit dem Rest des Skripts fortgefahren. Das unten stehende Diagramm zeigt dies schematisch. 76 10 Schleifen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 10.2 while-Schleife Das folgende Skript, das wir in der interaktiven Shell direkt eintippen können, gibt die Zahlen von 1 bis 4, gefolgt von ihrem Quadrat, unter Benutzung einer while-Schleife aus: >>> i = 1 >>> while i <= 4: ... print(i, i**2) ... i += 1 ... 1 1 2 4 3 9 4 16 Auch die Summe der Zahlen von 1 bis 100 lässt sich mittels einer while-Schleife leicht berechnen, wie wir im folgenden Programm sehen können: n = 100 sum = 0 i = 1 while i <= n: sum = sum + i i = i + 1 print("Summe von 1 bis " + str(n) + ": " + str(sum) ) Zu obigem Programm lässt sich Folgendes sagen. Zur Aufnahme der Summe haben wir eine Variable sum benutzt. sum ist aber auch eine Python-Funktion, die die Summe einer aus numerischen Werten bestehenden Liste oder eines Tupels berechnet: >>> sum([3, 4.5, 9]) 16.5 Damit hätten wir uns natürlich auch das obige Programm zur Berechnung der Summe der Zahlen von 1 bis 100 sparen können, indem wir „sum” und „range” benutzen: >>> n = 100 >>> sum(range(1, n+1)) 5050 10.3 break und continue Einige kennen sicherlich auch die Gaußsche Summenformel, mit der wir die Aufgabenstellung auch ohne sum und range direkt lösen können: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> n = 100 >>> summe = n * (n + 1) / 2 >>> summe 5050.0 10.3 break und continue Für die Schleifen existieren zwei wichtige Anweisungen: „break” zum vorzeitigen Abbruch der Schleife und „continue”, um einen Durchlauf zu beenden und mit dem nächsten Durchlauf bzw. der Überprüfung der Abbruchbedingung weiterzumachen. Die breakAnweisung steht innerhalb des Schleifenrumpfs meist in Verbindung mit einer if-Abfrage. Stößt der Programmablauf auf eine break-Anweisung, wird die Schleife unmittelbar abgebrochen. Das Programm wird mit der ersten Anweisung nach der Schleife fortgesetzt. Bei geschachtelten Schleifen wird mittels break nur die innerste Schleife abgebrochen. Trifft der Programmablauf auf eine continue-Anweisung, wird der Schleifendurchlauf abgebrochen, und der Programmablauf kehrt zum Schleifenkopf zurück, wo geprüft wird, ob die Bedingung für einen weiteren Durchlauf erfüllt ist. Im folgenden Beispiel benutzen wir sowohl break als auch continue. Falls ein doppelter Eintrag in der Liste steht, wird mittels continue auf das nächste Listenelement weitergegangen. Falls die Liste eine negative Zahl oder eine Null enthält, wird die Schleife komplett abgebrochen: liste = eval(input("Liste mit positiven Zahlen eingeben: ")) n = len(liste) i = 0 previous = None erg = [] while i < n: current = liste[i] i += 1 if current == previous: continue if current <= 0: print("Abbruch: Nicht-positive Zahl gefunden!") break erg.append(current) previous = current print(erg) 77 78 10 Schleifen Zum besseren Verständnis des obigen Programms zeigen wir einen Beispiellauf: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. $ python3 break_example.py Liste mit positiven Zahlen eingeben: [37, 99, 123, 17, 17, 17, 89, 32, -3] Abbruch: Nicht-positive Zahl gefunden! [37, 99, 123, 17, 89, 32] 10.4 else-Teil C-Programmierern1 wie auch Programmierern anderer Programmiersprachen kommt es meist sehr merkwürdig vor, wenn sie ein else ohne zugehöriges if finden. Bei Schleifen kann ein else-Teil folgen, das muss aber nicht so sein. Die Anweisungen im else-Teil werden ausgeführt, sobald die Bedingung nicht mehr erfüllt ist. Sicherlich fragen sich einige nun, worin dann der Unterschied zu einer normalen while-Schleife liegt. Hätte man die Anweisungen nicht in den else-Teil gesteckt, sondern einfach hinter die while-Schleife gestellt, wären sie ja auch genauso ausgeführt worden. Der else-Teil einer while-Schleife wird erst zusammen mit dem break-Kommando sinnvoll. Normalerweise wird eine Schleife nur beendet, wenn die Bedingung im Schleifenkopf erfüllt ist. Mit break kann man aber eine Schleife vorzeitig – also gewissermaßen als Notausstieg – verlassen. Im folgenden Beispiel, einem einfachen Zahlenratespiel, kann man erkennen, dass in Kombination mit einem break der else-Zweig durchaus sinnvoll sein kann. Nur wenn die while-Schleife regulär beendet wird, d.h. der Spieler die Zahl erraten hat, gibt es einen Glückwunsch. Gibt der Spieler auf, d.h. break, dann wird der else-Zweig der while-Schleife nicht ausgeführt. import random n = 20 to_be_guessed = random.randint(1, n) 1 Es gibt auch eine else-Schleife in C und C++. Diese wird aber nur selten verwendet. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 10.4 die Alternative im Erfolgsfall: else guess = 0 while guess != to_be_guessed: guess = int(input("Neuer Versuch: ")) if guess > 0: if guess > to_be_guessed: print("Zu gross") elif guess < to_be_guessed: print("Zu klein") else: print("Schade, dass du aufgibst!") break else: print("Gratuliere, das war's") Die Ausgabe einer Spielsitzung könnte beispielsweise so aussehen: $ python3 number_game.py Dein/Ihr Versuch: 10 Zu klein Dein/Ihr Versuch: 15 Zu klein Dein/Ihr Versuch: 18 Gratuliere, das war's $ Bei der while-Schleife von Python handelt es sich um eine sogenannte kopfgesteuerte Schleife. Bevor der Schleifenrumpf ausgeführt wird, muss geprüft werden, ob die Bedingung im Schleifenkopf, also nach dem Schlüsselwort „while”, erfüllt ist. Bei einer kopfgesteuerten Schleife erfolgt also immer zuerst die Abfrage der im Schleifenkopf stehenden Bedingung, bevor der Schleifenrumpf ausgeführt wird. Manchmal würde man lieber zuerst den Schleifenrumpf ausführen und dann eine Abbruchsbedingung prüfen. Dies ist ein Schleifenkonstrukt, welches viele Programmiersprachen bieten und unter dem Namen „fußgesteuerte Schleife” bekannt ist, z.B. in der syntaktischen Form „do SCHLEIFENKÖRPER until ABBRUCHBEDINGUNG” oder „repeat SCHLEIFENKÖRPER until ABBRUCHBEDINGUNG”. Python bietet keine fußgesteuerte Schleife. Man kann diese jedoch mit einem „while True” in Verbindung mit einer „break”-Anweisung am Ende des Schleifenkörpers simulieren. Wir demonstrieren dies im folgenden Beispiel: import random n = 20 to_be_guessed = random.randint(1,n) guess = 0 while True: guess = int(input("Ihr Versuch: ")) if guess > 0: if guess > to_be_guessed: print("Zu gross") 79 80 10 Schleifen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. elif guess < to_be_guessed: print("Zu klein") # repeat until: if guess == to_be_guessed: print("Glückwunsch, das war's") break 10.5 For-Schleife Wie auch die while-Schleife ist die for-Schleife eine Kontrollstruktur, mit der eine Gruppe von Anweisungen (ein Block) wiederholt ausführt werden kann. Die Syntax der forSchleifen unterscheidet sich in den verschiedenen Programmiersprachen. Ebenso ist die Semantik einer for-Schleife, also wie sie vom Compiler oder Interpreter zu verstehen bzw. auszuführen ist, von Programmiersprache zu Programmiersprache unterschiedlich. Die „klassische” numerische Schleife, wie sie C und C++ kennt, besitzt eine Schleifenvariable, die mit einem Startwert initialisiert wird und sich nach jedem Durchlauf des Schleifenkörpers verändert, d.h. meistens um einen bestimmten Wert (z.B. 1) erhöht oder vermindert wird, bis der definierte Zielwert erreicht ist. Man nennt diese Schleifenform auch Zählschleife, weil die Schleifenvariable und damit auch der Startwert, der Endwert und die Schrittweite numerisch sein müssen. Im folgenden Beispiel sehen wir eine for-Schleife in C, die die Zahlen von 1 bis 100 ausdruckt: for( i = 0; i < 100; i++) printf("i: %d\n", i); Auch wenn Sie diese Schleifenform bereits in C oder einer anderen Sprache liebgewonnen haben, müssen wir Sie leider enttäuschen: Python kennt keine solche for-Schleife. Wohlgemerkt „keine solche”, aber sehr wohl eine for-Schleife. Die in Python benutzte Art von for-Schleife entspricht der in der Bash-Shell oder in Perl verwendeten foreach-Schleife. Bei dieser Schleifenart handelt es sich um ein Sprachkonstrukt, mit dessen Hilfe nacheinander beispielsweise die Elemente einer Menge oder Liste bearbeitet werden können. Dazu werden sie einer Variable zugewiesen. Im Folgenden sehen wir die allgemeine Syntax der for-Schleife in Python. Sequenz steht für ein iterierbares Objekt. for Variable in Sequenz: Anweisung_1 Anweisung_2 ... Anweisung_n else: Else-Anweisung_1 Else-Anweisung_2 ... Else-Anweisung_m 10.5 For-Schleife Wie bereits gesagt, dient in Python die for-Schleife zur Iteration über eine Sequenz von Objekten, während sie in vielen anderen Sprachen meist nur „eine etwas andere whileSchleife” ist. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiel einer for-Schleife in Python: >>> languages = ["C", "C++", "Perl", "Python"] >>> for language in languages: ... print(language) ... C C++ Perl Python >>> Wie die while-Schleife besitzt auch die for-Schleife einen optionalen else-Block. Wie bei der while-Schleife wird der else-Block nur ausgeführt, wenn die Schleife nicht durch eine break-Anweisung abgebrochen wurde. Das bedeutet, dass der else-Block nur dann ausgeführt wird, wenn alle Elemente der Sequenz abgearbeitet worden sind. Trifft der Programmablauf auf eine break-Anweisung, so wird die Schleife sofort verlassen und das Programm nach der Anweisung fortgesetzt, die der for-Schleife folgt, falls es überhaupt noch Anweisungen nach der for-Schleife gibt. Üblicherweise befindet sich die break-Anweisung wie im folgenden Beispiel innerhalb einer Konditionalanweisung: essbares = ["Schinken", "Speck", "Spinat", "Nüsse"] for gericht in essbares: if gericht == "Spinat": print("Ich mag keinen Spinat!") break print("Lecker, " + gericht) else: print("Glück gehabt, kein Spinat dabei gewesen!") print("Jetzt bin ich satt!") Wenn wir obiges Beispiel unter kein_spinat.py speichern und aufrufen, erhalten wir folgende Ausgaben: bernd@marvin ~/beispiele/kein_spinat.py Lecker, Schinken Lecker, Speck Ich mag keinen Spinat! Jetzt bin ich satt! $ Wenn wir in der Liste der essbaren Dinge „Spinat” durch „Schokolade” austauschen, erhalten wir folgende Ausgabe: 81 82 10 Schleifen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. bernd@marvin ~/beispiele $ python3 kein_spinat.py Lecker, Schinken Lecker, Speck Lecker, Schokolade Lecker, Nüsse Glück gehabt, kein Spinat dabei gewesen! Jetzt bin ich satt! Vielleicht ist unsere Abscheu vor Spinat aber doch nicht so groß, dass wir sofort aufhören zu essen, wie es in unserem Programm der Fall ist. In diesem Fall kommt die continueAnweisung ins Spiel. Im folgenden kleinen Skript benutzen wir continue, um mit dem nächsten Artikel der essbaren Dinge weiterzumachen. „continue” schützt uns davor, „Spinat” essen zu müssen: essbares = ["Schinken", "Speck", "Spinat", "Nüsse"] for gericht in essbares: if gericht == "Spinat": print("Ich mag keinen Spinat!") continue print("Lecker, " + gericht) else: print("Glück gehabt, kein Spinat dabei gewesen!") print("Jetzt bin ich satt!") Die Ausgabe sieht dann wie folgt aus: bernd@marvin ~/dropbox/python-buch/dritte_auflage_neu/beispiele $ python3 kein_spinat.py Lecker, Schinken Lecker, Speck Ich mag keinen Spinat! Lecker, Nüsse Glück gehabt, kein Spinat dabei gewesen! Jetzt bin ich satt! $ Mit for-Schleifen können wir über beliebige iterierbare Objekte iterieren, wie wir in den folgenden Beispielen zeigen So können wir beispielsweise auch über Dictionaries iterieren, obwohl diese ja keine Reihenfolge aufweisen: cities = {"London": "Berlin": "Madrid": "Rome": for city in cities: print(city) 8615246, 3562166, 3165235, 2874038} 10.6 Aufgaben An der Ausgabe erkennen wir, dass eine Iteration der Iteration über die Keys entspricht: bernd@marvin ~/tmp $ python3 for_examples.py London Rome Madrid Berlin Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Iterieren wir über einen String, so tun wir dies buchstabenweise: for char in "Berlin": print(char) Die Ausgabe sieht wie folgt aus: bernd@marvin ~/tmp $ python3 for_examples.py B e r l i n 10.6 Aufgaben Für die erste Aufgabe benötigen wir die römischen Zahlen. Für diejenigen, die mit römischen Zahlen nicht so ganz sicher sind, geben wir hier die Zuordnungen in der folgenden Tabelle. Tabelle 10.1 Römische Zahlen Römische Zahl Wert (Dezimalzahl) I 1 II 2 III 3 IV 4 V 5 VI 6 VII 7 VIII 8 IX 9 X 10 XI 11 XIV 14 (Fortsetzung nächste Seite) 83 84 10 Schleifen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Tabelle 10.1 Römische Zahlen (Fortsetzung) Römische Zahl Wert (Dezimalzahl) XV 15 XVI 16 ... ... XIX 19 XX 20 XXI 21 ... ... XXIX 29 XXX 30 XL 40 L 50 LX 60 XC 90 C 100 CC 200 CD 400 D 500 CM 900 M 1000 MM 2000 1. Aufgabe: Schreiben Sie ein Python-Programm, das eine beliebige römische Zahl in eine „gewöhnliche” Dezimalzahl umrechnet. Lösung: 40.4 (1. Aufgabe), Seite 482 2. Aufgabe: Bild 10.2 Achtung: Frösche In der nächsten Aufgabe lernen wir einen besonderen Frosch kennen, so wie ihn sich nur Mathematiker ausdenken können. Besonders seine Art, eine Straße zu über- 10.6 Aufgaben queren, macht es zweifelhaft, ob er in der realen Welt lange überleben könnte. Er überquert eine 2,50 Meter breite Straße wie folgt: Mit dem ersten Sprung legt er die erstaunliche Distanz von einem Meter zurück, dann springt er wegen zunehmender Erschöpfung mit jedem weiteren Schritt immer nur noch halb so weit wie vorher. Die Entfernung, die er dabei zurücklegt, berechnet sich also als Summe der Werte 1 + 0, 5 + 0.25 + 0, 125 und so weiter. Dies entspricht natürlich in mathematischer Schreibweise der folgenden Notation: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. n 1 X i =0 2i = 1+ 1 1 1 1 + + + ... 2 4 8 16 Versuchen Sie, mittels eines Python-Programms herauszubekommen, ob der Frosch es auf die andere Straßenseite schafft. Lösung: 40.4 (2. Aufgabe), Seite 483 3. Aufgabe: Der indische Herrscher Shihram tyrannisierte seine Untertanen und stürzte sein Land in Not und Elend. Um die Aufmerksamkeit des Königs auf seine Fehler zu lenken, ohne seinen Zorn zu entfachen, schuf Dahers Sohn, der weise Brahmane Sissa, ein Spiel, in dem der König als wichtigste Figur ohne Hilfe anderer Figuren und Bauern nichts ausrichten kann. Der Unterricht im Schachspiel machte auf Shihram einen starken Eindruck. Er wurde milder und ließ das Schachspiel verbreiten, damit alle davon Kenntnis nähmen. Um sich für die anschauliche Lehre von Lebensweisheit und zugleich Unterhaltung zu bedanken, gewährte er dem Brahmanen einen freien Wunsch. Dieser wünschte sich Weizenkörner: Auf das erste Feld eines Schachbretts wollte er ein Korn, auf das zweite Feld die doppelte Menge, also zwei, auf das dritte wiederum doppelt so viele, also vier und so weiter. Der König lachte und war gleichzeitig erbost über die vermeintliche Bescheidenheit des Brahmanen. Als sich Shihram einige Tage später erkundigte, ob Sissa seine Belohnung in Empfang genommen habe, musste er hören, dass die Rechenmeister die Menge der Weizenkörner noch nicht berechnet hätten. Der Vorsteher der Kornkammer meldete nach mehreren Tagen ununterbrochener Arbeit, dass er diese Menge Getreidekörner im ganzen Reich nicht aufbringen könne. Auf allen Feldern eines Schachbretts zusammen wären es 18.446.744.073.709.551.615 Weizenkörner. Nun stellte er sich die Frage, wie das Versprechen eingelöst werden könne. Der Rechenmeister half dem Herrscher aus der Verlegenheit, indem er ihm empfahl, er solle Sissa ibn Dahir ganz einfach das Getreide Korn für Korn zählen lassen.2 Berechnen Sie mithilfe eines Python-Programms, ohne eine Formel zu Hilfe zu nehmen, wie viele Weizenkörner angehäuft werden müssen. Lösung: 40.4 (3. Aufgabe), Seite 484 2 Die Weizenkornlegende wurde wörtlich von Wikipedia entnommen. 85 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 11 11.1 Dateien lesen und schreiben Dateien Man findet wohl kaum jemanden im 21. Jahrhundert, der nicht wüsste, was eine Datei ist. Auch wenn nicht jeder eine exakte Definition geben könnte, also beispielsweise: Eine Datei ist eine Menge von logisch zusammenhängenden und meist sequentiell geordneten Daten, die auf einem Speichermedium dauerhaft gespeichert werden und mittels eines Bezeichners bzw. Namens wieder identifizierbar und damit ansprechbar sind. Die Daten einer Datei existieren also über die Laufzeit eines Programms hinaus. Deswegen werden sie auch als nicht-flüchtig oder persistent bezeichnet. Auch wenn es so scheinen mag: Das Wort Datei ist kein altes deutsches Wort. Es ist eine künstliche Schöpfung des Deutschen Instituts für Nor- Bild 11.1 Dateien mung (DIN). Die beiden Wörter Daten und Kartei wurden zu dem neuen Wort Datei verschmolzen. Ein solches Kunstwort wird übrigens als Portmanteau oder Kofferwort bezeichnet. Der Inhalt jeder Datei besteht grundsätzlich aus einer eindimensionalen Aneinanderreihung von Bits, die normalerweise in Byte-Blöcken zusammengefasst interpretiert werden. Die Bytes erhalten erst durch Anwendungsprogramme und das Betriebssystem eine Bedeutung. Sie entscheiden also, ob es sich um eine Textdatei, ein ausführbares Programm oder beispielsweise ein Musikstück oder ein Bild handelt. 11.2 Text aus einer Datei lesen Unser erstes Beispiel zeigt, wie man Daten aus einer Datei ausliest. Um dies tun zu können, muss man zuerst die Datei zum Lesen öffnen. Dazu benötigt man die open()-Funktion. Mit 88 11 Dateien lesen und schreiben der open-Funktion erzeugt man ein Dateiobjekt und liefert eine Referenz auf dieses Objekt als Ergebniswert zurück. Die open-Funktion erwartet zwei Parameter: einen Dateinamen, gegebenenfalls mit einem Pfad, und optional kann man den Modus angeben: open(filename,mode) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im folgenden Beispiel öffnen wir die Datei „ad_lesbiam.txt” zum Lesen, da der Modus auf „r” (read) gesetzt ist: fobj = open("ad_lesbiam.txt", "r") Alternativ kann man die obige Anweisung auch ohne die Angabe des „r” schreiben. Fehlt der Modus, wird automatisch Lesen zum Default-Wert: fobj = open("ad_lesbiam.txt") Sehr häufig bearbeitet man eine Datei zeilenweise, d.h. man liest eine Datei Zeile für Zeile. Das folgende Programmstück demonstriert, wie man eine Datei zeilenweise einliest. Jede Zeile wird dann mit print ausgegeben. Die String-Methode rstrip() entfernt vor der Ausgabe etwaige Leerzeichen und Newlines vom rechten Rand: fobj = open("ad_lesbiam.txt") for line in fobj: print(line.rstrip()) fobj.close() Führt man obiges Programm aus, erhält man folgende Ausgabe1 : VIVAMUS mea Lesbia, atque amemus, rumoresque senum severiorum omnes unius aestimemus assis! soles occidere et redire possunt: nobis cum semel occidit brevis lux, nox est perpetua una dormienda. da mi basia mille, deinde centum, dein mille altera, dein secunda centum, deinde usque altera mille, deinde centum. dein, cum milia multa fecerimus, conturbabimus illa, ne sciamus, aut ne quis malus invidere possit, cum tantum sciat esse basiorum. 1 Es handelt sich um ein Gedicht des Dichters Catullus. Deutsche Übersetzung: „Lass uns leben, meine liebe Lesbia, und lieben und lass uns alle Gerüchte der allzu strengen Alten nicht höher als einen Cent achten! Sonnen können untergehen und wieder aufgehen: wenn uns einmal das kurze Licht ausgeht, müssen wir eine ewige Nacht schlafen. Gib mir tausend Küsse, darauf hundert, darauf ein anderes Tausend und ein zweites Hundert, als nächstes ein weiteres Tausend und dann hundert. Dann, wenn wir viele Tausende gemacht haben, werden wir jene verwirren, damit wir es nicht wissen, noch irgendein Schlechter auf uns neidisch sein kann, wenn er weiß, wie viele Küsse es gewesen sind.” 11.3 Schreiben in eine Datei 11.3 Schreiben in eine Datei Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Auch das Schreiben in eine Datei lässt sich in Python einfach bewerkstelligen. Zum Öffnen der Datei benutzt man „w” statt „r” als Modus. Daten schreibt man in eine Datei mit der Methode write des Dateiobjekts. Im folgenden Programm versehen wir die vorige Datei mit Zeilennummern, d.h. wir schreiben eine neue Datei, die alle Zeilen der alten Datei plus eine Zeilennummer vor jeder Zeile enthält. fobj_in = open("ad_lesbiam.txt") fobj_out = open("ad_lesbiam2.txt", "w") counter = 0 for line in fobj_in: counter += 1 out_line = "{0:>3s} {1:s}\n".format(str(counter),line.rstrip()) fobj_out.write(out_line) fobj_in.close() fobj_out.close() Die Datei „ad_lesbiam2.txt” enthält nach Ausführung des obigen Programms folgenden Text: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 VIVAMUS mea Lesbia, atque amemus, rumoresque senum severiorum omnes unius aestimemus assis! soles occidere et redire possunt: nobis cum semel occidit brevis lux, nox est perpetua una dormienda. da mi basia mille, deinde centum, dein mille altera, dein secunda centum, deinde usque altera mille, deinde centum. dein, cum milia multa fecerimus, conturbabimus illa, ne sciamus, aut ne quis malus invidere possit, cum tantum sciat esse basiorum. 11.4 Die Methoden read() und readlines() Bis jetzt haben wir Dateien Zeile für Zeile mit Schleifen verarbeitet. Aber es kommt öfters vor, dass man eine Datei gerne in eine komplette Datenstruktur einlesen will, z.B. einen String oder eine Liste. Auf diese Art kann die Datei schnell wieder geschlossen werden, und man arbeitet anschließend nur noch auf der Datenstruktur weiter: 89 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 90 11 Dateien lesen und schreiben >>> poem = open("ad_lesbiam.txt").readlines() >>> print(poem) ['VIVAMUS mea Lesbia, atque amemus,\n', 'rumoresque senum severiorum\n ', 'omnes unius aestimemus assis!\n', 'soles occidere et redire possunt:\n', 'nobis cum semel occidit brevis lux,\n', 'nox est perpetua una dormienda.\n', 'da mi basia mille, deinde centum,\n', 'dein mille altera, dein secunda centum,\n', 'deinde usque altera mille, deinde centum.\n', 'dein, cum milia multa fecerimus,\n', ' conturbabimus illa, ne sciamus,\n', 'aut ne quis malus invidere possit,\n', 'cum tantum sciat esse basiorum. \n', '\n'] >>> print(poem[2]) omnes unius aestimemus assis! Im obigen Beispiel wurde das ganze Gedicht in eine Liste namens poem geladen. Wir können nun beispielsweise die dritte Zeile mit poem[2] ansprechen. Eine andere angenehme Methode, eine Datei einzulesen, bietet die Methode read() von open. Mit dieser Methode kann man eine ganze Datei in einen String einlesen, wie wir im folgenden Beispiel zeigen: >>> poem = open("ad_lesbiam.txt").read() >>> print(poem[20:34]) atque amemus, 11.5 with-Anweisung Wie wir in den vorigen Abschnitten Dateien gelesen und geschrieben haben, sollte man es eigentlich nicht tun, obwohl es so funktioniert. Man sollte unbedingt die with-Anweisung benutzen. with ist ein allgemeines Konstrukt, das auch in anderen Zusammenhängen in Python benutzt wird und welches man auch für eigene Zwecke definieren kann. Benutzt man es mit einem open, so wird die Datei automatisch nach Ablauf des Blocks oder bei Auftreten einer Ausnahme geschlossen. Damit ist sichergestellt, dass die Datei immer geschlossen wird! Ein explizites close ist also nicht mehr nötig! Unsere Beispiele aus den vorigen Abschnitten haben wir im Folgenden mit with neu geschrieben: with open("ad_lesbiam.txt") as fobj: for line in fobj: print(line.rstrip()) 11.6 Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im folgenden Beispiel öffnen wir mit with eine Datei zum Lesen und eine zum Schreiben: with open("ad_lesbiam.txt") as fobj_in: with open("ad_lesbiam2.txt", "w") as fobj_out: counter = 0 for line in fobj_in: counter += 1 out = "{0:>3s} {1:s}\n".format(str(counter),line) fobj_out.write(out.rstrip()) Im vorigen Beispiel ist es unschön, dass wir nach jedem with einrücken müssen. Dadurch kommen wir auf acht Leerzeichen Einrückungstiefe. Wir können auch mit einem with gleichzeitig mehrere Dateien öffnen. Diese werden dann mit Kommata getrennt. Allerdings müssen dann alle in einer Zeile stehen. Allerdings kann man auch den Rückwärtsschrägstrich benutzen, um die Zeile in einer weiteren Zeile fortzusetzen. Man muss allerdings darauf achten, dass der Rückwärtsschrägstrich das letzte Zeichen der Zeile ist: with open("ad_lesbiam.txt") as fobj_in,\ open("ad_lesbiam2.txt", "w") as fobj_out: counter = 0 for line in fobj_in: counter += 1 out_line = "{0:>3s} {1:s}\n".format(str(counter),line.rstrip() ) fobj_out.write(out_line) 11.6 Aufgaben 1. Aufgabe: Gegeben sei eine Datei, in der jeweils abwechselnd ein Vorname und ein Nachname in einer Zeile steht, also beispielsweise so: Fred Miller Eve Turner Steve Baker Diese Datei soll in eine andere Datei überführt werden, in der pro Zeile jeweils ein Vorname und ein Nachname steht. Lösung: 40.5 (1. Aufgabe), Seite 485 91 92 11 Dateien lesen und schreiben 2. Aufgabe: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Schreiben Sie ein Programm, das jeweils drei Zeilen aus der folgenden Datei zu jeweils einer zusammenfasst und diese als Standardausgabe mittels print() ausgibt. D.h. eine Ausgabezeile soll jeweils den Vornamen, den Nachnamen und den Künstlernamen bzw. die Band des Künstlers enthalten. Der Band- bzw. Künstlername soll in Klammern ausgegeben werden. Also z.B. „Marshall Bruce Mathers III (Eminem)”: Brian Molko Placebo Jim Morrison The Doors Ray Davies The Kinks Marshall Bruce Mathers III Eminem Andre Romelle Young Dr. Dre Beth Gibbons Portishead Lösung: 40.5 (2. Aufgabe), Seite 486 3. Aufgabe: Ändern Sie das vorige Programm so, dass es nun die Ausgabe in eine Ausgabedatei schreibt. Lösung: 40.5 (3. Aufgabe), Seite 486 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 12 12.1 Formatierte Ausgabe und Strings formatieren Wege zur Formatierung der Ausgabe Wenn man programmieren lernt, steht im Anfang zunächst einmal die Programmierlogik im Vordergrund. Man möchte schnell lernen, wie Variablen, Datenstrukturen, Verzweigungen, Schleifen usw. funktionieren. Die „schöne” Ausgabe der Ergebnisse ist erst einmal sekundär. Man freut sich, wenn das Programm überhaupt etwas ausgibt, und vor allen Dingen, wenn es dann auch noch richtig ist. Ob die Ergebnisse ansprechend formatiert sind, ist in dieser Phase von untergeordneter Bedeutung. Aber irgendwann rückt dann auch der Wunsch nach einer hübschen Ausgabe der Ergebnisse in den Fokus der Lernenden. „Irgendwann” könnte jetzt wohl bei allen erreicht sein. Deshalb werden wir uns nun intensiv den verschiedenen Methoden zuwenden, mit denen man formatierte Strings und formatierte AusgaBild 12.1 Buchdruck 1568 ben erzeugen kann. Python bietet, was eigentlich Python-untypisch ist, verschiedene Wege zur Formatierung, die wir im Folgenden alle vorstellen. Wir empfehlen aber, die formatMethode der Stringklasse zu benutzen, die wir kurz vor Ende dieses Kapitels besprechen. Die format-Methode ist die bei Weitem flexibelste. Bevor wir auf die Stringformatierung eingehen, wollen wir uns die print-Funktion im Detail anschauen. 12.2 print-Funktion Das Aufrufverhalten der print-Funktion ist wie folgt definiert: print(value1, ..., sep=' ', end='\n', file=sys.stdout, flush=False) 94 12 Formatierte Ausgabe und Strings formatieren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die print-Funktion druckt beliebig viele Werte (value1, value2, ...) aus, die durch Komma getrennt sind. Bei der Ausgabe werden die Werte standardmäßig durch Leerzeichen getrennt. Im folgenden Beispiel sehen wir zwei print-Aufrufe, die jeweils zwei Werte, d.h. einen String und eine Float-Zahl, ausgeben: >>> print("a = ", a) a = 3.564 >>> print("a = \n", a) a = 3.564 Wir sehen im zweiten print des vorigen Beispiels, dass das Leerzeichen zwischen zwei Werten, also in unserem Fall die Werte „a = \n” und „3.564”, immer durch ein Leerzeichen getrennt werden, auch wenn die Ausgabe in einer neuen Zeile weitergeht. Mit dem Schlüsselwortparameter sep kann man den Separator, der zwischen den Werten ausgegeben wird, auf einen beliebigen Stringwert setzen, also auch zum Beispiel auf einen leeren String oder einen Smiley: >>> print("a", a b >>> print("a", ab >>> print(192, 192.168.178.42 >>> print("a", a:-)b "b") "b", sep="") 168, 178, 42, sep=".") "b", sep=":-)") Nach der Ausgabe der Werte beendet die print-Funktion die Ausgabe mit einem Newline, wie wir im Folgenden sehen: >>> for i in range(3): ... print(i) ... 0 1 2 Man kann dem Schlüsselwortparameter end einen beliebigen String zuweisen, der dann statt dem Default-Wert \n verwendet wird. Das erlaubt uns z.B., mehrere Werte in einer Zeile auszugeben: >>> for ... ... 0 1 2 3 >>> >>> for ... ... 0 :-) 1 i in range(4): print(i, end=" ") i in range(4): print(i, end=" :-) ") :-) 2 :-) 3 :-) 12.3 Stringformatierung im C-Stil Die Ausgabe der print-Funktion wird in einen Datenstrom1 geleitet. Standardmäßig wird dazu die Standardausgabe benutzt, also sys.stdout. Mit dem Schlüsselwort file sind wir schließlich in der Lage, die Ausgabe in eine Datei umzuleiten. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> fh = open("daten.txt","w") >>> print("42 ist die Antwort, aber was ist die Frage?", file=fh) >>> fh.close() Wir können feststellen, dass print nun keine Ausgabe mehr in der interaktiven Shell erzeugt. Die Ausgabe erfolgt jetzt in die Datei „daten.txt”. Auf diese Art ist es nicht nur möglich, Ausgaben in eine beliebige Datei umzulenken, sondern auch in den Standardfehlerkanal: >>> import sys >>> # Ausgabe in Standardfehlerkanal: ... >>> print("Error: 42", file=sys.stderr) Error: 42 12.3 Stringformatierung im C-Stil Wir zeigen im Folgenden eine der Programmiersprache C und den C-Funktionen printf und sprintf angelehnte Vorgehensweise, um formatierte Strings zu erzeugen.2 Gibt es ein printf in Python? Das ist die brennende Frage für Python-Anfänger, die von C, Perl, Bash oder anderen Programmiersprachen kommen, die dieses Statement bzw. diese Funktion benutzen. Zu sagen, dass Python eine print-Funktion und keine printf-Funktion hat, ist nur die eine Seite der Medaille. Also gibt es doch eine „printf”-Funktion in Python? Nein, aber die Funktionalität des „alten” printf wurde in anderer Form übernommen. Zu diesem Zweck wird der Modulo-Operator „%” von der String-Klasse überladen. Deshalb spricht man bei dem überladenen Operator „%” der String-Klasse auch häufig vom StringModulo-Operator, obwohl es eigentlich wenig mit der eigentlichen Modulo-Arithmetik zu tun hat. Manchmal wird er auch synonym als String-Modulus-Operator bezeichnet. Ein weiterer Begriff für diese Operation lautet „String-Interpolation”, weil bei dieser Operation Werte wie Integers, Floats usw. in den zu erzeugenden formatierten String eingefügt, also interpoliert werden. Den anhand der String-Interpolation erzeugten formatierten String kann man dann z.B. mittels print (also nicht printf ) ausgeben. Aber man kann diesen formatierten String auch auf andere Art im Programm weiter verwenden. Das folgende Diagramm verbildlicht die Arbeitsweise des String-Modulo-Operators: Auf der linken Seite des String-Modulo-Operators befindet sich der sogenannte FormatString, und auf der rechten Seite steht ein Tupel mit den Werten, die in den Format-String interpoliert bzw. eingefügt werden sollen. Die Werte können Literale, Variablen oder beliebige arithmetische Ausdrücke sein. 1 2 engl. stream Dieses Vorgehen gilt als veraltet und als schlechter Python-Stil. 95 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 96 12 Formatierte Ausgabe und Strings formatieren Der Formatstring enthält Platzhalter. In unserem Beispiel befinden sich zwei davon: „%5d” und „%8.2f”. Die allgemeine Syntax für diese Platzhalter lautet: %[flags][width][.precision]type Schauen wir uns einmal die Platzhalter unseres Beispiels genauer an: Der zweite, also „%8.2f”, ist eine Formatbeschreibung für eine Float-Zahl. Wie auch alle anderen Platzhalter wird er durch ein „%”-Zeichen eingeleitet. Dieses wird von einer Zahl gefolgt, die die totale Anzahl der Zeichen angibt, mit der die Zahl ausgegeben werden soll. Diese Anzahl beinhaltet auch den Dezimalpunkt und alle Ziffern vor und nach dem Punkt. Unsere Float-Zahl 59.058 muss also mit 8 Zeichen dargestellt werden. Der Dezimalanteil der Zahl ist auf 2 gesetzt, dies ist die Zahl hinter dem „.” beim Platzhalter. Das bedeutet, dass die Anzahl der Nachkommastellen auf 2 gesetzt ist. Das letzte Zeichen im Platzhalter ist der Buchstabe „f”. Wie sich leicht vermuten lässt, steht er für „float”. Bild 12.2 float-Platzhalter Schaut man sich die Ausgabe an, erkennt man, dass die Zahl „59.058” als „ 59.06” ausgegeben wird. Man sieht auch, dass die Ausgabe nicht etwa nach zwei Stellen abgebrochen wird, sondern dass eine Rundung erfolgt. Außerdem werden bei der Ausgabe von links drei Leerzeichen an die Zahl angefügt. Der erste Platzhalter „%5d” dient als Beschreibung für die erste Komponente unseres Tupels, d.h. die Ganzzahl (Integer) 453. Diese Zahl soll mit 5 Zeichen ausgedruckt werden. Da 453 aber nur aus 3 Stellen besteht, werden der Zahl in der Ausgabe zwei Leerzeichen vorangestellt. Viele werden statt „%5d” einen Platzhalter der Art „%5i” erwartet haben, da es sich ja um eine Integer-Zahl handelt. Worin besteht der Unterschied? Ganz einfach: Es gibt keinen Unterschied zwischen „d” und „i”. Beide werden zur Formatierung von Integers genutzt. 12.3 Stringformatierung im C-Stil Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Vorteile bzw. die Schönheit der formatierten Ausgabe kann man nur ermessen, wenn man mehrere gleichermaßen formatierte Ausgaben über mehrere Zeilen ausgibt. Im folgenden Beispiel zeigen wir, wie es aussieht, wenn wir fünf Fließkommazahlen (floats) gleich formatiert mit dem Platzhalter „%6.2f” in verschiedenen Zeilen ausgeben: Platzhaltersymbol Bedeutung d vorzeichenbehaftete Ganzzahl (Integer, dezimal) i vorzeichenbehaftete Ganzzahl (Integer, dezimal) o vorzeichenlose Ganzzahlen im Oktalformat u obsolete (veraltet), ansonsten wie d, d.h. eine vorzeichenbehaftete Ganzzahl x vorzeichenlose Ganzzahlen (hexadezimal) X vorzeichenlose Ganzzahlen (hexadezimal), Uppercase e Fließkommazahl in wissenschaftlicher Notation, d.h. im Exponentialformat. E wie e, aber Großbuchstaben für Exponenten „e”). f Fixed point. Displays the number as a fixed-point number. F wie f, aber nan wird als NAN dargestellt g g entspricht entweder e oder f. Dies wird automatisch in Abhängigkeit zur Größe des Wertes und der gegebenen Präzision entschieden. G G ist analog zu g, aber es entspricht entweder E oder f. c ein Zeichen s Eine Zeichenkette (String); beliebige Python-Objekte werden in einen String mittels der Methode str() gewandelt. % Es findet keine Argument-Konvertierung statt, es wird ein „%”-Zeichen ausgegeben. Die folgenden Beispiele zeigen einige exemplarische Anwendungen der Konvertierungsmöglichkeiten der vorigen Tabelle: >>> "%10.3e"% (356.08977) ' 3.561e+02' >>> "%10.3E"% (356.08977) ' 3.561E+02' >>> "%10.3f"% (356.08977) ' 356.090' 97 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 98 12 Formatierte Ausgabe und Strings formatieren >>> x = float('nan') >>> "%5.2F" % (x,) ' NAN' >>> "%5.2f" % (x,) ' nan' >>> >>> print("%10o"% (25)) 31 >>> print("%10.3o"% (25)) 031 >>> print("%10.5o"% (25)) 00031 >>> "%c" % (97,) 'a' >>> "%c" % (98,) 'b' >>> print("%5x"% (47)) 2f >>> print("%5.4x"% (47)) 002f >>> print("%5.4X"% (47)) 002F >>> print("Ein Prozentzeichen: %% " % ()) Ein Prozentzeichen: % >>> Flag Bedeutung # Wird dieses Zeichen mit o, x oder X benutzt, wird der jeweilige Wert mit dem entsprechenden folgenden Präfix 0, 0o, 0O, 0x oder 0X versehen. 0 Das Ergebnis der Umwandlung wird mit Nullen aufgefüllt. - Das Ergebnis der Umwandlung wird linksbündig ausgegeben. Falls kein Vorzeichen (beispielsweise ein Minuszeichen) ausgegeben wird, wird ein Leerzeichen vor den Wert gesetzt. + Das Ergebnis der Umwandlung wird mit einem Vorzeichen versehen („+” oder „-”). Dieses Flag überschreibt ein „space”-Flag. Beispiele: >>> print("%#5X"% (47)) 0X2F >>> print("%5X"% (47)) 2F >>> print("%#5.4X"% (47)) 0X002F >>> print("%#5o"% (25)) 0o31 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 12.4 Der pythonische Weg: Die String-Methode „format” >>> +42 >>> 42 >>> +42 >>> 42 >>> 42 print("%+d"% (42)) print("% d"% (42)) print("%+2d"% (42)) print("% 2d"% (42)) print("%2d"% (42)) Obwohl es so ausschauen mag, ist die Formatierung nicht Teil der Print-Funktion. Schaut man sich die vorigen Beispiele genauer an, sieht man, dass wir einen formatierten String an die Print-Funktion übergeben haben. Oder anders ausgedrückt: Wenn die StringInterpolation auf einen String angewendet wird, liefert sie einen String zurück. Dieser String wird dann an print übergeben. Dies bedeutet wiederum, dass wir die String-Interpolation auch hätten „zweistufig” anwenden können, d.h. wir hätten erst einen formatierten String erzeugt, diesen einer Variablen zugewiesen und diese Variable dann an print übergeben: >>> s = "Preis: $ %8.2f"% (356.08977) >>> print(s) Preis: $ 356.09 >>> 12.4 Der pythonische Weg: Die String-Methode „format” Die String-Methode „format” wurde mit Python 2.6 als Ersatz für die String-Interpolation eingeführt. Ganz allgemein sieht die Syntax von „format” wie folgt aus: template.format(p0, p1, ..., k0=v0, k1=v1, ...) Der Format-String ist ein String, der ein oder mehrere Format-Codes innerhalb eines konstanten Texts enthält, d.h. Felder, die ersetzt werden sollen. Die zu „ersetzenden Felder” sind von geschweiften Klammern umgeben, also „{” und „}”. Eine geschweifte Klammer und der von ihr umschlossene „Code” wird mit dem formatierten Wert aus dem korrespondierenden Argument ersetzt. Nach welchen Regeln dies erfolgt, werden wir im Folgenden erläutern. Alles andere, was nicht von geschweiften Klammern umschlossen ist, wird wörtlich, also ohne Änderungen, ausgedruckt. Möchte man eine öffnende oder eine schließende geschweifte Klammer ausgeben, muss man diese verdoppeln, also „{{” oder „}}”. Es gibt zwei Arten von Argumenten für die format-Methode. Die Argumentenliste startet mit 0 oder mehr Positionsargumenten (p0, p1, ...), die von 0 oder mehr Schlüsselwortargumenten der Form name=value gefolgt werden können. 99 100 12 Formatierte Ausgabe und Strings formatieren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Ein Positionsparameter der format-Methode kann benutzt werden, indem man den Index des Parameters nach der öffnenden geschweiften Klammer angibt, d.h. {0} für den ersten Parameter, {1} für den zweiten und so weiter. Dem Index des Positionsparameters nach der öffnenden geschweiften Klammer kann ein Doppelpunkt und einem Formatstring folgen, der dem des String-Interpolationsparameters ähnelt, den wir zu Beginn des Kapitels besprochen hatten, also zum Beispiel {0:5d}. Falls die Positionsparameter in der Reihenfolge benutzt werden, in der sie in der Parameterliste stehen, kann die Angabe des Indexes innerhalb der geschweiften Klammern entfallen, d.h. ’{} {} {}’ entspricht ’{0} {1} {2}’. Um es nochmals klarzustellen: Will man sie in einer anderen Reihenfolge benutzen, sind die Angaben der Indexpositionen dringend notwendig, wie z.B. hier ’{2} {1} {0}’. Im folgenden Diagramm zeigen wir anhand eines Beispiels, wie die Stringmethode „format” für zwei Parameter funktioniert: Beispiele mit Positionsparametern: >>> "Erstes Argument: {0}, zweites: {1}".format(47,11) 'Erstes Argument: 47, zweites: 11' >>> "Zweites Argument: {1}, erstes: {0}".format(47,11) 'Zweites Argument: 11, erstes: 47' >>> "Zweites Argument: {1:3d}, erstes: {0:7.2f}".format(47.42,11) 'Zweites Argument: 11, erstes: 47.42' >>> "Erstes Argument: {}, zweites: {}".format(47,11) 'Erstes Argument: 47, zweites: 11' >>> # Argumente können auch mehrmals verwendet werden: ... >>> "precisions: {0:6.2f} or {0:6.3f}".format(1.4148) 'precions: 1.41 or 1.415' Im folgenden Beispiel demonstrieren wir, wie Schlüsselwortparameter mit der formatMethode benutzt werden können: >>> "Artikel: {a:5d}, Preis: {p:8.2f}".format(a=453, p=59.058) 'Artikel: 453, Preis: 59.06' Mit der format-Methode ist es auch möglich, Daten links- und rechtsbündig auszugeben. Dazu müssen wir der Formatierungsanweisung ein „<” (linksbündig) oder ein „>” (rechtsbündig) voranstellen. Wir demonstrieren dies an den folgenden Beispielausgaben: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 12.4 Der pythonische Weg: Die String-Methode „format” >>> "{0:<20s} {1:6.2f}".format('Spam & Eggs:', 6.99) 'Spam & Eggs: 6.99' >>> "{0:>20s} {1:6.2f}".format('Spam & Eggs:', 6.99) ' Spam & Eggs: 6.99' >>> "{0:>20s} {1:6.2f}".format('Spam & Ham:', 7.99) ' Spam & Ham: 7.99' >>> "{0:<20s} {1:6.2f}".format('Spam & Ham:', 7.99) 'Spam & Ham: 7.99' >>> "{0:<20} {1:6.2f}".format('Spam & Ham:', 7.99) 'Spam & Ham: 7.99' >>> "{0:>20} {1:6.2f}".format('Spam & Ham:', 7.99) ' Spam & Ham: 7.99' >>> Ausrichtungsoption Bedeutung ’<’ Das Feld wird linksbündig innerhalb des vorhandenen Raums ausgegeben. Strings werden standardmäßig linksbündig ausgegeben. ’>’ Das Feld wird rechtsbündig innerhalb des vorhandenen Raums ausgegeben. Numerische Werte werden standardmäßig rechtsbündig ausgegeben. ’=’ Die Auffüllzeichen (padding characters) werden zwischen Vorzeichen, falls ein Vorzeichen ausgegeben wird, und dem eigentlichen Beginn der Ziffern einer Zahl eingeführt. Damit können Felder der Art „+000000120” ausgegeben werden. Diese Ausrichtungsoption kann nur auf numerische Werte angewendet werden. ’^’ Ein Feld wird zentriert innerhalb des zur Verfügung stehenden Raums ausgegeben. Wenn keine minimale Feldlänge angegeben wird, entspricht die Feldlänge immer der Länge der Daten, die ein Feld enthält. In diesen Fällen haben die Ausrichtungsoptionen keine Bedeutung. Zusätzlich können wir noch die Ausgabe mittels der sign-Option beeinflussen. Diese Optionen sind nur für numerische Werte gültig: 101 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 102 12 Formatierte Ausgabe und Strings formatieren sign-Option Bedeutung ’+’ Es soll immer ein Vorzeichen ausgegeben werden, also unabhängig davon, ob es sich um eine positive oder negative Zahl handelt. ’-’ Ein Vorzeichen soll nur bei negativen Zahlen verwendet werden. space Statt eines „+”-Zeichens wird bei positiven Zahlen ein Leerzeichen „ ” vorangestellt. Bei negativen Zahlen ein Minus-Zeichen „-”. 12.5 Benutzung von Dictionaries beim Aufruf der „format”-Methode In den vorigen Kapiteln haben wir gesehen, dass wir zwei Möglichkeiten haben, Werte mit „format” zu formatieren: ■ Benutzung des Index bzw. der Position des Arguments: >>> print("Die Hauptstadt von {0:s} ist {1:s}".format("Ontario"," Toronto")) Die Hauptstadt von Ontario ist Toronto Um es nochmals zu erwähnen: Im vorigen Beispiel hätten wir auch geschweifte Klammern mit leerem Inhalt benutzen können! ■ Benutzung von Schlüsselwortparametern: >>> print("Die Hauptstadt von {province} ist {capital}".format( province="Ontario",capital="Toronto")) Die Hauptstadt von Ontario ist Toronto Im zweiten Fall könnten wir auch ein Dictionary verwenden, wie wir im folgenden Code sehen können: >>> print("Die Hauptstadt von {province} ist {capital}".format(**k)) Die Hauptstadt von Ontario ist Toronto Schauen wir uns das folgende Python-Programm an: capital_country = {"Vereinigte Staaten" : "Washington", "US" : "Washington", "Kanada" : "Ottawa", "Deutschland": "Berlin", "Frankreich" : "Paris", "Großbritannien" : "London", "Schweiz" : "Bern", "Österreich" : "Wien", "Niederlande" : "Amsterdam"} print("Länder und ihre Hauptstädte:") for c in capital_country: print("{country}: {capital}".format(country=c, capital= capital_country[c])) 12.6 Benutzung von lokalen Variablen in „format” Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wenn wir dieses Programm starten, erhalten wir die folgende Ausgabe: $ python3 country_capitals.py Länder und ihre Hauptstädte: Schweiz: Bern Großbritannien: London US: Washington Frankreich: Paris Kanada: Ottawa Niederlande: Amsterdam Deutschland: Berlin Österreich: Wien Vereinigte Staaten: Washington Das vorige Programm können wir so umschreiben, dass im Aufruf der format-Methode das Dictionary direkt verwendet wird. Dazu verwenden wir jedoch einen Mechanismus (**), den wir erst in Kapitel Doppeltes Sternchen im Funktionsaufruf, Seite 129 behandeln werden: capital_country = {"Vereinigte Staaten" : "Washington", "US" : "Washington", "Kanada" : "Ottawa", "Deutschland": "Berlin", "Frankreich" : "Paris", "Großbritannien" : "London", "Schweiz" : "Bern", "Österreich" : "Wien", "Niederlande" : "Amsterdam"} print("Countries and their capitals:") for c in capital_country: format_string = c + ": {" + c + "}" print(format_string.format(**capital_country)) Der Ausdruck ist gleich wie beim ersten Programm. 12.6 Benutzung von lokalen Variablen in „format” „locals” ist eine Funktion, die ein Dictionary mit den lokal definierten Namen und ihren aktuellen Werten zurückliefert. Die Variablen sind die Schlüssel dieses Dictionarys, und die Werte zu den Schlüsseln entsprechen den Werten der Variablen: >>> a = 42 >>> b = 47 >>> def f(): return 42 103 104 12 Formatierte Ausgabe und Strings formatieren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ... >>> locals() {'a': 42, 'b': 47, 'f': <function f at 0xb718ca6c>, '__builtins__': < module 'builtins' (built-in)>, '__package__': None, '__name__': ' __main__', '__doc__': None} Das Dictionary, was von locals() zurückgeliefert wird, kann als Parameter für die formatMethode genutzt werden. Dadurch ist es möglich, im Format-String lokale Variablen zu verwenden. Im folgenden Beispiel benutzen wir die lokalen Variablen a, b und f: >>> print("a={a}, b={b} and f={f}".format(**locals())) a=42, b=47 and f=<function f at 0xb718ca6c> 12.7 Formatierte Stringliterale Formatierte Stringliterale, auch als f-Strings bekannt, sind Strings, denen ein „f” vorangestellt ist. Sie sind erst seit Python3.6 verfügbar! Sie sind ähnlich wie die Strings, die von str.format() akzeptiert werden. Sie enthalten Ersetzungsfelder, die von geschweiften Klammern umgeben sind. Diese Ersetzungsfelder sind Ausdrücke, die zur Laufzeit ausgewertet und nach den format-Regeln formatiert werden: >>> article = "Regal" >>> height = 176.4 >>> f"Artikel: {article}, Höhe: {height:6.2f}" 'Artikel: Regal, Höhe: 176.40' >>> width, precision = 10, 3 >>> x = 529.9637 >>> f"Value: {x:{width}.{precision}}" 'Value: 5.3e+02' 12.8 Weitere String-Methoden zum Formatieren Die String-Klasse enthält weitere Methoden, die man auch zu Formatierungszwecken nutzen kann: ljust, rjust, center und zfill Falls S ein String ist, können wir die vier Methoden wie folgt erklären: ■ center(...): S.center(width[, fillchar]) -> str 12.8 Weitere String-Methoden zum Formatieren An den String S werden links und rechts Füllzeichen bis zur Länge „width” so angefügt, dass der ursprüngliche String zentriert wird. Wird für das Füllzeichen „fillchar” kein Wert angegeben, wird als Standardwert ein Leerzeichen verwendet. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiele: >>> s = "Python" >>> s.center(10) ' Python ' >>> s.center(10,"*") '**Python**' ■ ljust(...): S.ljust(width[, fillchar]) -> str Der String S wird linksbündig zurückgeliefert. Rechts wird der String mit Füllzeichen „fillchar” bis zur Länge „width” angefüllt. Auch hier ist ein Leerzeichen der Default-Wert für „fillchar”. Beispiele: >>> s = "Training" >>> s.ljust(12) 'Training ' >>> s.ljust(12,":") 'Training::::' ■ rjust(...): S.rjust(width[, fillchar]) -> str Der String S wird rechtsbündig zurückgeliefert. Von links wird der String mit Füllzeichen „fillchar” bis zur Länge „width” angefüllt. Auch hier ist ein Leerzeichen der Default-Wert für „fillchar”. Beispiele: >>> s = "Programming" >>> s.rjust(15) ' Programming' >>> s.rjust(15, "~") '~~~~Programming' ■ zfill(...): S.zfill(width) -> str Ein String S wird von links mit Nullen bis zu der Länge „width” aufgefüllt. Der String S wird nicht abgeschnitten, falls er bereits länger als die vorgesehene Länge „width” ist. Diese Methode entspricht einem S.rjust(width, "0") 105 106 12 Formatierte Ausgabe und Strings formatieren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiele: >>> account_number = "43447879" >>> account_number.zfill(12) '000043447879' >>> # can be emulated with rjust: ... >>> account_number.rjust(12,"0") '000043447879' Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 13 13.1 Flaches und tiefes Kopieren Einführung In diesem Kapitel geht es nun um das Kopieren von Listen und vor allem um das Kopieren von verschachtelten Listen. Dabei kann es vor allem bei Anfängern, falls sie die genauen Zusammenhänge nicht kennen, zu verblüffenden und verwirrenden Erfahrungen kommen, die meist mit Fehlern im Programm einhergehen. Im Folgenden werden wir die Identitätsfunktion id benutzen, die ein Objekt als Parameter hat. id(obj) liefert die „Identität” des Objekts obj. Diese Identität, der Rückgabewert der Funktion, ist ein Integer-Wert, der eindeutig und konstant für dieses Objekt ist, solange es im Programmablauf existiert. Betrachten wir folgenden Code: >>> x = 3 >>> y = x Es stellt sich die Frage, was bei der Zuweisung y = x Bild 13.1 Mona-Lisa-Kopie passiert? Bezeichnen x und y die gleiche Speicherzelle – oder besser das gleiche Objekt – oder wird eine eigene Speicherzelle, d.h. ein eigenes Objekt, für y angelegt, in das die Zahl 3 kopiert wird? Ersteres ist der Fall, d.h. x und y teilen sich das gleiche Objekt. Bildlich kann man sich dies wie folgt veranschaulichen: 108 13 Flaches und tiefes Kopieren Den Beweis, dass dies wirklich so ist, können wir mittels der id-Funktion führen. Wir zeigen, dass die Identität von x und y gleich sind, d.h. sie „zeigen” auf das gleiche Objekt bzw. teilen sich das gleiche Objekt. Man sagt auch: x und y referenzieren das gleiche Objekt: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> x = 3 >>> y = x >>> print(id(x), id(y)) 137220832 137220832 Was passiert nun, wenn wir einer der beiden Variablen einen neuen Wert zuweisen, also beispielsweise die Zahl 2 der Variablen y: >>> y = 2 >>> print(id(x)==id(y), x, y) False 3 2 Bildlich sieht die Situation nun also wie folgt aus: y zeigt nun auf ein eigenes Integer-Objekt. Alle Variablen in Python sind Referenzen auf Objekte. Trotzdem verhalten sich Zuweisungen der Art y = x (wenn x ein unveränderlicher Datentyp wie int, float, str oder tuple ist) wie Kopien, da das „Sharing” (gemeinsame Referenz) aufgelöst wird, sobald x oder y ein neues Objekt zugewiesen wird. Veränderliche Objekte wie Listen und Dictionaries können wir jedoch nicht mittels einer einfachen Zuweisung kopieren. Auch in diesem Fall gibt es nur Zeiger auf das gleiche Objekt. Im Folgenden wollen wir dies und die daraus resultierenden Herausforderungen im Detail darstellen und auf einige Probleme eingehen. 13.2 Kopieren einer Liste Im folgenden Code-Beispiel legen wir als Erstes eine flache Liste mit dem Namen colours1 an. Mit „flach”1 meinen wir, dass die Liste nicht verschachtelt ist, also keine weiteren Unterlisten enthält. Anschließend weisen wir die Liste colours1 einer Variablen colours2 zu. Mittels der print()-Funktion können wir uns überzeugen, dass beide Variablen den gleichen Inhalt haben, und mittels id() sehen wir, dass beide Variablen auf das gleiche Listenobjekt zeigen. Nun weisen wir colours2 eine neue Liste zu. Es erstaunt wenig, dass die Werte von colours1 dabei unverändert bleiben, d.h. colours1 zeigt weiterhin auf sein altes Objekt, während colours2 ein neues Objekt erhalten hat. Wie im ersten Beispiel mit den Integer-Variablen wird ein neuer Speicherbereich für colours2 angelegt, wenn dieser Variablen eine komplett neue Liste – also ein neues Objekt – zugeordnet wird. 1 engl. shallow Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 13.2 Kopieren einer Liste >>> colours1 = ["red", "blue"] >>> colours2 = colours1 >>> print(colours1) ['red', 'blue'] >>> print(colours2) ['red', 'blue'] >>> print(id(colours1), id(colours2)) 140233898425800 140233898425800 >>> colours2 = ["rot", "blau"] >>> print(colours1) ['red', 'blue'] >>> print(colours2) ['rot', 'blau'] Im folgenden Beispiel gehen wir der Frage nach, was passiert, wenn wir einer Variablen nicht ein neues Objekt zuordnen, sondern nur ein einzelnes Element der Liste ändern. Um dies zu testen, weisen wir dem zwei- Bild 13.2 Referenzen auf Listen ten Element von colours2, also dem Element mit dem Index 1, einen neuen Wert zu. Ohne das vorher Gesagte würde es viele erstaunen, nun zu sehen, dass auch colours1 damit verändert wurde. Aber colours1 wurde ja nicht verändert, sondern nur das Objekt, auf das diese Variable ebenso wie colours2 zeigt. >>> colours1 = ["red", "blue"] >>> colours2 = colours1 >>> colours2[1] = "green" >>> print(colours1) ['red', 'green'] >>> print(colours2) ['red', 'green'] Wie wir bereits im vorigen Beispiel gesehen haben, wird bei der Zuweisung colours2 = colours1 keine Kopie, sondern nur eine Referenz auf die gleiche Liste erzeugt. Änderungen innerhalb des Listenobjekts wirken sich also auf beide Variablennamen – colours1 und colours2 – aus, da sie ja auf dasselbe Objekt zeigen. Bild 13.3 Referenzen auf Listen 109 110 13 Flaches und tiefes Kopieren 13.3 Flache Kopien Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Mit der copy-Methode der list-Klasse können wir flache Kopien von Listen erzeugen2 : >>> liste1 = ['a','b','c','d'] >>> liste2 = liste1.copy() >>> liste2[1] = 'x' >>> print(liste1) ['a', 'b', 'c', 'd'] >>> Sobald jedoch auch Unterlisten in der zu kopierenden Liste vorkommen, werden nur Zeiger auf diese Unterlisten kopiert: Dieses Verhalten beim Kopieren veranschaulicht das nebenstehende Bild. Weist man nun zum Beispiel dem 0-ten Element einer der beiden Listen einen neuen Wert zu, führt dies nicht zu einem Nebeneffekt. Probleme gibt es erst, wenn man direkt eines der beiden Elemente der Unterliste verändert. Bild 13.4 Kopieren von Listen Um dies zu demonstrieren, ändern wir nun zwei Einträge in lst2: >>> lst1 = ['a','b',['ab','ba']] >>> lst2 = lst1.copy() >>> lst2[0] = 'c' >>> lst2[2][1] = 'd' >>> print(lst1) ['a', 'b', ['ab', 'd']] Man erkennt, dass man aber nicht nur die Einträge in lst2 geändert hat, sondern auch den Eintrag von lst1[2][1]. Dies liegt daran, dass in beiden Listen, also lst1 und lst2, das jeweils dritte Element nur ein Link auf eine physikalisch gleiche Unterliste ist. Diese Unterliste wurde nicht mit lst1.copy() mitkopiert. Bild 13.5 Kopieren von Listen 2 Dazu kann man auch den Teilbereichsoperator (slicing) verwenden: „liste2 = liste1[:]” 13.4 Kopieren mit deepcopy 13.4 Kopieren mit deepcopy Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Abhilfe für das eben beschriebene Problem schafft das Modul copy. Dieses Modul stellt die Funktion deepcopy zur Verfügung, die das komplette oder besser: tiefe Kopieren einer nicht flachen Listenstruktur erlaubt: >>> from copy import deepcopy >>> lst1 = ['a','b',['ab','ba']] >>> lst2 = deepcopy(lst1) Die Speichersituation nach dem obigen Code haben wir im folgenden Diagramm dargestellt: Im folgenden Code zeigen wir, dass sich Zuweisungen nur auf die jeweilige Liste auswirken, da nun auch die Unterliste kopiert worden ist: >>> lst2[2][1] = "d" >>> lst2[0] = "c"; >>> print(lst2) ['c', 'b', ['ab', 'd']] >>> print(lst1) ['a', 'b', ['ab', 'ba']] Die Speichersituation sehen wir im folgenden Diagramm: 111 112 13 Flaches und tiefes Kopieren 13.5 Deepcopy für Dictionaries Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. deepcopy aus dem Modul copy funktioniert für beliebige Python-Objekte, also beispielsweise auch für Dictionaries: >>> from copy import deepcopy >>> d = {"a":3, "b":4} >>> copy_of_d = deepcopy(d) >>> d["c"] = 1 >>> d {'b': 4, 'a': 3, 'c': 1} >>> copy_of_d {'b': 4, 'a': 3} Auch die Methode copy der Klasse dict erzeugt nur flache Kopien eines Dictionarys, wie wir im Folgenden sehen können: >>> p = {"Frank" : {"Gewicht":74.8, "Größe":183.4}} >>> p = {"Frank" : {"Gewicht":74.8, "Größe":183.4}, ... "Sarah": {"Gewicht":57.3, "Größe": 168.9}} >>> p2 = p.copy() >>> p["Sarah"]["Gewicht"] -= 0.4 >>> p2 {'Sarah': {'Gewicht': 56.9, 'Größe': 168.9}, 'Frank': {'Gewicht': 74.8, 'Größe': 183.4}} Wir sehen, dass wir nicht nur das Dictionary p, sondern auch das Dictionary p2 verändert haben. Um dieses Problem zu beheben, erzeugen wir mit deepcopy eine tiefe Kopie. Wir arbeiten weiter mit den Werten der obigen Shell. Das Gewicht von Sarah reduziert sich um weitere 200 Gramm, allerdings wirkt sich dies nun nicht auf die tiefe Kopie p2 aus. >>> from copy import deepcopy >>> p2 = deepcopy(p) >>> p["Sarah"]["Gewicht"] -= 0.2 >>> p2["Sarah"]["Gewicht"] 56.9 >>> p["Sarah"]["Gewicht"] 56.699999999999996 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 14 14.1 Funktionen Allgemein Das Konzept einer Funktion gehört wohl zu den bedeutendsten der Mathematik. Auch in Programmiersprachen werden sie häufig eingesetzt, um mathematische Funktionen in Algorithmen umzusetzen. Eine solche Funktion berechnet ein oder mehrere Werte und ist vollständig von den übergebenen Eingabewerten, den sogenannten Parametern, abhängig. Ganz allgemein gesehen stellt eine Funktion ein Strukturierungselement in einer Programmiersprache dar, um eine Menge von Anweisungen zu gruppieren, damit man sie mehrmals im Programm verwenden kann. Ohne Funktionen könnte man Code nur kopieren, um ihn wiederzuverwenden. Spätere Bild 14.1 Funktionen Änderungen müssten dann an allen kopierten Stellen durchgeführt werden, was natürlich sehr aufwendig und fehlerträchtig wäre. Die Benutzung von Funktionen erhöht außerdem die Verständlichkeit und Qualität eines Programms. Ebenso lassen sich durch ihre Verwendung auch die Kosten für die Software-Entwicklung und die Wartung der Software senken. 14.2 Funktionen In diesem Kapitel zeigen wir, wie man in Python eigene Funktionen schreibt. Funktionen hatten wir bereits in den vorigen Kapiteln benutzt. So benutzten wir die Funktion len, um die Anzahl der Zeichen eines Strings oder die Anzahl der Elemente einer Liste zu ermitteln. Eine Funktion wird aufgerufen und liefert einen Wert – oder genauer gesagt: ein Objekt – zurück. Diesen Wert kann man auch wieder in einem Ausdruck weiterverwenden, wie wir es im nächsten Beispiel tun. Wir multiplizieren den Rückgabewert mit der Zahl 3: 114 14 Funktionen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> name = "Monty Python" >>> len(name) 12 >>> len(name) * 3 36 Eine Funktionsdefinition wird mit dem Schlüsselwort def eingeleitet, gefolgt von einem frei wählbaren Funktionsnamen. Eingeschlossen in ein Klammerpaar folgt dann die Parameterliste. Sie kann auch leer sein. Die Parameternamen der Parameterliste werden durch Kommata getrennt. def funktionsname(Parameterliste): Anweisung(en) Der Funktionskörper, der aus den Anweisungen besteht, die bei Aufruf der Funktion ausgeführt werden, wird durch eine homogene Einrückung markiert, also ebenso wie alle anderen Blöcke. Die Funktionsdefinition ist beendet, wenn eine Anweisung erfolgt, die wieder auf derselben Einrückungsstufe steht wie der Kopf der Funktion. Der Funktionskörper kann ein oder mehrere return-Anweisungen enthalten. Diese können sich an beliebiger Stelle innerhalb des Funktionskörpers befinden. Eine return-Anweisung beendet den Funktionsaufruf, und das Ergebnis des Ausdrucks, der hinter der returnAnweisung steht, wird an die aufrufende Stelle zurückgeliefert. Falls dem return kein Ausdruck folgt, wird der spezielle Wert None zurückgeliefert. Falls der Funktionsaufruf das Ende des Funktionskörpers erreicht, ohne auf eine return-Anweisung gestoßen zu sein, endet der Funktionsaufruf, und es wird ebenfalls der Wert None zurückgegeben. Schauen wir uns ein einfaches Beispiel an: def say_hello(name): return "Hello " + name n = "Michael" n = say_hello(n) print(n) name = "Kirstin" name = say_hello(name) print(name) Bild 14.2 Funktionsaufrufe 14.3 Docstring Im Diagramm zeigen wir, was passiert, wenn man das Programm ausführt. Beim ersten Teil des Programms handelt es sich um die Definition der Funktion. Sie wird beim Lauf des Programms „übersprungen”. Als erste Anweisung wird die Zuweisung an die Variable n ausgeführt. Danach wird die Funktion say_hello mit dem Argument n aufgerufen. Zurückgeliefert wird der String „Hello Michael”. Danach wird die Variable „name” auf „Kirstin” gesetzt. Dann wird wieder die Funktion say_hello aufgerufen, die dann den String „Hello Kirstin” zurückliefert. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Ein weiteres Beispiel: def fahrenheit(T_in_celsius): return (T_in_celsius * 9 / 5) + 32 for t in (22.6, 25.8, 27.3): print(t, ": ", fahrenheit(t)) Die Ausgabe des obigen Skripts mit der Funktion fahrenheit sieht wie folgt aus: 22.6 : 25.8 : 27.3 : 72.68 78.44 81.14 14.3 Docstring Wie der Name nahelegt, handelt es sich beim Docstring um einen String, der zu Dokumentationszwecken dient. Er steht in einer Funktion direkt nach dem Funktionskopf, also nach der „def”-Zeile. Der Wert des Docstrings ist in dem Attribut .__doc__ gespeichert und vom Programm aus abrufbar. Wir definieren unsere Funktion „fahrenheit” vom vorigen Abschnitt nun mit einem Docstring. Diesmal allerdings in der interaktiven Python-Shell: >>> def fahrenheit(T_in_celsius): ... """ returns the temperature in degrees Fahrenheit """ ... return (T_in_celsius * 9 / 5) + 32 ... >>> Wir können sehen, dass der Docstring keine Auswirkungen auf das Ein- und Ausgabeverhalten der Funktion hat: >>> fahrenheit(10) 50.0 Den Docstring selbst können wir über das Attribut .__doc__ ansprechen: >>> fahrenheit.__doc__ ' returns the temperature in degrees Fahrenheit ' >>> 115 116 14 Funktionen Die besondere Bedeutung des Docstrings sieht man, wenn man die help-Funktion benutzt. Wenn wir in der Shell den Befehl help(fahrenheit) eingeben, erhalten wir folgende Ausgabe: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Help on function fahrenheit in module __main__: fahrenheit(T_in_celsius) returns the temperature in degrees Fahrenheit (END) Durch Eintippen des Buchstaben „q” können Sie übrigens die Help-Ausgabe wieder verlassen. Besonders eindrucksvoll zeigt sich jedoch der Docstring, wenn wir mehrere Funktionen in einer Python-Datei definieren. Wir können auch einen weiteren String an den Anfang dieser Datei stellen. Das folgende Programm bietet zwei Funktionen zur Umwandlung zwischen Celsius und Fahrenheit: """ Ein Modul mit Konvertierungshilfen zwischen Grad Celsius und Grad Fahrenheit """ def fahrenheit(T_in_celsius): """ rechnet die Temperatur T_in_celsius in Grad Fahrenheit um """ return T_in_celsius * 9 / 5 + 32 def celsius(T_in_fahrenheit): """ rechnet die Temperatur T_in_fahrenheit in Grad Celsius um """ return (T_in_fahrenheit - 32) * 5 / 9 Wir speichern das obige Programm in einer Datei, die wir konvertierungen.py nennen. Wir können diese Datei wie ein Modul importieren.1 Danach rufen wir help auf dieses Modul auf: >>> import konvertierungen >>> help(konvertierungen) Wir erhalten nun die folgende schöne Ausgabe, die sich in ihrem Aussehen nicht von Standardmodulen unterscheidet: Help on module konvertierungen: NAME konvertierungen - Ein Modul mit Konvertierungshilfen zwischen Grad Celsius und Grad Fahrenheit FUNCTIONS celsius(T_in_fahrenheit) 1 Jedes Python-Programm ist automatisch auch ein Modul! 14.4 Standardwerte für Funktionen rechnet die Temperatur T_in_fahrenheit in Grad Celsius um fahrenheit(T_in_celsius) rechnet die Temperatur T_in_celsius in Grad Fahrenheit um FILE Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. /home/bernd/Dropbox/python-buch/dritte_auflage/beispiele/ konvertierungen.py (END) Wir sehen, dass der Docstring im Hilfstext unter den entsprechenden Funktionen erscheint. 14.4 Standardwerte für Funktionen Man hat auch die Möglichkeit, die Parameter einer Funktion mit Standardwerten – meist auch als Default-Werte bezeichnet – zu versehen. Diese Parameter sind dann optional beim Aufruf der Funktion, d.h. man kann für einen solchen Parameter ein Argument zur Verfügung stellen, muss das aber nicht. Wird kein Wert angegeben, wird der Standardwert für diesen Parameter eingesetzt. Wir zeigen dies mit einer kleinen Funktion im folgenden Beispiel. Mit der Funktion „hallo” wird eine Person mit Namen begrüßt. Wird jedoch beim Aufruf kein Name übergeben, druckt sie nur „Hallo Namenloser!”: >>> def hallo(name="Namenloser"): ... print("Hallo " + name + "!") ... >>> hallo("Peter") Hallo Peter! >>> hallo() Hallo Namenloser! >>> Im nächsten Beispiel definieren wir die Funktion „umfang”, die den Umfang eines Rechtecks berechnet: >>> def umfang(laenge=2, breite=1): ... return 2 * (laenge + breite) ... >>> >>> umfang(5, 3) 16 >>> umfang(5) # für die breite wird der Standardwert ,,1'' benutzt 12 >>> umfang() # es werden nun beide Standardwerte benutzt 6 117 118 14 Funktionen Wie sieht es aber aus, wenn wir nur einen Wert für die Breite angeben wollen? Bisher wurden die Argumente der Reihenfolge nach an die Parameter übergeben. Übergibt man nur ein Argument, bedeutet das automatisch, dass dies ein Wert für den ersten Parameter darstellt, also in unserem Fall für laenge. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 14.5 Schlüsselwortparameter Für das zuletzt beschriebene Problem liefern die Schlüsselwortparameter eine Lösung. Schlüsselwortparameter werden benutzt, um beim Aufruf einer Funktion einen Ausdruck einem bestimmten Parameter zuzuordnen. Die Funktionsdefinition selbst ändert sich nicht, d.h. ein Schlüsselwortparameter entspricht einem Parameter. Wir sind nun in der Lage, unsere vorher definierte Funktion „umfang” nur mit einem Wert für den Parameter „breite” aufzurufen, wenn wir diesen als Schlüsselwortparameter verwenden: >>> umfang(breite=1.5) 7.0 Im folgenden Beispiel nutzen wir Schlüsselwortparameter, obwohl es nicht notwendig wäre. Es dient aber dazu, die Lesbarkeit eines Programmes deutlich zu erhöhen: >>> umfang(laenge=5, breite=3) 16 >>> umfang(5, 3) # so hätten wir auch statt des vorigen Aufrufs schreiben können 16 >>> umfang(breite=3, laenge=5) # oder so 16 14.6 Funktionen ohne return In der Funktion Hallo hatten wir lediglich eine print-Funktion, aber keine returnAnweisung verwendet. Man sieht also, dass es nicht zwingend notwendig ist, eine returnAnweisung in einer Funktion zu haben. Zu Beginn unseres Kapitels stellten wir aber fest, dass Funktionen einen Wert bzw. ein Objekt zurückliefern. Funktionen liefern immer ein Objekt zurück. Was passiert aber, wenn wir keine return–Anweisungen in der Funktion haben? Bei Funktionen ohne returnAnweisungen wird das None-Objekt zurückgeliefert. Wir demonstrieren dies mit einer primitiven Funktion, deren Körper nur aus einer pass-Anweisung besteht: >>> def f(): ... pass ... 14.7 Mehrere Rückgabewerte Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> >>> x = f() >>> x == None True >>> print(x) None None wird auch zurückgeliefert, wenn eine return-Anweisung ohne nachfolgenden Ausdruck eine Funktion beendet, wie wir im folgenden Beispiel sehen: >>> def f(): ... return ... >>> x == None True Ansonsten wird der Wert des Ausdrucks hinter dem return zurückgeliefert. 14.7 Mehrere Rückgabewerte Eine Funktion kann genau ein Objekt zurückliefern, welches beispielsweise ein numerischer Wert wie eine ganze Zahl (Integer) oder eine Fließkommazahl (float) sein kann, aber auch ein komplexes Objekt wie etwa eine Liste oder ein Dictionary. Wollen wir beispielsweise drei Integers zurückliefern, können wir diese in ein Tupel oder eine Liste packen und dieses Listen- oder Tupel-Objekt als return-Wert nutzen. Wir können also indirekt auf diese Art beliebig viele Werte zurückliefern. Im folgenden Beispiel berechnet die Funktion fib_intervall die Fibonacci-Begrenzung für eine beliebige positive Zahl, d.h. sie liefert ein 2-Tupel zurück. Das erste Element ist die größte Fibonacci-Zahl, die kleiner oder gleich x ist, und die zweite Komponente ist die kleinste Fibonacci-Zahl größer oder gleich x. Der Rückgabewert wird direkt mittels „unpacking” in die Variablen lub und sup gespeichert: def fib_intervall(x): """ liefert ein Tupel mit der größten Fibonacci-Zahl, kleiner oder gleich x, und der kleinsten Fibonacci-Zahl, größer oder gleich x, zurück """ if x < 0: return -1 old, new = 0,1 while True: if new < x: old, new = new, old+new else: return (old, new) 119 120 14 Funktionen for i in (0, 1, 2, 3, 4, 5, 7, 8, 9, 12, 13, 14) : print(i, fib_intervall(i)) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Das obige Programm liefert die folgenden Ergebnisse zurück: 0 (0, 1) 1 (0, 1) 2 (1, 2) 3 (2, 3) 4 (3, 5) 5 (3, 5) 7 (5, 8) 8 (5, 8) 9 (8, 13) 12 (8, 13) 13 (8, 13) 14 (13, 21) 14.8 Lokale und globale Variablen Variablen sind standardmäßig lokal in einer Funktion, in der sie definiert werden. Man kann aber auch auf globale Variablen innerhalb einer Funktion lesend zugreifen: def f(): print(s) s = "Python" f() Das obige Skript gibt „Python” aus. Wir fügen s = „Perl” vor der print-Anweisung ein: def f(): s = "Perl" print(s) s = "Python" f() print(s) Die Ausgabe lautet dann: Perl Python Daran erkennt man, dass die Variable s in der Funktion f lokal ist, denn die Variable s des Hauptprogramms hat noch ihren ursprünglichen Wert. Interessant wird es nun, wenn man eine print-Anweisung vor die Zuweisung an s in der Funktion f einfügt: def f(): print(s) 14.8 Lokale und globale Variablen in Funktionen s = "Perl" print(s) s = "Python" f() print(s) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Man erhält nun die folgende Fehlermeldung: UnboundLocalError: local variable 's' referenced before assignment Die Variable s ist in f() mehrdeutig, d.h. im ersten print von f() könnte es sich um die globale Variable s mit dem Wert „Python” handeln. Anschließend definieren wir eine lokale Variable mit dem Wert „Perl”. Will man innerhalb einer Funktion auf eine globale Variable schreibend zugreifen, so muss man diese explizit als global deklarieren. Dies geschieht mittels der global-Anweisung: def f(): global s print(s) s = "Perl" print(s) s = "Python" f() print(s) Die Ausgabe lautet: Python Perl Perl Definiert man eine Variable innerhalb einer Funktion als global, so muss diese nicht im Namensraum existieren, aus dem die Funktion aufgerufen wird. Die Variable wird zur Laufzeit erzeugt und existiert nach Abarbeitung der Funktion auch im Namensraum, aus dem die Funktion aufgerufen worden ist: >>> # es existiert keine Variable x: ... >>> x Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not defined >>> >>> def f(): ... global x ... x = 42 ... >>> # nach der Definition von f existiert noch keine Variable x: 121 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 122 14 Funktionen ... >>> x Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not defined >>> >>> f() >>> >>> # nach dem Aufruf von f() existiert nun eine Variable x ... >>> x 42 >>> 14.9 Parameterübergabe im Detail Eine Funktion oder eine Prozedur benötigt üblicherweise Informationen über die Umgebung, aus der heraus sie aufgerufen wurde. Die Schnittstelle zwischen dieser Umgebung und der Funktion bzw. Prozedur, d.h. dem Funktions- bzw. Prozedurrumpf (body), besteht aus speziellen Variablen, die man als Parameter bezeichnet. Indem man Parameter benutzt, kann man verschiedenste Objekte von „außen” innerhalb der Funktion benutzen. Die Syntax, wie man Parameter deklariert, und die Semantik, wie man die Argumente des Aufrufs an die formalen Parameter der Funktion oder Prozedur weiterleitet, sind abhängig von der jeweiligen Programmiersprache. Im vorigen Abschnitt haben wir mehrfach die Begriffe Parameter und Argumente verwendet. Bei einigen Lesern hat sich sicherlich der Eindruck eingestellt, dass die beiden Begriffe synonym sind. Auch in der Literatur werden diese Begriffe häufig synonym verwendet. Dennoch gibt es einen klar definierten Unterschied. Parameter stehen im Kopf der Funktion oder Prozedur, also in der Definition der Funktion oder Prozedur, während Argumente beim Aufruf verwendet werden. Sie liefern die Werte für die formalen Parameter während der Laufzeit des Programms. Python benutzt einen Mechanismus, den man als „Call by Object” (auch „Call by Object Reference” oder „Call by Sharing”) bezeichnet. Wenn man unveränderliche Argumente wie Integer, Strings oder Tupel an eine Funktion übergibt, verhält sich die Übergabe nach außen hin wie eine Wertübergabe2 . Die Referenz auf das unveränderliche Objekt wird an den formalen Parameter der Funktion übertragen. Innerhalb der Funktion kann der Inhalt des Objekts nicht verändert werden, da es sich ja um unveränderliche Objekte handelt. Anders verhält es sich jedoch, wenn man veränderliche Argumente übergibt. Auch sie werden per Referenz an die Funktionsparameter übertragen. Allerdings können einzelne Elemente eines solchen veränderlichen Objekts im Funktionsrumpf verändert werden. Wenn wir beispielsweise eine Liste an eine Funktion übergeben, müssen wir zwei Fälle unter2 Oft auch als „Call by value” bezeichnet 14.9 Parameterübergabe im Detail Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. scheiden: Die Elemente einer Liste können verändert werden, d.h. diese Änderung wirkt sich auch auf den Gültigkeitsbereich aus, aus dem die Funktion aufgerufen wurde. Wird allerdings ein komplett neues Element dem formalen Parameter zugewiesen, z.B. eine andere Liste, dann hat dies keine Auswirkungen auf die „alte” Liste, also auf die Liste, die beim Aufruf übergeben worden ist.3 Das oben Gesagte veranschaulichen wir zunächst mit einem Beispiel für die Übergabe einer Integer-Variablen. Der Parameter innerhalb der Funktion ref_demo bleibt solange eine Referenz auf das Objekt, das übergeben wurde, wie keine Änderung am Parameter erfolgt. Sobald also ein neuer Wert an den Parameter überwiesen wird, erzeugt Python eine neue Speicherstelle für das Objekt, und der formale Parameter zeigt nun auf diese Speicherstelle. Die Variable des Funktionsaufrufs wird dadurch nicht verändert, wie wir im folgenden Beispiel nachvollziehen können: Bild 14.3 ref_demo vor der Zuweisung „x=42” def ref_demo(x): print("x=",x," id=",id(x)) x=42 print("x=",x," id=",id(x)) Wenn man die Funktion ref_demo() des obigen Beispiels aufruft, so wie wir es im unten stehenden Beispiel tun, können wir prüfen, was mit x passiert: Wir können im Hauptprogramm (Gültigkeitsbereich main) sehen, dass x die Identität 10914624 hat. In der ersten print-Anweisung der ref_demo()-Funktion wird das x aus dem main-Gültigkeitsbereich benutzt, denn wir sehen, dass wir den gleichen Identitätswert für x erhalten. Wenn wir jedoch den Wert 42 dem x zuweisen, erhält x eine neue Identität, 10915680. Dies bedeutet, dass die Variable x der Funktion ein neues Objekt referenziert bzw. erhalten hat. Die Variable x in main bleibt unberührt, d.h. sie referenziert immer noch das Objekt mit der Identität 10914624, welches immer noch den Wert 9 beinhaltet. Bild 14.4 ref_demo nach der Dies bedeutet, dass sich Python zuerst wie Call-by- Zuweisung „x=42” Reference verhält, d.h. man benutzt bzw. referenziert dasselbe Objekt. Aber sobald x einen neuen Wert zugewiesen bekommt, verhält es sich wie bei Call-by-Value, was in diesem Fall bedeutet, dass x ein eigenes Objekt erhält. Es wird also eine lokale Speicherposition für den formalen Parameter x angelegt, und das x in main behält seinen ursprünglichen Wert: >>> x = 9 >>> id(x) 10914624 >>> ref_demo(x) 3 Sollten Sie dazu Verständnisprobleme haben, empfehlen wir Ihnen, das Kapitel 13 (Flaches und tiefes Kopieren) nochmals gründlich durchzuarbeiten. 123 124 14 Funktionen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. x= 9 id= 10914624 x= 42 id= 10915680 >>> id(x) 10914624 >>> x 9 14.10 Effekte bei veränderlichen Objekten Im vorigen Abschnitt haben wir gesehen, dass man unveränderliche Objekte, die man als formale Parameter an eine Funktion übergibt, in der Funktion nicht verändern kann. Anders sieht es jedoch bei veränderlichen Objekten wie Listen oder Dictionaries aus. Im folgenden Beispiel übergeben wir die Liste fib an den formalen Parameter liste der Funktion s: >>> def s(liste): ... print(id(liste)) ... liste += [47,11] ... print(id(liste)) ... >>> fib = [0,1,1,2,3,5,8] >>> id(fib) 24746248 >>> s(fib) 24746248 24746248 >>> id(fib) 24746248 >>> fib [0, 1, 1, 2, 3, 5, 8, 47, 11] Wir sehen, dass die Werte 47 und 11 an die Liste angehängt werden. Da sowohl die Variable liste als auch die Variable fib dieses Objekt referenzieren, wirkt sich diese Änderung auf beide aus. Dies bedeutet, dass wir die an s übergebene Liste innerhalb der Funktion verändert haben. Dies kann gewünscht sein, wie dies beispielsweise bei append der Fall ist, aber es könnte sich auch um einen „unerwünschten” Seiteneffekt der Funktion handeln. Von einem Funktionsaufruf erwartet man, dass die Funktion den korrekten Wert für die Argumente zurückliefert und sonst keine Effekte verursacht, z.B. Ändern von Speicherzuständen von übergebenen Objekten. Auch wenn manche Programmierer bewusst Nebeneffekte zur Lösung ihrer Probleme einsetzen, wird dies im Allgemeinen als schlechter Stil betrachtet. Schlimmer ist es jedoch, wenn Nebeneffekte4 auftreten, mit denen man nicht gerechnet hat, und 4 auch als Seiteneffekte bezeichnet 14.11 Kommandozeilenparameter dadurch Fehler verursacht werden. Dies kann auch in Python passieren, z.B. dann, wenn Listen oder Dictionaries als Parameter übergeben werden. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Man kann dies verhindern, wenn man statt einer Liste eine Kopie der Liste als Parameter übergibt. Mittels der Slicing-Funktion kann man ganz leicht eine Kopie einer flachen Liste erzeugen. So wird in s(fib[:]) nicht die Liste fib, sondern eine komplette Kopie übergeben. Der Aufruf verändert also nicht den Wert von fib, wie man im folgenden interaktiven Programmstück sehen kann: >>> ... ... ... >>> >>> [0, >>> [0, def s(liste): liste += [47,11] print(liste) fib = [0,1,1,2,3,5,8] s(fib[:]) 1, 1, 2, 3, 5, 8, 47, 11] fib 1, 1, 2, 3, 5, 8] 14.11 Kommandozeilenparameter Nicht nur Funktionen, sondern auch einem Python-Skript kann man Argumente übergeben. Man bezeichnet diese als Kommandozeilenparameter. Ruft man ein Python-Skript aus einer Shell auf, werden die Argumente jeweils durch ein Leerzeichen voneinander getrennt hinter dem Skriptnamen aufgeführt. Im Python-Skript selber sind die Argumente, also die Kommandozeilenparameter, als Liste unter dem Namen sys.argv abrufbar. Zusätzlich zu den Kommandozeilenparametern enthält diese Liste auch den Namen des aufrufenden Skripts (Dateiname). Dieser steht als erster Eintrag in der Liste, also sys.argv[0]. Das Skript (argumente.py) listet sämtliche mitgegebenen Argumente in der Standardausgabe auf: # Modul sys wird importiert: import sys # Iteration über sämtliche Argumente: for Argument in sys.argv: print(Argument) Beispiel eines Aufrufs, falls das Skript unter argumente.py gespeichert wurde: python argumente.py python kurs fuer anfaenger Dieser Aufruf erzeugt folgende Ausgabe: argumente.py python 125 126 14 Funktionen kurs fuer anfaenger Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 14.12 Variable Anzahl von Parametern Man hat sehr häufig Fälle, in denen die Anzahl der beim Aufruf nötigen Parameter a priori nicht bekannt ist.5 Im Folgenden zeigen wir, wie man Funktionen definiert, die mit einer beliebigen Anzahl von Argumenten aufgerufen werden können. Der Begriff Stelligkeit oder Arität bezeichnet in der Informatik die Parameteranzahl von Funktionen, Prozeduren oder Methoden. Als variadische Funktion bezeichnet man in der Informatik Funktionen, Prozeduren oder Methoden mit unbestimmter Arität, also solche, deren Parameteranzahl nicht bereits in ihrer Deklaration festgelegt ist. Zur Definition von variadischen Funktionen benutzen wir in Python den „*”-Operator vor einem Parameter. >>> def varpafu(*x): print(x) ... >>> varpafu() () >>> varpafu(34, "Do you like Python?", "Of course") (34, 'Do you like Python?', 'Of course') Wir erkennen, dass die beim Aufruf an varpafu übergebenen Argumente in einem Tupel gesammelt werden. Auf dieses Tupel kann als „normale” Variable im Rumpf der Funktion zugegriffen werden. Ruft man die Funktion ohne jegliche Argumente auf, so ist x ein leeres Tupel. Manchmal benötigt man eine feste Anzahl von Positionsparametern, die von einer beliebigen Anzahl von Parametern gefolgt werden können. Dies ist möglich, aber die Positionsparameter müssen immer zuerst kommen. Im folgenden Beispiel benutzen wir einen Positionsparameter „city”, der immer angegeben werden muss, gefolgt von einer beliebigen Anzahl von weiteren Städten „other_cities”: >>> def locations(city, *other_cities): ... print(city, other_cities) ... >>> locations("Berlin") Berlin () >>> locations("Berlin", "Freiburg", "Stuttgart", ... "Konstanz","Frankfurt") Berlin ('Freiburg', 'Stuttgart', 'Konstanz', 'Frankfurt') Wir wollen dies an einem sinnvolleren Beispiel veranschaulichen. Wir schreiben dazu eine Funktion, die das arithmetische Mittel aus einer variablen Anzahl von Werten berechnet: 5 Diejenigen mit Programmiererfahrungen in C und C++ kennen das Konzept als varargs. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 14.12 Variable Anzahl von Parametern / Variadische Funktionen >>> def arimittel(erster_wert, *weitere_werte): ... return (erster_wert + sum(weitere_werte)) / (1 + len( weitere_werte)) ... >>> arimittel(1) 1.0 >>> arimittel(1, 2) 1.5 >>> arimittel(35, 89, 103, 1, 17) 49.0 >>> arimittel() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: arimittel() missing 1 required positional argument: ' erster_wert' Man mag sich fragen, warum wir sowohl einen Positionsparameter „erster_wert” als auch einen Parameter für eine variable Anzahl von Werten „*weitere_werte” in unserer Funktionsdefinition benutzt haben. Die Idee besteht darin, dass wir erzwingen wollen, dass unsere Funktion immer mit einer nicht-leeren Anzahl von Argumenten aufgerufen wird. Dies ist notwendig, da ein leeres Tupel die Länge 0 hat und wir dann durch 0 dividieren würden, was einen Fehler verursachen würde. Wie sieht es aber nun aus, wenn wir das arithmetische Mittel einer Liste oder eines Tupel berechnen wollen? Ruft man die Funktion mit einer Liste oder einem Tupel als Argument statt mit einer variablen Anzahl von Zahlen auf, wird ein Fehler generiert: >>> lst = [4, 7, 9, 45] >>> arimittel(lst) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in arimittel TypeError: can only concatenate list (not "int") to list >>> Da man aber häufig Listen oder Tupel mit Werten hat, von denen man das arithmetische Mittel berechnen möchte, brauchen wir einen einfachen Weg, um dies zu bewerkstelligen. In unserem konkreten Fall könnte man die einzelnen Elemente der Liste lst auch direkt aufrufen. Aber es ist klar, dass dies für eine längere Liste unpraktikabel wäre. >>> arimittel(lst[0], lst[1], lst[2], lst[3]) 16.25 Schwerwiegender ist jedoch die Tatsache, dass wir normalerweise nicht wissen, wieviel Elemente eine Liste oder Tupel hat. Deshalb ist obiges Vorgehen im allgemeinen Fall nicht anwendbar. Die Lösung besteht in der Benutzung eines weiteren Sternchens: >>> arimittel(*lst) 16.25 127 128 14 Funktionen 14.13 * in Funktionsaufrufen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Ein Stern kann auch in einem Funktionsaufruf erscheinen, wie wir im vorigen Beispiel gesehen haben: Die Semantik ist in diesem Fall „invers” zu der Verwendung eines Sterns in der Funktionsdefinition. Ein Argument wird entpackt und nicht gepackt. In anderen Worten: Die Elemente einer Liste oder eines Tupels werden vereinzelt: >>> def f(x,y,z): ... print(x,y,z) ... >>> p = (47,11,12) >>> f(*p) (47, 11, 12) Es besteht wohl kaum die Notwendigkeit zu erwähnen, dass diese Art, unsere Funktion aufzurufen, komfortabler ist als die folgende: >>> f(p[0],p[1],p[2]) (47, 11, 12) Unsere Beispielfunktion f erwartet genau 3 Argumente. Häufig wird jedoch sowohl in der Definition als auch beim Aufruf einer Funktion ein Sternchen verwendet. Beim Aufruf werden dann Tupel oder Listen entpackt, und bei der Definition werden beliebig viele Argumente in ein Tupel gepackt: >>> def f(*args): ... return args ... >>> >>> f(3, 4, 5, 6) # In der Funktion f werden die Werte ins Tupel args gepackt (3, 4, 5, 6) >>> >>> lst = [4, 6, 12] >>> f(*lst) # Liste lst wird entpackt, und dann werden die Werte in args gepackt (4, 6, 12) >>> Da die Länge der Liste lst erst während der Laufzeit bekannt ist, kann man beim Schreiben des Programms also nur die Sternchen-Notation verwenden. Ein explizites Aufrufen der Listenelemente, also f(lst[0], lst[1], lst[2]), ist deshalb nicht möglich, wenn wir die Anzahl der Elemente einer Liste nicht kennen. 14.14 Beliebige Schlüsselwortparameter 14.14 Beliebige Schlüsselwortparameter Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Es gibt auch einen Mechanismus für eine beliebige Anzahl von Schlüsselwortparametern. Um dies zu ermöglichen, wurde als Notation ein doppeltes Sternchen „**” eingeführt: >>> def f(**args): ... print(args) ... >>> f() {} >>> f(de="German",en="English",fr="French") {'fr': 'French', 'de': 'Germnan', 'en': 'English'} Die Pseudo-Schlüsselwortparameter werden also in ein Dictionary gepackt. 14.15 Doppeltes Sternchen im Funktionsaufruf Das folgende Beispiel veranschaulicht die Verwendung von ** in einem Funktionsaufruf: >>> def f(a,b,x,y): ... print(a,b,x,y) ... >>> d = {'a':'append', 'b':'block','x':'extract','y':'yes'} >>> f(**d) ('append', 'block', 'extract', 'yes') und nun * und ** im Zusammenspiel: >>> def foo(*args, **kwargs): ... print("args: ", args) ... print("kwargs: ", kwargs) ... >>> foo(42,89,12, x=17, y="Hallo", z=[3,8,9]) args: (42, 89, 12) kwargs: {'x': 17, 'y': 'Hallo', 'z': [3, 8, 9]} >>> 129 130 14 Funktionen 14.16 Aufgaben 1. Aufgabe: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Auch für diejenigen, die bereits aus dem Alter heraus sind, in dem sie sich mit Freunden gerne in einer Geheimsprache unterhalten, sollte die folgende Aufgabe interessant sein: Zu schreiben ist eine Funktion, die den String, den sie als Eingabe erhält, in die sogenannte Löffelsprache übersetzt. >>> txt2loeffelsprache("Hallo") 'Halefallolefo' >>> txt2loeffelsprache("Verstehst du Deutsch?") 'veleferstelefehst dulefu deulefeutsch?' Die Codierung erfolgt nach folgenden Regeln: Immer, wenn ein Vokal kommt, wird dieser verdoppelt und dazwischen ein vorher festgelegter String gesetzt. Häufig wird für diesen String „lew” oder wie oben „lef” verwendet. Diphtonge werden wie ein Vokal betrachtet. Damit sieht die Umsetzung wie folgt aus: e = elewe a = alewa i = ilewi o = olewo u = ulewu ü = ülewü ö = ölewö ä = älewä ei = eilewei au = aulewau ie = ielewie eu = euleweu Der Einfachheit halber wandeln wir den String vor der Kodierung in Kleinbuchstaben um, damit wir keine Großbuchstabenvokale und -diphtonge behandeln müssen. Lösung: Lösungen zu Kapitel 14 (Funktionen), Seite 490 2. Aufgabe: Schreiben Sie eine Funktion, die einen String aus der Löffelsprache wieder in normales Deutsch zurück wandelt. Also: >>> l = txt2loeffelsprache("Eineiig") >>> print(l) eilefeineilefeiilefig >>> print(loeffelsprache2txt(l) ... ) eineiig Hinweis: Zur Lösung dieser Aufgabe können Sie die Stringmethode replace möglicherweise gut gebrauchen. Diese wird in Kapitel 19 (Alles über Strings . . . ) auf Seite 186 behandelt. Lösung: Lösungen zu Kapitel 14 (Funktionen), Seite 491 14.16 Aufgaben 3. Aufgabe: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Schreiben Sie eine Funktion is_palindrome, die prüft, ob es sich bei einer übergebenen Zeichenkette um ein Palindrom handelt. Anmerkung: Ein Palindrom ist eine Zeichenkette, die von vorne und von hinten gelesen gleich bleibt, wie beispielsweise die Namen „Anna” und „Otto” oder die Wörter „Reittier” und „Lagerregal”. Das längste deutsche Ein-Wort-Palindrom lautet „Reliefpfeiler”. Palindrome müssen nicht nur aus Buchstaben bestehen. Es gibt auch Zahlenpalindrome, wie etwa 24742. Lösung: Lösungen zu Kapitel 14 (Funktionen), Seite 492 4. Aufgabe: Es gibt auch Satzpalindrome. Auch hier gilt, dass sie vorwärts und rückwärts gelesen gleich sind. Aber bei Satzpalindromen gilt die Besonderheit, dass Leerzeichen und Sonderzeichen gegebenenfalls ignoriert bzw. eingefügt werden: Beispiele: Ein Eis esse sie nie. Ein Esel lese nie. Nein, kann Anna knien? Oh, Cello voll Echo! Erweitern Sie Ihre Funktion is_palindrome, dass sie auch Satzpalindrome prüfen kann. Lösung: Lösungen zu Kapitel 14 (Funktionen), Seite 493 131 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 15 15.1 Rekursive Funktionen Definition und Herkunft des Begriffs Die Rekursion hat etwas mit Unendlichkeit zu tun. Ich weiß, dass die Rekursion etwas mit Unendlichkeit zu tun hat. Er denkt, dass ich weiß, dass die Rekursion etwas mit Unendlichkeit zu tun hat. Sie ist sicher, dass er denkt, dass ich weiß, dass die Rekursion etwas mit Unendlichkeit zu tun hat. Wir glauben nicht, dass sie sicher ist, dass er denkt, dass ich weiß, dass die Rekursion etwas mit Unendlichkeit zu tun hat ... Dieses sprachliche Spiel könnten wir beliebig fortsetzen. Dies ist ein Beispiel für RekurBild 15.1 Rekursion in der natürlichen sion in der natürlichen Sprache. Die Rekursi- Sprache on ist nicht nur eine fundamentale Eigenschaft der natürlichen Sprache, sondern der menschlichen kognitiven Fähigkeiten. Unser ganzes Denken basiert auf rekursiven Denkprozessen. Der Linguist Noam Chomsky hatte einen Formalismus eingeführt, um sprachliche Ausdrücke mithilfe einer Metasprache formal zu definieren. Diese Definition definiert rekursiv eine Grammatik. Diese formalen Sprachen von Chomsky werden auch zur Definition und Spezifikation von Programmiersprachen in der Informatik benutzt. Die Beweismethode der „Vollständigen Induktion” in der Mathematik ist auch eine Art von Rekursion. Das Adjektiv „rekursiv” stammt vom lateinischen Verb „recurrere”, was „zurücklaufen” bedeutet. Das ist genau das, was eine rekursive Definition oder eine rekursive Funktion macht: Sie läuft gewissermaßen zurück auf sich selbst, indem sie sich selbst aufruft. Jedem, der sich bereits ein wenig mit Mathematik oder Informatik beschäftigt hat, dürfte wohl die Fakultät begegnet sein. Vor allem, wenn man bereits eine Programmiersprache erlernt hat. Die Fakultätsfunktion ist das Standardbeispiel für die Einführung der Rekursion in nahezu jedem Tutorial. Die Gründe liegen auf der Hand: Der mathematische Hintergrund ist leicht zu verstehen, und sie lässt sich besonders leicht rekursiv programmieren. Also folgen wir der Tradition und widmen uns auch in unserem Python-Tutorial dieser Funktion. Mathematisch wird die Fakultät wie folgt definiert: n! = n * (n-1)!, if n > 1 and f(1) = 1 134 15 Rekursive Funktionen 15.2 Definition der Rekursion Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Rekursion ist eine Programmiertechnik oder ein Programmierkonzept, in dem eine Funktion sich selbst ein oder mehrmals in ihrem Funktionskörper (body) aufruft. Eine Funktionsdefinition, die die Bedingungen der Rekursion erfüllt, nennen wir eine rekursive Funktion. Abbruchbedingung: Eine rekursive Funktion muss terminieren, damit man sie in einem Programm benutzen kann. Eine rekursive Funktion terminiert, wenn mit jedem Rekursionsschritt das Problem reduziert wird und sich in Richtung der Abbruchbedingung bewegt, d.h. dem Fall, in dem die Funktion sich nicht mehr selbst aufruft. So liefert beispielsweise die Fakultätsfunktion für das Argument 1 den Wert 1 zurück, ohne dass ein rekursiver Aufruf notwendig ist (Anmerkung für Mathematiker: Das Abbruchkriterium in einer rekursiven Funktion entspricht der Induktionsverankerung bei der vollständigen Induktion!). Eine rekursive Funktion kann in eine Endlosschleife führen, wenn das Abbruchkriterium nicht erreicht wird. Beispiel für die Fakultät: 4! = 4 * 3! 3! = 3 * 2! 2! = 2 * 1 Ersetzt man die ausgerechneten Werte in der jeweiligen Ursprungsgleichung, erhält man für 4! folgenden Ausdruck: 4! = 4 * 3 * 2 * 1 Ganz allgemein könnten wir sagen: Rekursion ist eine Methode in der Informatik, in der die Lösung für ein Problem auf die Lösung kleinerer Instanzen des Problems zurückgeführt wird. 15.3 Rekursive Funktionen in Python Als Beispiel für eine rekursive Funktion in Python wählen wir eine rekursive Implementierung der Fakultätsfunktion in Python. Man sieht, dass das Python-Skript ebenso elegant wie die mathematische Funktion ist: def factorial(n): if n == 1: return 1 else: return n * factorial(n-1) Wenn wir nachvollziehen wollen, wie die Funktion funktioniert, können wir zwei print()Funktionen ergänzen: 15.4 Die Tücken der Rekursion Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def factorial(n): print("factorial has been called with n = " + str(n)) if n == 1: return 1 else: res = n * factorial(n-1) print("intermediate result for ", n, " * factorial(" ,n-1, "): ",res) return res print(factorial(5)) Obiges Python-Skript liefert folgende Ausgaben: factorial has been called with n = 5 factorial has been called with n = 4 factorial has been called with n = 3 factorial has been called with n = 2 factorial has been called with n = 1 intermediate result for 2 * factorial( intermediate result for 3 * factorial( intermediate result for 4 * factorial( intermediate result for 5 * factorial( 120 1 2 3 4 ): ): ): ): 2 6 24 120 Die Fakultätsfunktion lässt sich natürlich auch iterativ schreiben: def iterative_factorial(n): result = 1 for i in range(2,n+1): result *= i return result 15.4 Die Tücken der Rekursion In diesem Abschnitt geht es um ein spezielles Problem bei der Rekursion. Ruft eine Funktion sich in einem Ausdruck wiederholt auf, also mehr als einmal, erhalten wir ein exponentielles Laufzeitverhalten. Am Beispiel der Fibonacci-Zahlen demonstrieren wir, wie dieses exponentielle Laufzeitverhalten zustande kommt. Die Fibonacci-Zahlen sind eine unendliche Zahlenfolge. Deshalb werden die FibonacciZahlen auch als Fibonacci-Folge bezeichnet. Bild 15.2 Fibonacci-Rechtecke und goldener Schnitt 135 136 15 Rekursive Funktionen Im Folgenden sehen Sie die ersten Glieder der Fibonacci-Folge: 0,1,1,2,3,5,8,13,21,34,55,89, ... Versuchen Sie doch einmal, das Konstruktionsprinzip dieser Folge zu erkennen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Es sollte hoffentlich nicht zu schwer gewesen sein. Jede Zahl, außer den beiden ersten, ergibt sich aus der Summe der beiden Vorgänger. Formale Definition der Fibonacci-Zahlen: Fn = Fn-1 + Fn-2 wobei gilt: F0 = 0 und F1 = 1 Die Fibonacci-Zahlen sind nach dem Mathematiker Leonardo von Pisa, besser bekannt als Fibonacci, benannt. In seinem Buch „Liber Abaci”, das im Jahre 1202 veröffentlicht wurde, führte er diese Folge als Übungsaufgabe ein. In dieser Übung geht es um Kaninchen. Seine Folge beginnt mit F1 = 1, während in modernen Mathematikbüchern und Büchern über Programmiersprachen die Folge meistens mit dem Wert für das Argument 0 startet, also F0 = 0. Dies ist aber gewissermaßen nur Geschmackssache, denn ob man bei 0 oder 1 startet, hat keine Auswirkungen auf die weiteren Folgeglieder. Die Fibonacci-Zahlen resultieren also aus einem „künstlichen” Kaninchenproblem, das die folgenden Bedingungen erfüllt: ■ ■ ■ Die Anfangspopulation wird von einem Kaninchenpaar gebildet. Ein neugeborenes Kaninchenpaar kann sich erst am Ende des ersten Monats paaren und wirft am Ende des zweiten Monates ein weiteres Paar. Ansonsten wirft jedes Kaninchenpaar jeweils ein weiteres Kaninchenpaar pro Monat. Sie sind unsterblich. Die Fibonacci-Zahlen geben also die Anzahl der Paare am Ende eines bestimmten Monats an. 15.5 Fibonacci-Folge in Python Nun kommen wir endlich wieder zu Python und den rekursiven Funktionen zurück. Die Fibonacci-Zahlen lassen sich sehr leicht als rekursive Python-Funktion realisieren. Die rekursive Python-Implementierung spiegelt ziemlich exakt die rekursive mathematische Funktion wider: def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) 15.5 Fibonacci-Folge in Python Eine iterative Funktion zur Berechnung der Fibonacci-Zahlen lässt sich ebenso leicht in Python bewerkstelligen, wie wir im folgenden Skript sehen können: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def fibi(n): a, b = 0, 1 for i in range(n): a, b = b, a + b return a Benutzt man die beiden Funktionen fib() und fibi(), stellt man sehr schnell fest, dass sie ein deutlich unterschiedliches Laufzeitverhalten an den Tag legen. Bei kleinen Werten, z.B. n = 10, 11 oder 15, hat man rein gefühlsmäßig, also ohne exakte Zeitmessung, den Eindruck, dass fibi() und fib() genauso schnell wären. Aber bei Werten ab n = 30 sieht es deutlich anders aus. fib() braucht nun sekundenlang, während fibi() weiterhin „sofort” mit dem Ergebnis da ist. Um dieses Gefühl mit soliden Messwerten zu untermauern, haben wir ein Skript geschrieben, mit dem wir die Zeiten messen können: from timeit import Timer from fibo import fib t1 = Timer("fib(10)","from fibo import fib") for i in range(1,41): s = "fib(" + str(i) + ")" t1 = Timer(s,"from fibo import fib") time1 = t1.timeit(3) s = "fibi(" + str(i) + ")" t2 = Timer(s,"from fibo import fibi") time2 = t2.timeit(3) print("n=%2d, fib: %8.6f, fibi: %7.6f, percent: %10.2f" % (i, time1, time2, time1/time2)) time1 ist die Zeit in Sekunden, die drei Aufrufe von fib(n) benötigen, und time2 ist entsprechend die Zeit für drei Aufrufe der iterativen Funktion fibi(). Wenn wir uns die Ergebnisse anschauen, sehen wir, dass fib(20) etwa 14 Millisekunden benötigt, während fibi(20) für drei Aufrufe gerade 0,011 Millisekunden benötigt. fib(20) ist also etwa 1300 Mal schneller als fibi(20). fib(40) braucht bereits 215 Sekunden, also mehr als 3,5 Minuten, während fibi(4) sich mit 0,016 Millisekunden begnügt. n= 1, n= 2, n= 3, n= 4, n= 5, n= 6, n= 7, n= 8, n= 9, n=10, fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: 0.000004, 0.000005, 0.000006, 0.000008, 0.000013, 0.000020, 0.000030, 0.000047, 0.000075, 0.000118, fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: 0.000005, 0.000005, 0.000006, 0.000005, 0.000006, 0.000006, 0.000006, 0.000008, 0.000007, 0.000007, percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: 0.81 1.00 1.00 1.62 2.20 3.36 5.04 5.79 10.50 16.50 137 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 138 15 Rekursive Funktionen n=11, n=12, n=13, n=14, n=15, n=16, n=17, n=18, n=19, n=20, n=21, n=22, n=23, n=24, n=25, n=26, n=27, n=28, n=29, n=30, n=31, n=32, n=33, n=34, n=35, n=36, n=37, n=38, n=39, n=40, fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: 0.000198, fibi: 0.000007, percent: 27.70 0.000287, fibi: 0.000007, percent: 41.52 0.000480, fibi: 0.000007, percent: 69.45 0.000780, fibi: 0.000007, percent: 112.83 0.001279, fibi: 0.000008, percent: 162.55 0.002059, fibi: 0.000009, percent: 233.41 0.003439, fibi: 0.000011, percent: 313.59 0.005794, fibi: 0.000012, percent: 486.04 0.009219, fibi: 0.000011, percent: 840.59 0.014366, fibi: 0.000011, percent: 1309.89 0.023137, fibi: 0.000013, percent: 1764.42 0.036963, fibi: 0.000013, percent: 2818.80 0.060626, fibi: 0.000012, percent: 4985.96 0.097643, fibi: 0.000013, percent: 7584.17 0.157224, fibi: 0.000013, percent: 11989.91 0.253764, fibi: 0.000013, percent: 19352.05 0.411353, fibi: 0.000012, percent: 34506.80 0.673918, fibi: 0.000014, percent: 47908.76 1.086484, fibi: 0.000015, percent: 72334.03 1.742688, fibi: 0.000014, percent: 123887.51 2.861763, fibi: 0.000014, percent: 203442.44 4.648224, fibi: 0.000015, percent: 309461.33 7.339578, fibi: 0.000014, percent: 521769.86 11.980462, fibi: 0.000014, percent: 851689.83 19.426206, fibi: 0.000016, percent: 1216110.64 30.840097, fibi: 0.000015, percent: 2053218.13 50.519086, fibi: 0.000016, percent: 3116064.78 81.822418, fibi: 0.000015, percent: 5447430.08 132.030006, fibi: 0.000018, percent: 7383653.09 215.091484, fibi: 0.000016, percent: 13465060.78 Was ist faul an unserer rekursiven Implementierung? Schauen wir uns den Berechnungsbaum an, d.h. die Reihenfolge, in der die Funktionsaufrufe erfolgen. Statt fib() schreiben wir allerdings vereinfachend f(): 15.5 Fibonacci-Folge in Python Wir können sehen, dass der Unterbaum zur Berechnung von f(2) (also fib(3)) dreimal auftaucht und der Unterbaum zur Berechnung von f(3) zweimal. Wenn man sich vorstellt, f(6) zu berechnen, erkennt man, dass f(4) zweimal aufgerufen wird, f(3) dreimal und so weiter. Das bedeutet, dass alle Berechnungen immer wieder durchgeführt werden, da bereits berechnete Werte nicht gespeichert werden. Sie müssen also immer wieder aufs Neue berechnet werden. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir können eine „Erinnerung” für unsere rekursive Version implementieren. Dazu nutzen wir ein Dictionary, in dem wir bereits berechnete Werte speichern: memo = {0:0, 1:1} def fibm(n): if not n in memo: memo[n] = fibm(n-1) + fibm(n-2) return memo[n] Diese Technik, also das Speichern von Ergebnisse zur späteren Wiederverwendung, bezeichnet man als Memoisation. Wir schauen uns wieder die benötigten Zeiten im Vergleich zu fibi() an: from timeit import Timer from fibo import fib t1 = Timer("fib(10)","from fibo import fib") for i in range(1,41): s = "fibm(" + str(i) + ")" t1 = Timer(s,"from fibo import fibm") time1 = t1.timeit(3) s = "fibi(" + str(i) + ")" t2 = Timer(s,"from fibo import fibi") time2 = t2.timeit(3) print("n=%2d, fib: %8.6f, fibi: %7.6f, percent: %10.2f" % (i, time1, time2, time1/time2)) Wir können sehen, dass unsere neue Funktionsversion sogar schneller ist als die iterative. Vor allem die großen Argumente profitieren besonders von der Memoisation: n= 1, n= 2, n= 3, n= 4, n= 5, n= 6, n= 7, n= 8, n= 9, n=10, n=11, fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: 0.000011, 0.000011, 0.000012, 0.000012, 0.000012, 0.000011, 0.000012, 0.000011, 0.000011, 0.000010, 0.000011, fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: 0.000015, 0.000013, 0.000014, 0.000015, 0.000016, 0.000017, 0.000017, 0.000018, 0.000018, 0.000020, 0.000020, percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: 0.73 0.85 0.86 0.79 0.75 0.65 0.72 0.61 0.61 0.50 0.55 139 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 140 15 Rekursive Funktionen n=12, n=13, n=14, n=15, n=16, n=17, n=18, n=19, n=20, n=21, n=22, n=23, n=24, n=25, n=26, n=27, n=28, n=29, n=30, n=31, n=32, n=33, n=34, n=35, n=36, n=37, n=38, n=39, n=40, fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: fib: 0.000004, 0.000004, 0.000004, 0.000004, 0.000003, 0.000004, 0.000004, 0.000004, 0.000003, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, 0.000004, fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: fibi: 0.000007, 0.000007, 0.000008, 0.000008, 0.000008, 0.000009, 0.000009, 0.000009, 0.000010, 0.000009, 0.000010, 0.000010, 0.000011, 0.000012, 0.000011, 0.000011, 0.000012, 0.000012, 0.000013, 0.000012, 0.000012, 0.000013, 0.000012, 0.000013, 0.000013, 0.000014, 0.000014, 0.000013, 0.000014, percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: percent: 0.59 0.57 0.52 0.50 0.39 0.45 0.45 0.45 0.29 0.45 0.40 0.40 0.35 0.33 0.34 0.35 0.32 0.33 0.31 0.34 0.33 0.30 0.34 0.31 0.31 0.29 0.29 0.31 0.29 Wenn Sie gerne wissen wollen, wie wir den rekursiven Ansatz in seiner „Reinheit” retten können, dann empfehlen wir das Kapitel 33.9 (Memoisation). 15.6 Aufgaben 1. Aufgabe: Schreiben Sie eine rekursive Version der Funktion f(n) = 3 * n, also eine Funktion, die die Vielfachen von 3 berechnet. Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 495 15.6 Aufgaben 2. Aufgabe: Schreiben Sie eine rekursive Python-Funktion, welche die Summe der ersten n ganzen Zahlen zurückliefert. (Hinweis: Diese Funktion sieht ähnlich wie die Fakultätsfunktion aus!) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 495 3. Aufgabe: Schreiben Sie eine Funktion, die das Pascalsche Dreieck implementiert: 1 1 1 1 1 1 1 2 3 4 5 1 3 1 6 4 1 10 10 5 1 Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 495 4. Aufgabe: Die Fibonacci-Zahlen verbergen sich auch innerhalb des Pascalschen Dreiecks. Wenn man die fett markierten Zahlen im folgenden Pascalschen Dreieck aufsummiert, erhält man die 7. Fibonacci-Zahl: 1 1 1 1 1 2 3 1 3 1 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 1 4 Schreiben Sie ein rekursives Programm, d.h. rekursive Funktion, die die FibonacciZahlen aus einem Pascalschen Dreieck heraus berechnet. Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 496 5. Aufgabe: Schreiben Sie eine rekursive Funktion in Python für das Sieb des Eratosthenes. Das Sieb des Eratosthenes ist ein einfacher Algorithmus zur Berechnung aller Primzahlen bis zu einer bestimmten natürlichen Zahl. Dieser Algorithmus geht auf den alten griechischen Mathematiker Eratosthenes zurück. Algorithmus zum Finden aller Primzahlen kleiner einer gegebenen natürlichen Zahl n: 1. Erzeuge eine Liste der natürlichen Zahlen von 2 bis n: 2, 3, 4, ..., n. 2. Starte mit einem Zähler i mit dem Wert 2, d.h. die erste Primzahl. 3. Beginnend mit i+i, inkrementiere jeweils um i und lösche alle so erhaltenen Zahlen aus der Liste, falls vorhanden, also die Zahlen 2*i, 3*i, 4*i, und so weiter. 141 142 15 Rekursive Funktionen 4. Finde die erste auf i folgende Zahl in der Liste. Dies ist die nächste Primzahl. 5. Setze i auf den im letzten Schritt gefundenen Wert. 6. Wiederhole die Schritte 3, 4 und 5, bis i größer als n ist. (Als Verbesserung: Es genügt, bis zur Quadratwurzel von n zu gehen) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 7. Alle Zahlen, die noch in der Liste sind, sind Primzahlen. Wenn wir uns den obigen Algorithmus genauer anschauen, sehen wir, dass wir ineffizient sind. So versuchen wir zum Beispiel, die Vielfachen von 4 zu entfernen, obwohl die bereits durch die Vielfachen von 2 entfernt worden sind. Man erkennt, dass es genügt, die Vielfachen der Primzahlen bis zu der Quadratwurzel von n zu entfernen. Diese Menge kann man rekursiv konstruieren. Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 496 6. Aufgabe: Schreiben Sie eine rekursive Funktion find_index(), die für einen Wert n den Index aus der Fibonacci-Folge zurückliefert, falls n eine Fibonacci-Zahl ist, und ansonsten -1 zurückliefert, also wenn n kein Element der Folge ist. Für eine Fibonacci-Zahl n gilt also: fib(find_index(n)) == n Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 497 7. Aufgabe: Die Summe der Quadrate zweier aufeinander folgender Fibonacci-Zahlen ist ebenfalls wieder eine Fibonacci-Zahl. Man sieht dies zum Beispiel bei 2 und 3, denn 2*2 + 3*3 ergibt 13, was Fib(7) entspricht. Benutzen Sie die Funktion aus der vorigen Aufgabe, um die Position der Summe der Quadrate aus zwei aufeinander folgenden Fibonacci-Zahlen in der Folge zu finden. Mathematische Erklärung: Seien a und b zwei aufeinander folgende Fibonacci-Zahlen. Die Fibonacci-Folge, die mit „a” startet, sieht wie folgt aus: 0 1 2 3 4 5 6 a b a + b a + 2b 2a + 3b 3a + 5b 5a + 8b Wir erkennen, dass die Fibonacci-Zahlen als Faktoren von a und b erscheinen. Das n-te Element dieser Folge kann mit folgender Formel berechnet werden: F(n) = Fib(n-1)* a + Fib(n) * b Daraus folgt, dass für eine natürliche Zahl n mit n > 1 Folgendes gilt: Fib(2*n + 1) = Fib(n)**2 + Fib(n+1)**2 Lösung: Lösungen zu Kapitel 15 (Rekursive Funktionen), Seite 498 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 16 16.1 Listen und Tupel im Detail Stapelspeicher Wir haben schon einiges über Listen erfahren, aber es fehlen noch einige wichtige Aspekte. So kann man eine Liste auch wie einen Stapelspeicher1 behandeln. In der Informatik bezeichnet ein Stapelspeicher (oder Stapel) – manchmal auch Kellerspeicher (Keller) genannt – eine Datenstruktur, die im Wesentlichen zwei Operationen besitzt: eine, mit der man Daten auf den Stapel legen kann, und eine, um das oberste Element vom Stapel wegzunehmen. Stellen Sie sich das wie einen Stapel noch zu lesender Bücher vor. Sie wollen die Bücher des Stapels von oben nach unten durchlesen. Kaufen Sie ein neues Buch, kommt es oben drauf und ist damit das nächste Buch, was gelesen werden „muss”. Bild 16.1 Bücherstapel In den Programmiersprachen werden hierfür meistens die folgenden Operationen zur Verfügung gestellt: ■ ■ ■ 1 2 push Mit dieser Methode legt man ein neues Objekt oben2 auf den Stapel. Diese Operation bezeichnet man im Deutschen als „einkellern”. Bei Python gibt es im Gegensatz zu vielen anderen Sprachen keine Methode mit dem Namen „push”, aber „append” erfüllt die gleiche Funktionalität. pop Diese Methode liefert das oberste Objekt eines Stapels zurück. Dabei wird das Objekt vom Stapel entfernt. Man bezeichnet dies im Deutschen als „auskellern”. peek Diese Methode dient zum Nachschauen, was zuoberst auf dem Stapel liegt. Dabei wird das Objekt aber nicht wie bei pop entfernt. In Python gibt es keine solche Methode. Bei Listen oder Tupel kann man sie jedoch mit einem Indexzugriff simulieren. Falls liste eine Liste bezeichnet, dann verhält sich liste[-1] wie eine Methode liste.peek(), die es aber in der list-Klasse nicht gibt. englisch: stack oder „rechts”, je nach Sichtweise 144 16 Listen und Tupel im Detail 16.2 ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ ■ Stapelverarbeitung in Python s.append(x) Hängt x ans Ende der Liste s an. Dies entspricht der Methode „push”, so wie sie sich in anderen Programmiersprachen findet. s.pop(i) Gibt das i-te Element von s zurück und entfernt es dabei aus der Liste. Normalerweise, also in anderen Sprachen, liefert pop nur das „oberste” bzw. das am weitesten rechts stehende Element zurück. s.pop() Ist i nicht angegeben, wird das letzte Element genommen, was dem „normalen” pop entspricht, wie es in anderen Programmiersprachen verwendet wird. Wie man „pop” und „append” anwendet, kann man dem folgenden Beispiel entnehmen. >>> colors=["red","green"] >>> colors.append("blue") >>> colors ['red', 'green', 'blue'] >>> c = colors.pop() >>> c 'blue' >>> colors ['red', 'green'] >>> colors.append(colors.pop()) >>> colors ['red', 'green'] 16.3 Bild 16.2 pop und append extend Die Methode „extend” dient dazu, an eine Liste mehrere Elemente anzuhängen. s.extend(t) Dabei muss das Argument „t” ein iterierbares Objekt sein. >>> >>> >>> [0, fib = [0,1,1,2,3,5] fib.extend([8,13,21]) fib 1, 1, 2, 3, 5, 8, 13, 21] Man beachte den Unterschied zu append: >>> >>> >>> [0, fib = [0,1,1,2,3,5] fib.append([8,13,21]) fib 1, 1, 2, 3, 5, [8, 13, 21]] 16.4 ,+’-Operator oder append Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Das Argument von extend() kann auch ein Tupel sein. Das Ergebnis bleibt gleich: >>> >>> >>> >>> >>> [0, >>> >>> >>> [0, t = (8,13,21) l = [8,13,21] fib = [0,1,1,2,3,5] fib.extend(t) print fib 1, 1, 2, 3, 5, 8, 13, 21] fib = [0,1,1,2,3,5] fib.extend(l) print fib 1, 1, 2, 3, 5, 8, 13, 21] Übergibt man einen String als Argument für extend, so wird der String in seine einzelnen Buchstaben zerlegt, die dann einzeln in die Liste eingefügt werden: >>> liste = [] >>> liste.extend("So what?") >>> liste ['S', 'o', ' ', 'w', 'h', 'a', 't', '?'] 16.4 ,+’-Operator oder append Außer append gibt es noch weitere Möglichkeiten, Elemente an eine Liste anzuhängen. So kann man beispielsweise ein oder mehrere Elemente mit dem „+“-Operator anhängen: >>> >>> >>> [3, L = [3,4] L = L + [42] L 4, 42] Was das Laufzeitverhalten betrifft, ist bei diesem Weg höchste Vorsicht geboten, wie wir im Folgenden sehen werden. Eine weitere Möglichkeit besteht in der Verwendung der erweiterten Zuweisung3 : >>> >>> >>> [3, L = [3,4] L += [42] L 4, 42] Logisch gesehen sind beide Vorgehensweisen gleichwertig, d.h. sie liefern die gleichen Ergebnisse. Im Folgenden wollen wir das Laufzeitverhalten dieser beiden und der appendMethode vergleichen. Wir messen die Laufzeit mittels des time-Moduls, auf das wir hier nicht weiter eingehen wollen. Für das Verständnis des Programms genügt es zu wissen, 3 engl. augmented assignment, compound assignment 145 146 16 Listen und Tupel im Detail dass time.time() eine Float-Zahl zurückliefert, die die Zeit in Sekunden seit „The Epoch”4 darstellt. Mit time.time() - start_time berechnen wir also die Zeit in Sekunden, die nötig war, die for-Schleifen zu berechnen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. import time n= 100000 start_time = time.time() l = [] for i in range(n): l = l + [i * 2] print(time.time() - start_time) start_time = time.time() l = [] for i in range(n): l += [i * 2] print(time.time() - start_time) start_time = time.time() l = [] for i in range(n): l.append(i * 2) print(time.time() - start_time) Dieses Programm liefert „erschreckende” Ergebnisse: 26.3175041676 0.0305399894714 0.0207479000092 Der „+”-Operator ist in diesem Lauf etwa 1268-mal langsamer als die append-Methode. Die Erklärung ist einfach: Bei der append-Methode wird in jedem Schleifendurchlauf einfach ein weiteres Element an die Liste angehängt. Im ersten Fall wird in jedem Schleifendurchgang, also mit jeder Zuweisung l = l + [i * 2], die komplette Liste kopiert, dann das neue Element an die kopierte Liste angehängt. Anschließend muss der Speicherplatz für die alte – nun nicht mehr benötigte – Liste freigegeben werden. Wir können auch sehen, dass die Benutzung der erweiterten Zuweisung im Vergleich zur ersten Methode nahezu genauso schnell ist wie der Weg mit append. Allerdings ist die erweiterte Methode dennoch etwas langsamer, da bei append das übergebene Objekt nur referenziert wird. 4 Bezeichnet das Datum 1. Januar 1970 und die Uhrzeit 00:00, als Beginn der Unix-Zeitzählung (ab UNIX Version 6) 16.5 Entfernen eines Wertes 16.5 Entfernen eines Wertes Mit der Methode „remove” kann man einen bestimmten Wert ohne Kenntnis des Indexes aus einer Liste entfernen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. s.remove(x) Dieser Aufruf entfernt das erste Vorkommen des Wertes x aus der Liste s. Falls x beim Aufruf nicht in der Liste vorhanden ist, gibt es einen ValueError. Im folgenden Beispiel rufen wir dreimal die remove-Methode auf, um die Farbe „green” zu entfernen. Da die Farbe nur zweimal in der Liste „colours” vorkommt, erhalten wir beim dritten Mal einen ValueError: >>> colours = ["red", "green", "blue", "green", "yellow"] >>> colours.remove("green") >>> colours ['red', 'blue', 'green', 'yellow'] >>> colours.remove("green") >>> colours ['red', 'blue', 'yellow'] >>> colours.remove("green") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list 16.6 Enthalten in Liste Wie wir bereits gesehen haben, kann man mit „el in liste” testen, ob ein Wert „el” in der Liste „liste” vorkommt. Man erhält als Ergebnis True, wenn „el” einmal oder mehrmals in der Liste vorkommt. Ist man an der genauen Anzahl interessiert, bietet sich die Methode „count” an. „count” liefert die Anzahl der Vorkommen eines Elements in einer Liste zurück. >>> colours = ["red", "green", "blue", "green", "yellow"] >>> colours.count("green") 2 >>> colours.count("black") 0 16.7 Positionsbestimmung Mit der Methode index kann man die Position eines Elements innerhalb einer Liste ermitteln. s.index(x[, i[, j]]) 147 148 16 Listen und Tupel im Detail Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Es wird der Index für das x ermittelt. Falls der optionale Parameter i gegeben ist, beginnt die Suche erst ab dieser Position und endet bei der Position j, falls j gegeben ist. Es erfolgt eine Fehlermeldung, falls x nicht in s vorkommt. >>> colours = ["red", "green", "blue", "green", "yellow"] >>> colours.index("green") 1 >>> colours.index("green", 2) 3 >>> colours.index("green", 3,4) 3 >>> colours.index("black") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: 'black' is not in list 16.8 Einfügen von Elementen Wir haben gesehen, dass wir mit append ein Element an das Ende einer Liste anhängen können. Aber es gibt keine Möglichkeit, mittels append ein Element an eine beliebige Stelle einer Liste einzufügen. Dazu gibt es die Methode „insert”: s.insert(index, object) Ein Objekt „object” wird in die Liste „s” eingefügt. Die früheren Elemente ab der Position index werden um eins nach rechts verschoben. >>> colours = ["red", "green", "blue", "yellow"] >>> colours.insert(1,"black") >>> colours ['red', 'black', 'green', 'blue', 'yellow'] Das Verhalten der Methode „append” lässt sich sehr einfach mit „insert” simulieren: >>> abc = ["a","b","c"] >>> abc.insert(len(abc),"d") >>> abc ['a', 'b', 'c', 'd'] 16.9 Besonderheiten bei Tupel Wir haben bereits erfahren, dass Tupel unveränderlich sind, d.h. man kann Elemente nicht entfernen, verändern oder neue Elemente anfügen. Das bedeutet, dass es die Methoden „pop”, „append” und „insert” für Tupel nicht gibt. Auch über den Index kann man ein Tupel nicht verändern, wie wir im Folgenden sehen: 16.9 Besonderheiten bei Tupel Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> t = (1,1,2,3,5,8,13) >>> t[0] = 42 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment 16.9.1 Leere Tupel Leere Tupel definiert man mit leeren runden Klammern: >>> t = () >>> type(t) <class 'tuple'> 16.9.2 1-Tupel Wie sieht es aber aus, wenn man ein Tupel mit nur einem Element definieren will? Runde Klammern werden auch in arithmetischen Ausdrücken zur Klammerung von Teilausdrücken verwendet. So sind beispielsweise ((a + b) * c) und (c) gültige Ausdrücke. Damit versteht man auch, dass „t = (5)” eine Integer-Zahl und nicht einen Einer-Tupel mit der Zahl 5 definiert: >>> t = (5) >>> type(t) <class 'int'> Tupel mit nur einem Element kann man nur mit einem „Trick” generieren. Hinter dem Wert muss ein Komma folgen. Dies sieht nicht schön aus, aber erfüllt seinen Zweck: >>> t = (5,) >>> type(t) <class 'tuple'> 16.9.3 Mehrfachzuweisungen, Packing und Unpacking Wir haben schon gesehen, dass es in Python eine Mehrfachzuweisung gibt, mit der man beispielsweise ideal Variablenwerte vertauschen kann: >>> x, y = 47, 11 >>> x, y = y, x >>> print(x,y) 11 47 Wir wollen nun zeigen, worin der Zusammenhang zwischen obigen Anweisungen und Tupeln besteht. 149 150 16 Listen und Tupel im Detail Die folgende Anweisung bezeichnet man als Tupel-Packing, da man die Zahlen 47 und 11 in ein Tupel packt: >>> t = "Philip", "Glass" >>> type(t) <class 'tuple'> Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Dies ist gewissermaßen eine Kurzschreibweise für >>> t = ("Philip", "Glass") Man spricht von Tupel-Unpacking oder Tupel-Entpacken, wenn man die einzelnen Werte eines Tupels Variablen zuordnet: >>> (first, last) = t >>> print(first, last) Philip Glass Es gibt auch viele Anwendungsfälle, bei denen man beispielsweise den ersten Wert der rechten Seite einer Variablen zuweisen will und den Rest als Liste einer weiteren Variablen. Dies lässt sich mit dem Sternoperator umsetzen: >>> first, *remainder = 35, 99,27, 18 >>> print(first) 35 >>> print(remainder) [99, 27, 18] Dies lässt sich auch analog mit der letzten Variablen bewerkstelligen. Natürlich auch in Kombination mit der ersten Variablen: >>> first, *middle, last = 35, 99,27, 18 >>> print(first) 35 >>> print(middle) [99, 27] >>> print(last) 18 >>> >>> name = "Joyce Carol Oates" >>> firstname, middlename, surname = name.split() >>> print(firstname) Joyce >>> print(middlename) Carol >>> print(surname) Oates 16.10 Die veränderliche Unveränderliche 16.10 Die veränderliche Unveränderliche Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir haben festgestellt, dass sich Tupel nicht verändern können! Folgender Code zeigt, dass sie es scheinbar doch können: >>> galileo = ([],) >>> galileo[0].append("Sie dreht sich doch!") >>> galileo (['Sie dreht sich doch!'],) Nein, keine Angst. Tupel sind unveränderlich, so wie sich die Sonne auch nur scheinbar um die Erde dreht. Im obigen Beispiel ist das erste und einzige Objekt ein Zeiger auf eine Liste. Die Liste wird dann mit der in-Place-Methode append verändert. Wir haben also die Liste innerhalb des Tupels und nicht das Tupel selbst verändert. Versucht man, dem ersten Element direkt einen neuen Wert zuzuweisen, erhält man wie erwartet einen Fehler: >>> galileo = ([],) >>> galileo[0] = "Sie dreht sich doch!" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment 16.11 16.11.1 Sortieren von Listen „sort” und „sorted” Zum Sortieren von Python-Listen gibt es zwei Möglichkeiten: zum einen die sortMethode der list-Klasse, die die Liste inplace sortiert, und zum anderen die eingebaute Funktion „sorted”, die aus einem Iterator-Objekt eine neue sortierte Liste baut. Schauen wir uns zunächst einmal „sort” an: >>> >>> >>> [7, liste = [32,7,12,20,8] liste.sort() liste 8, 12, 20, 32] Die Methode „sort” sortiert also direkt das Objekt und liefert kein Ergebnis zurück oder besser gesagt den speziellen Wert „None”. Ein beliebter Fehler besteht darin, Bild 16.3 Alles Strings 151 152 16 Listen und Tupel im Detail dass man glaubt, dass liste.sort() die sortierte Liste zurückgibt. Im folgenden Beispiel hat dann „liste” nur noch den Wert „None”, und die Liste ist verloren: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> liste = [32,7,12,20,8] >>> liste = liste.sort() >>> print(liste) None Die Funktion „sorted” hingegen liefert als Ergebnis die sortierte Liste zurück, und die Originalliste bleibt unverändert: >>> liste = [32,7,12,20,8] >>> x = sorted(liste) >>> x [7, 8, 12, 20, 32] >>> liste [32, 7, 12, 20, 8] 16.11.2 Umkehrung der Sortierreihenfolge Um die Sortierreihenfolge umzukehren, genügt es, sowohl bei „sort” als auch bei „sorted” den Schlüsselwortparameter „reverse” auf True zu setzen. Standardmäßig steht er auf False. >>> liste = [32,7,12,20,8] >>> x = sorted(liste, reverse=True) >>> x [32, 20, 12, 8, 7] >>> liste.sort(reverse=True) >>> liste [32, 20, 12, 8, 7] 16.11.3 Eigene Sortierfunktionen Manchmal kommt es vor, dass man eine Liste oder ein iterierbares Objekt nicht nach der Standard-Ordnungsrelation sortieren will. Für diesen Fall übergeben wir dem Schlüsselwortparameter „key” eine Funktionsreferenz auf eine Funktion, die als Argument den gleichen Typ wie die Listenelemente erwartet. Die Arbeitsweise kann man sich mathematisch wie folgt vorstellen: Bild 16.4 sort mit eigener Sortierabbildung 16.11 Sortieren von Listen Die zu sortierende Liste sieht wie folgt aus: l = [x 1 , x 2 , x 3 , . . . x n ] Sei nun f die an „key” übergebene Funktion, dann wird die ganze Liste mittels f in eine neue Liste abgebildet: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. [ f (x 1 ), f (x 2 ), f (x 3 ), f (x n )] Die neue Liste wird von Python standardmäßig sortiert, aber jedes Mal, wenn zwei Werte, sagen wir beispielsweise f (x i ) und f (x j ), vertauscht werden müssen, werden auch die entsprechenden Elemente in l vertauscht, also x i und x j . Wir demonstrieren nun die Arbeitsweise von „key” im folgenden Beispiel. Wenn wir eine Liste von Strings sortieren, erhalten wir keine alphabetische Sortierung, sondern eine, die sich nach der Codierung der Zeichen richtet. Das heißt, dass jeder Großbuchstabe bzgl. der Sortierreihenfolge kleiner ist als alle Kleinbuchstaben: >>> l = ["yellow", "Green", "blue", "Black", "red"] >>> sorted(l) ['Black', 'Green', 'blue', 'red', 'yellow'] Wären alle Farben in obigem Beispiel klein geschrieben, gäbe es das Problem nicht mit „Black” und „Green”. Dies führt uns zur Lösung: Wir bilden alle Strings mittels des Parameters „key” in Kleinbuchstaben ab: >>> l = ["yellow", "Green", "blue", "Black", "red"] >>> sorted(l, key=str.lower) ['Black', 'blue', 'Green', 'red', 'yellow'] Bei Python ist nicht für jeden Datentyp oder Klasse eine Ordnung definiert. So beispielsweise auch nicht für die komplexen Zahlen. Versucht man, eine Liste mit komplexen Zahlen zu sortieren, erhält man einen „TypeError”: >>> x = [ 3 + 4j, 2 - 1j, -4 -7j, 3 +2j] >>> x.sort() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unorderable types: complex() < complex() Übergibt man jedoch die Funktion „abs” an „key”, funktioniert das Sortieren, da „abs” die Länge eines Vektors berechnet, was einer Float-Zahl entspricht: >>> x = [ 3 + 4j, 2 - 1j, -4 -7j, 3 +2j] >>> x.sort(key=abs) >>> x [(2-1j), (3+2j), (3+4j), (-4-7j)] Im Folgenden wollen wir Listen mit Tupel oder anderen Listen als Elemente anhand einer bestimmten Komponente dieser Listen- oder Tupelelemente sortieren. Dazu müssen wir eine Funktion übergeben, die ein Tupel oder eine Liste als Argument hat und eine Komponente zurückliefert. 153 154 16 Listen und Tupel im Detail Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Sehr elegant kann man diese Aufgabe mit der lambda-Notation lösen, die wir aber erst in Kapitel lambda, map, filter und reduce, Seite 307 behandeln: >>> phone = [("John", "Miller", "4567"), ("Tina", "Baker", "4289"), (" Randy", "Steiner", "4103")] >>> sorted(phone, key=lambda x:x[0]) [('John', 'Miller', '4567'), ('Randy', 'Steiner', '4103'), ('Tina', ' Baker', '4289')] >>> sorted(phone, key=lambda x:x[1]) [('Tina', 'Baker', '4289'), ('John', 'Miller', '4567'), ('Randy', ' Steiner', '4103')] >>> sorted(phone, key=lambda x:x[2]) [('Randy', 'Steiner', '4103'), ('Tina', 'Baker', '4289'), ('John', ' Miller', '4567')] Will man die Aufgabe ohne Hilfe der lambda-Notation lösen, empfiehlt sich der Einsatz der Funktion „itemgetter” aus dem Modul „operator”. Die Funktion „itemgetter” wird mit einem Index „index” aufgerufen und liefert dann eine Funktion zurück, mit der man dann von einem beliebigen iterierbaren Objekt den Wert des Elements an der Position „index” zurückliefert. >>> from operator import itemgetter >>> colours = ["red", "green", "blue", "yellow", "pink", "brown"] >>> numbers = [10,15,21,33,5,8] >>> get_index1 = itemgetter(1) >>> get_index1(colours) 'green' >>> get_index1(numbers) 15 Natürlich bedarf es nicht des Umwegs, dass man sich einen extra Funktionsnamen für den i-ten Itemzugriff anlegt. Man kann dies auch direkt tun: >>> from operator import itemgetter >>> colours = ["red", "green", "blue", "yellow", "pink", "brown"] >>> numbers = [10,15,21,33,5,8] >>> itemgetter(1)(colours) 'green' >>> itemgetter(1)(numbers) 15 >>> itemgetter(4)(numbers) 5 >>> itemgetter(4)(colours) 'pink' 16.12 Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Nun können wir unser ursprüngliches Sortierproblem, was wir bereits in lambda-Notation gelöst hatten, auch mit der itemgetter-Funktion lösen: >>> from operator import itemgetter >>> phone = [("John", "Miller", "4567"), ("Tina", "Baker", "4289"), (" Randy", "Steiner", "4103")] >>> # nach dem 0-ten Index sortieren: ... >>> sorted(phone, key=itemgetter(0)) [('John', 'Miller', '4567'), ('Randy', 'Steiner', '4103'), ('Tina', ' Baker', '4289')] >>> # nach dem 1-ten Index sortieren: ... >>> sorted(phone, key=itemgetter(1)) [('Tina', 'Baker', '4289'), ('John', 'Miller', '4567'), ('Randy', ' Steiner', '4103')] >>> # nach dem 2-ten Index sortieren: ... >>> sorted(phone, key=itemgetter(2)) [('Randy', 'Steiner', '4103'), ('Tina', 'Baker', '4289'), ('John', ' Miller', '4567')] 16.12 Aufgaben 1. Aufgabe: Schreiben Sie eine Funktion, die die Reihenfolge einer Liste umkehrt. Verzichten Sie aber auf die Benutzung der Listen-Methode „reverse” und versuchen Sie stattdessen, nur die Methoden „pop” und „append” zu benutzen. >>> liste = ["a","b","c","d"] >>> rev(liste) ["d","c","b","a"] Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 487 2. Aufgabe: Schreiben Sie mithilfe der Methoden extend() und append() eine Funktion flatten(), die eine verschachtelte Liste (oder ein Tupel) in eine flache Liste überführt. type() wird auch benötigt. >>> flatten([(1,2), "Python", ["a",[1,7]], 1, 1.3]) [1, 2, 'Python', 'a', 1,7,1, 1.3] Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 487 155 156 16 Listen und Tupel im Detail 3. Aufgabe: Sortieren Sie eine Liste von Zahlen in alphabetischer Reihenfolge, d.h. das gilt: 1 < 10 < 111 < 19 < 2 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 488 4. Aufgabe: Schreiben Sie eine Funktion, die die Buchstabenhäufigkeit eines Strings bestimmt. Die Häufigkeiten sollen sortiert ausgegeben werden. Dabei sollen keine Sonderzeichen und Ziffern berücksichtigt werden. Beispiel: s = "Monty Python" x = letter_frequency(s) print x Ausgegeben wird: [('o', 2), ('n', 2), ('t', 2), ('y', 2), ('h', 1), ('m', 1), ('p', 1)] Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 488 5. Aufgabe: Gegeben ist eine Liste von Verkäufern mit ihren getätigten Verkäufen für ein bestimmtes Produkt. Die Liste besteht aus 4 Tupel der Art (Vorname, Nachname, Anzahl, Einzelpreis): [ ('John', 'Miller', 46, 18.67), ('Randy', 'Steiner', 48, 27.99), ('Tina', 'Baker', 53, 27.23), ('Andrea', 'Baker', 40, 31.75), ('Eve', 'Turner', 44, 18.99), ('Henry', 'James', 50, 23.56)] Sortieren Sie die Liste zuerst nach den Verkaufserlösen, also dem Produkt aus der Anzahl und dem Einzelpreis. Dann geben Sie die Liste nochmals nach den Nachnamen sortiert aus. Bei Gleichheit des Nachnamens sortieren Sie anhand des Vornamens weiter. Lösung: Lösungen zu Kapitel 16 (Listen und Tupel im Detail), Seite 488 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 17 17.1 Modularisierung Module Modulare Programmierung ist eine Software-Designtechnik, die auf dem allgemeinen Prinzip des modularen Designs beruht. Modulares Design ist ein Ansatz, der sich im Ingenieurwesen schon lange vor den ersten Computern als unausweichlich und unentbehrlich herausgestellt hat. Unter modularem Design versteht man, dass man ein komplexes System in kleinere selbständige Einheiten oder Komponenten zerlegt. Diese Komponenten bezeichnet man üblicherweise als Module. Ein Modul kann unabhängig vom Gesamtsystem erzeugt und separat getestet werden. In den meisten Fällen kann man ein Modul auch in anderen Systemen verwenden. Bild 17.1 Dateien Heutzutage gibt es kaum ein Produkt, das nicht auf Modularisierung beruht, so wie Autos, Mobiltelefone und so weiter. Computer gehören zu den Produkten, die bis zum Äußersten modularisiert sind. Das, was für die Hardware ein Muss ist, stellt auch für die Software, die auf ihr läuft, eine unvermeidliche Notwendigkeit dar. Wenn man Programme schreiben will, die lesbar, zuverlässig und ohne hohen Aufwand wartbar sind, geht es nicht ohne modulares Software-Design, insbesondere bei größeren Software-Projekten. Es gibt verschiedene Konzepte, um Programme modular zu gestalten. Der Ansatz der modularen Programmierung besteht darin, Programme systematisch in logische Teilblöcke, d.h. Module, aufzuspalten. Die Aufteilung eines Quelltexts in einzelne Teile (Module) bezeichnet man als Modularisierung. In Python unterscheiden wir zwei Arten von Modulen: ■ Bibliotheken (Libraries) Stellen Datentypen oder Funktionen für alle Python-Programme bereit. Es gibt: – die umfangreiche Standardbibliothek – eigene Module – Module von Drittanbietern ■ lokale Module nur für ein Programm verfügbar. 158 17 Modularisierung Ein Modul, manchmal auch als Bibliothek bezeichnet, egal ob aus der Standardbibliothek oder ein eigenes, wird mit der import-Anweisung eingebunden. Mit der folgenden Anweisung wird beispielsweise das math-Modul aus der Standardbibliothek importiert: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. import math Es stellt, wie der Name vermuten lässt, mathematische Funktionen und Konstanten zur Verfügung, so zum Beispiel die Konstante π (math.pi), die Sinus-Funktion (math.sin()) und die Cosinus-Funktion (math.cos())1 : >>> math.pi 3.141592653589793 >>> math.sin(math.pi/2) 1.0 >>> math.cos(math.pi/2) 6.123031769111886e-17 >>> math.cos(math.pi) -1.0 Nach dem Schlüsselwort import können auch mehrere durch Komma getrennte Modulnamen folgen: import math, random import-Anweisungen können an jeder Stelle des Quellcodes stehen, aber man sollte sie zugunsten der Übersichtlichkeit an den Anfang stellen. 17.1.1 Namensräume von Modulen Wird eine Bibliothek wie z.B. import math importiert, dann stehen die Namen der Bibliothek in einem eigenen Namensraum zur Verfügung. Auf die sin()-Funktion des math-Moduls kann man zunächst nur über den vollen Namen („fully qualified”) zugreifen, d.h. >>> math.sin(3.1415) 9.265358966049024e-05 sin() ohne Präfix math ist nicht bekannt und führt zu einer entsprechenden Fehlermeldung: >>> sin(3.1415) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'sin' is not defined 1 6.123031769111886e-17 ist natürlich „nahezu” Null, wie man es erwarten würde. Bei Rechnungen mit FloatWerten kommt es wie bei allen Programmiersprachen zu numerischen Ungenauigkeiten! 17.1 Module Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Möchte man gerne „bequem” auf Funktionen wie z.B. die Sinus-Funktion zugreifen können, also ohne das Präfix math., so kann man die entsprechenden Funktionen direkt importieren: >>> from math import sin, pi >>> print(pi) 3.141592653589793 >>> sin(pi/2) 1.0 >>> Die anderen Methoden der Bibliothek stehen dann nicht zur Verfügung – weder mit vollem Namen noch direkt. Man kann im obigen Fall nur auf sin und pi zugreifen. Man kann auch eine Bibliothek komplett in den globalen Namensraum einbinden. Dabei werden dann gegebenenfalls bereits vorhandene gleichlautende Namen überschrieben, wie dies im folgenden Beispiel dargestellt wird: >>> pi = 57.898 >>> print(pi) 57.898 >>> from math import * >>> print(pi) 3.141592653589793 17.1.2 Namensräume umbenennen Beim Import einer Bibliothek kann man auch einen neuen Namen für den Namensraum wählen. Im Folgenden importieren wir math als m. Dies führt bei der Benutzung des mathModuls zu einer deutlichen Schreiberleichterung, ohne dass die Vorteile eines Namensraums aufgegeben werden: >>> import math as m >>> m.pi 3.141592653589793 >>> m.sin(m.pi) 17.1.3 Modularten Es gibt verschiedene Modularten: ■ ■ ■ in Python geschriebene Modulendung: .py oder .pyc Dynamisch geladene C-Module Endung: .dll, .pyd, .so, .sl, usw. C-Module, die mit dem Interpreter gelinkt sind Um eine Liste dieser Module zu erhalten: 159 160 17 Modularisierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> import sys >>> print(sys.builtin_module_names) ('__main__', '_ast', '_bisect', '_codecs', '_collections', '_datetime ', '_elementtree', '_functools', '_heapq', '_io', '_locale', ' _pickle', '_posixsubprocess', '_random', '_socket', '_sre', ' _string', '_struct', '_symtable', '_thread', '_warnings', ' _weakref', 'array', 'atexit', 'binascii', 'builtins', 'errno', ' fcntl', 'gc', 'grp', 'imp', 'itertools', 'marshal', 'math', ' operator', 'posix', 'pwd', 'pyexpat', 'select', 'signal', 'spwd', 'sys', 'syslog', 'time', 'unicodedata', 'xxsubtype', 'zipimport', 'zlib') 17.1.4 Suchpfad für Module Wenn man ein Modul importiert, z.B. abc, sucht der Interpreter nach abc.py in der folgenden Reihenfolge: ■ im aktuellen Verzeichnis ■ PYTHONPATH ■ Falls PYTHONPATH nicht gesetzt ist, wird installationsabhängig im Default-Pfad gesucht, also unter Linux/Unix z.B. in /usr/lib/python3.5. sys.path enthält die Verzeichnisse, in denen Module gesucht werden: >>> import sys >>> for dir in sys.path: ... print(dir) ... /usr/lib/python35.zip /usr/lib/python3.5 /usr/lib/python3.5/plat-x86_64-linux-gnu /usr/lib/python3.5/lib-dynload /usr/local/lib/python3.5/dist-packages /usr/lib/python3/dist-packages >>> In der folgenden interaktiven Sitzung sehen wir, wie man herausfinden kann, wo sich ein zuvor geladenes Modul befindet: >>> import numpy >>> numpy.__file__ '/usr/lib/python3/dist-packages/numpy/__init__.py' >>> import math >>> math.__file__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'math' has no attribute '__file__' 17.1 Module Wie wir oben sehen, ist das __file__-Attribut nicht vorhanden, wenn es sich bei dem Modul um ein C-Modul handelt, welches statisch an den Interpreter gelinkt ist. 17.1.5 Inhalt eines Modules Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Mit der built-in-Funktion dir() kann man sich die in einem Modul definierten Namen ausgeben lassen: >>> import math >>> dir(math) ['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos ', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', ' copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', ' expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', ' gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', ' isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf ', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', ' tanh', 'trunc'] Ohne Argumente liefert dir() die definierten Namen des Namensraums, in dem man sich befindet: $ python3 Python 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >> import math >>> a = 42 >>> b = 4.5 >>> lst = [34, "Salu"] >>> dir() ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', ' __spec__', 'a', 'b', 'lst', 'math'] 17.1.6 Eigene Module Eigene Module zu schreiben, ist in Python extrem einfach. Viele tun es, ohne es zu wissen. Jedes Python-Programm ist automatisch auch ein Modul. Die beiden folgenden Funktionen fib(), die den n-ten Fibonacci-Wert zurückliefert, und die Funktion fiblist() werden in einer Datei fibonacci.py gespeichert: def fib(n): a, b = 0, 1 for i in range(n): a, b = b, a + b return a 161 162 17 Modularisierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def fiblist(n): fib = [0,1] for i in range(1,n): fib += [fib[-1]+fib[-2]] return fib Von einem anderen Programm oder von der interaktiven Shell kann man nun, falls fibonacci.py innerhalb des Suchpfads zu finden ist, die Datei mit den beiden FibonacciFunktionen als Modul aufrufen: >>> import fibonacci >>> fibonacci.fib(10) 55 >>> fibonacci.fiblist(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] >>> fibonacci.__name__ 'fibonacci' >>> 17.1.7 Dokumentation für eigene Module Auch wenn es so ist, wie wir im vorigen Abschnitt gesagt haben, dass jedes PythonProgramm automatisch auch ein Modul ist, so sollte jedes Modul dennoch über ausreichend Kommentare verfügen. Das pydoc-Modul erzeugt automatisch eine Dokumentation für jedes Modul. Rufen wir beispielsweise help auf unser fibonacci-Modul auf, erhalten wir folgende Ausgabe: Help on module fibonacci: NAME fibonacci FUNCTIONS fib(n) fiblist(n) FILE /home/data/bodenseo/python/fibonacci.py Wünschenswert wären jedoch noch allgemeine Informationen zum fibonacci-Modul und zu den einzelnen Methoden. Eine allgemeine Beschreibung des Moduls kann man in einem Docstring zu Beginn einer Moduldatei verfassen. Die Funktionen dokumentiert man wie üblich mit einem Docstring unterhalb der ersten Funktionszeile: 17.2 Pakete """ Modul mit wichtigen Funktionen zur Fibonacci-Folge """ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def fib(n): """ Iterative Fibonacci-Funktion """ a, b = 0, 1 for i in range(n): a, b = b, a + b return a def fiblist(n): """ produziert Liste der Fibo-Zahlen """ fib = [0,1] for i in range(1,n): fib += [fib[-1]+fib[-2]] return fib Die help-Ausgabe sieht nun zufriedenstellend aus: Help on module fibonacci: NAME fibonacci - Modul mit wichtigen Funktionen zur Fibonacci-Folge FUNCTIONS fib(n) Iterative Fibonacci-Funktion fiblist(n) produziert Liste der Fibo-Zahlen FILE /home/data/bodenseo/python/fibonacci.py 17.2 Pakete Beim Verfassen sprachlicher Dokumente steht natürlich der Inhalt selbst im Vordergrund, aber man wird diesen Inhalt wohl kaum an seine Leserinnen und Leser bringen können, wenn er nicht organisiert ist. Man gliedert in Bücher, Kapitel, Unterkapitel, Abschnitte und so weiter. Ähnliches gilt natürlich auch für PythonProgramme. Werden sie nicht ordentlich gegliedert, d.h. modularisiert, wird es schwer, sie Bild 17.2 Beispielpaket in der Übersicht 163 164 17 Modularisierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. zu verstehen und zu warten. Im vorigen Kapitel hatten wir Module kennengelernt, die sich hervorragend eignen, Programme zu strukturieren bzw. zu modularisieren. Was aber, wenn die Zahl unserer Module wächst? Wenn wir die Übersicht über unsere Module verlieren könnten? Für diesen Fall hat Python das Paketkonzept bereitgestellt. Wir können mehrere oder beliebig viele Module zu einem Paket „schnüren”. Der dazu erforderliche Mechanismus ist in Python wiederum denkbar einfach gelöst. 17.2.1 Einfaches Paket erzeugen Um ein Paket in Python zu erstellen, sind nur zwei Dinge zu tun: Man erzeugt einen Unterordner in einem Verzeichnis, in dem Python Module erwartet bzw. sucht. In diesem neu erzeugten Ordner muss man nun eine Datei mit dem Namen __init__.py anlegen. Die Datei kann leer sein oder Initialisierungscode enthalten, der beim Einbinden des Pakets einmalig ausgeführt wird. Pakete können außer Modulen auch Pakete enthalten. Im Folgenden zeigen wir nun anhand eines einfachen Beispiels, wie man ein Paket erzeugt. Unser Paket soll simple_package heißen. Dazu erzeugen wir einen Ordner2 mit dem Namen simple_package. In diesem Ordner legen wir eine leere Datei mit dem Namen __init__.py an3 . Außerdem legen wir noch zwei einfache Module in diesem Verzeichnis an: ein Modul namens „a.py” mit folgendem Inhalt def f1(): print("Hello, f1 from module 'a' calling") sowie ein weiteres Modul mit dem Name „b.py” und diesem Inhalt: def foo(): print("Hello, foo from module 'b' calling") Nun sind wir in der Lage, unser Paket zu benutzen: >>> from simple_package import a,b >>> a.f1() Hello, f1 from module 'a' calling >>> b.foo() Hello, foo from module 'b' calling Versucht man, das Paket simple_package direkt, also mittels „import simple_package” zu importieren, erhält man eine Fehlermeldung. Dies liegt daran, dass die Module in einem Paket nicht automatisch importiert werden: >>> import simple_package >>> simple_package.a.f1() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'module' object has no attribute 'a' 2 3 In Linux oder Unix können wir dies mit dem Befehl „mkdir simple_package” tun. In Linux kann man dies am einfachsten mit einem touch-Kommando machen: „touch __init.py__”. 17.2 Pakete Jetzt kann man das Initialisierungsverhalten von __init__.py ideal einsetzen. Wir fügen in diese bisher leere Datei die Zeile „from simple_package import a, b’’ ein und können nun obigen Versuch wiederholen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> import simple_package >>> simple_package.a.f1() Hello, f1 from module 'a' calling Wie wir gesehen haben, erhalten wir keine Fehlermeldung und können auch auf die Module wie gewünscht zugreifen. 17.2.2 Komplexeres Paket Im folgenden Beispiel zeigen wir den Aufbau eines umfangreicheren Pakets. Es handelt sich um ein hypothetisches sound-Modul.4 sound |-- effects | |-- echo.py | |-- __init__.py | |-- reverse.py | `-- surround.py |-- filters | |-- equalizer.py | |-- __init__.py | |-- karaoke.py | `-- vocoder.py |-- formats | |-- aiffread.py | |-- aiffwrite.py | |-- auread.py | |-- auwrite.py | |-- __init__.py | |-- wavread.py | `-- wavwrite.py `-- __init__.py Die gesamte Beispielstruktur mit den Dateien kann als zip-File von unserer Webseite heruntergeladen werden.5 Wenn wir das Paket „sound” mittels import sound importieren, wird nur das Paket sound, aber nicht die Unterpakete effects, filters und formats importiert, wie man im Folgenden sieht: 4 5 https://docs.python.org/3/tutorial/modules.html http://www.python-kurs.eu/buch/ 165 166 17 Modularisierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> import sound sound package is getting imported! >>> sound <module 'sound' from '/home/bernd/packages/sound/__init__.py'> >>> sound.effects Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'sound' has no attribute 'effects' Dies liegt daran, dass die __init__.py-Datei außer Strings für die Help-Funktionen und einem print keinen Code zum Nachladen von Unterpaketen enthält: """An empty sound package This is the sound package, providing hardly anything!""" print("sound package is getting imported!") Möchte man auch das Paket effects nutzen, so muss man es explizit mittels import sound.effects importieren: >>> import sound.effects effects package is getting imported! >>> sound.effects <module 'sound.effects' from '/home/bernd/packages/sound/effects/ __init__.py'> Man kann dies auch automatisch beim Import des sound-Moduls erledigen lassen, indem man die Codezeile import sound.effects in der Datei __init__.py im Verzeichnis sound aufnimmt. Die Datei sieht dann wie folgt aus: """An empty sound package This is the sound package, providing hardly anything!""" import sound.effects print("sound package is getting imported!") Importieren wir jetzt das Paket sound in der interaktiven Python-Shell, sehen wir, dass nun auch das Unterpaket effects automatisch geladen wird: >>> import sound effects package is getting imported! sound package is getting imported! 17.2 Pakete Statt mit einem absoluten Pfad hätte man das effects-Paket auch relativ zum sound-Paket importieren können: """An empty sound package Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. This is the sound package, providing hardly anything!""" from . import effects print("sound package is getting imported!") Es ist auch möglich, automatisch das Unterpaket formats nachzuladen, wenn wir effects importieren. Wir können auch dies mit einem relativen Import erledigen, den wir in die __init__.py-Datei des Verzeichnisses effects aufnehmen: from .. import formats Beim Import von sound werden dann die Unterpaket formats und effects automatisch geladen: >>> import sound formats package is getting imported! effects package is getting imported! sound package is getting imported! Als Letztes wollen wir nun zeigen, wie wir nur das Modul karaoke aus dem Paket filters bei dem Import des Moduls effects laden können. Dazu fügen wir noch die Zeile from ..filters import karaoke in die __init__.py-Datei des Verzeichnisses effects ein. Die Datei sieht dann wie folgt aus: """An empty effects package This is the effects package, providing hardly anything!""" from .. import formats from .. filters import karaoke print("effects package is getting imported!") Wenn wir nun sound importieren, erhalten wir folgende Ausgabe: >>> import sound formats package is getting imported! filters package is getting imported! Module karaoke.py has been loaded! effects package is getting imported! sound package is getting imported! Wir können nun auf die Funktionen von karaoke zugreifen: >>> sound.filters.karaoke.func1() Funktion func1 has been called! >>> 167 168 17 Modularisierung 17.2.3 Komplettes Paket importieren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. In diesem Unterkapitel benutzen wir wieder das sound-Beispiel des vorigen Kapitels. Wir fügen noch ein Modul foobar in unserem sound-Verzeichnis hinzu, d.h. eine Datei foobar.py.6 Wir werden nun demonstrieren, was passiert, wenn wir das sound-Paket mit * importieren. Man könnte sich vorstellen, dass Python automatisch alle Untermodule des Paketes importiert. Dies ist allerdings nur auf den ersten Blick wünschenswert, da es möglicherweise lange dauern könnte und vor allen Dingen, weil es zu ungewollten Seiteneffekten führen könnte. >>> from sound import * sound package is getting imported! >>> Wir erhalten die „beruhigende” Nachricht, dass das sound-Paket importiert worden ist. Schauen wir allerdings mit der dir-Funktion nach, was importiert worden ist, sehen wir, dass weder das Modul foobar nach die Unterpakete effect, filters und formats importiert worden sind: >>> dir() ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__ ', '__package__', '__spec__'] >>> Das automatische Nachladen kann erreicht werden, wenn der Autor eines Pakets einen Paketindex anlegt. Einen Paketindex legt man an, indem man in der __init__-Datei eine Liste mit dem Namen __all__ definiert. Die import-Anweisung folgt folgender Konvention: Die Liste __all__ wird als Liste der Modulnamen betrachtet, die importiert werden sollen, wenn ein Sternchen im Import verwendet worden ist, also from package import *. Man muss dann allerdings dafür Sorge tragen, dass diese Liste sich auf dem neuesten Stand befindet, wenn eine neue Version des Pakets veröffentlicht wird. Paket-Autoren brauchen aber keinesfalls eine solche __all__-Liste bereitzustellen, wenn sie keinen Sinn in der Verwendung des Sternchens für ihr Paket sehen. Die Datei sound/effects/__init__.py könnte beispielsweise folgende Code-Zeile enthalten: __all__ = ["formats", "filters", "effects", "foobar"] Importieren wir nun sound mittels *, erhalten wir folgende Ausgabe: >>> from sound import * formats package is getting imported! filters package is getting imported! effects package is getting imported! The module foobar is getting imported >>> 6 Das gesamte Paket kann als Datei sound4bar.tbz2 heruntergeladen werden. 17.2 Pakete Obwohl nach dem Ausdruck bereits alles klar sein sollte, können wir es dennoch wieder mit der Funktion dir testen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> dir() ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', ' __spec__', 'effects', 'filters', 'foobar', 'formats'] >>> Als nächste Frage stellt sich, was passiert, wenn wir in einem Unterpaket den SternchenOperator nutzen: >>> from sound.effects import * sound package is getting imported! effects package is getting imported! >>> dir() ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', ' __spec__'] >>> Wie erwartet wurden die Module innerhalb von effects nicht automatisch importiert. Deshalb fügen wir die folgende __all__-Liste der __init__-Datei des effects-Pakets hinzu: __all__ = ["echo", "surround", "reverse"] Nun erhalten wir das beabsichtigte Ergebnis: >>> from sound.effects import * sound package is getting imported! effects package is getting imported! Module echo.py has been loaded! Module surround.py has been loaded! Module reverse.py has been loaded! >>> >>> dir() ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', ' __spec__', 'echo', 'reverse', 'surround'] >>> 169 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 18 18.1 Globale und lokale Variablen Einführung Python behandelt globale und lokale Variablen auf eigenwillige Art. Während in vielen anderen Programmiersprachen Variablen automatisch als global gelten, wenn man sie nicht explizit als lokal deklariert hat, ist dies in Python genau anders herum. Die zugrunde liegende Idee besteht darin, dass die Benutzung von globalen Variablen generell als schlechter Programmierstil betrachtet wird, weil dadurch viele Fehler und Nebeneffekte auftreten können. In den meisten Fällen, in denen man versucht ist, eine globale Variable zu verwenden, kann man den gewünschten Effekt besser mittels ei- Bild 18.1 Mond und Erde nes Funktionsparameters realisieren oder durch die Rückgabe eines Werts mit der return-Anweisung. Wie auch in vielen anderen Fällen wird hier durch das Design von Python ein guter Programmierstil gewissermaßen erzwungen. Das bedeutet, dass jede Variable, die man innerhalb einer Funktion definiert, automatisch einen lokalen Gültigkeitsbereich hat. Und das heißt wiederum, dass egal was man mit dieser Variable innerhalb der Funktion anstellt, dies keinen Einfluss auf andere Variablen außerhalb der Funktion hat, auch wenn diese den gleichen Namen haben. Der Funktionsrumpf ist also der Gültigkeitsbereich einer solchen Variablen. Um keine Missverständnisse aufkommen zu lassen: Variablen müssen nicht deklariert werden, wie dies in anderen Sprachen wie C und Java üblich ist. Variablen werden in Python implizit deklariert, wenn man sie definiert, d.h. ihnen einen Wert zuweist. Eine Variable erhält automatisch den richtigen Datentyp. 172 18 Globale und lokale Variablen 18.2 Globale und lokale Variablen in Funktionen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im folgenden Beispiel zeigen wir, wie globale Variablen innerhalb des Funktionsrumpfs einer Funktion benutzt werden können, allerdings nur „lesend”, also ohne den Wert zu ändern: def f(): print(s) s = "I love Paris in the summer!" f() Die Variable s wird definiert, indem ihr die Zeichenkette „I love Paris in the summer!” zugeordnet wird. Diese Definition erfolgt vor dem Funktionsaufruf f(). Der Funktionsrumpf von f() besteht nur aus der „print(s)”-Anweisung. Weil es keine lokale Variable s gibt, d.h. keine Zuweisung an s innerhalb des Funktionsrumpfs von f, wird der Wert der globalen Variable s benutzt. Dieser Wert kann natürlich nicht verändert werden, wie wir in diesem Kapitel weiter unten sehen werden. Es wird also der String „I love Paris in the summer!” ausgegeben. Es stellt sich aber die Frage, was passiert, wenn wir den Wert von s innerhalb der Funktion von f() verändern. Wird dies eine Auswirkung auf die globale Variable s haben? Wir testen dies im folgenden kleinen Skript: def f(): s = "I love London!" print(s) s = "I love Paris!" f() print(s) Starten wir das Skript, erhalten wir folgende Ausgabe: $ python3 global_lokal2.py I love London! I love Paris! Wie sieht es aber aus, wenn wir das erste Beispiel mit dem zweiten Beispiel kombinieren, d.h. wenn wir also zuerst auf s mittels print zugreifen in der Hoffnung, den globalen Wert zu erhalten, und dann s einen neuen Wert zuweisen? Indem wir s einen Wert zuweisen könnten, machten wir s zu einer lokalen Variable. Dadurch gäbe es s innerhalb des Funktionsrumpfs sowohl als globale als auch als lokale Variable. Python lässt diese Mehrdeutigkeit nicht zu, und es kommt zu einer Fehlermeldung, wie wir im folgenden, entsprechend angepassten Beispiel sehen können: def f(): print(s) s = "I love London!" print(s) 18.2 Globale und lokale Variablen in Funktionen s = "I love Paris!" f() print(s) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Rufen wir das Programm auf, erhalten wir folgende Fehlermeldung: $ python3 global_lokal3.py Traceback (most recent call last): File "global_lokal3.py", line 7, in <module> f() File "global_lokal3.py", line 2, in f print(s) UnboundLocalError: local variable 's' referenced before assignment Eine Variable kann nicht sowohl lokal als auch global innerhalb des gleichen Blocks (hier der Funktionsrumpf) sein. Deshalb betrachtet Python s als eine lokale Variable innerhalb des Funktionsrumpfs. Da aber auf diese lokale Variable zugegriffen wird, bevor sie definiert worden ist (sie also zum Zeitpunkt der print-Anweisung noch keinen Wert erhalten hat), erfolgt die Fehlermeldung. Man kann jedoch auf globale Variablen „schreibend” innerhalb einer Funktion zugreifen. Dazu muss man sie jedoch explizit mittels des Schlüsselworts global als global deklarieren. Wir demonstrieren dies im folgenden Beispiel: def f(): global s print(s) s = "Zur Zeit nicht, aber Berlin ist auch toll!" print(s) s = "Gibt es einen Kurs in Paris?" f() print(s) Als Ausgabe erhalten wir: $ python3 global_lokal4.py Gibt es einen Kurs in Paris? Zur Zeit nicht, aber Berlin ist auch toll! Zur Zeit nicht, aber Berlin ist auch toll! Im folgenden Beispiel wollen wir nun noch zeigen, dass man auf lokale Funktionsvariablen von außerhalb nicht zugreifen kann: def f(): s = "I am globally not known" print(s) f() print(s) 173 174 18 Globale und lokale Variablen Wenn man dieses Skript startet, erhält man folgende Ausgabe mit Fehlermeldung: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. $ python3 global_lokal5.py I am globally not known Traceback (most recent call last): File "global_lokal5.py", line 6, in <module> print(s) NameError: name 's' is not defined Das folgende Beispiel zeigt eine wilde Kombination von lokalen und globalen Variablen und Funktionsparametern, um die obigen Sachverhalte nochmals per Beispiel zu vertiefen: def foo(x, y): global a a = 42 x,y = y,x b = 33 b = 17 c = 100 print(a,b,x,y) a,b,x,y = 1,15,3,4 foo(17,4) print(a,b,x,y Die Ergebnisse sollten mit den bisherigen Erläuterungen keine Überraschungen beinhalten: $ python3 global_lokal6.py 42 17 4 17 42 15 3 4 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 19 19.1 Alles über Strings . . . ... fast alles In den vorigen Kapiteln haben wir bereits Strings kennengelernt und einiges über sie erfahren. Nun möchten wir Ihnen weitere Details über Strings bieten, aber nicht alle, wie es der Titel andeutet. Hier geht es nur um Textstrings in Python und nicht um die aus der Physik, der Musik oder die Bademode. Die ersten Computer wurden entwickelt, um numerische Probleme zu lösen. Damals konnten sich viele noch nicht einmal vorstellen, dass man mit einem Computer nicht nur rechnen, sondern auch komplexe Textverarbeitung betreiben könnte. Schließlich stammt das Wort Computer auch vom lateinischen „computare”, was im Deutschen „berechnen” bedeutet. Wenn man nur allein an Suchmaschinen denkt, sieht man, dass heutzutage ein Großteil der Probleme auf Texten, also Strings basiert. Bild 19.1 Alles Strings So ist es nicht verwunderlich, dass Python insbesondere für den Datentyp string mächtige Werkzeuge bereitstellt, damit man mit Programmen automatisch Textdateien bearbeiten kann. Einen guten Überblick über die Klasse „str” können wir uns mit der help-Funktion verschaffen, also help(str). Mit dir(str) erhalten wir die Namen der vorhandenen Methoden und Attribute. Insgesamt immerhin 74: >>> dir(str) ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', ' __eq__', '__format__', '__ge__', '__getattribute__', '__getitem__ ', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', ' __new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', ' __rmul__', '__setattr__', '__sizeof__', '__str__', ' __subclasshook__', 'capitalize', 'center', 'count', 'encode', ' Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 176 19 Alles über Strings . . . endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', ' islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', ' isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', ' partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', ' rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', ' swapcase', 'title', 'translate', 'upper', 'zfill'] >>> len(dir(str)) 74 19.2 Aufspalten von Zeichenketten Nehmen wir an, wir haben einen String, der eine Zeile, einen Satz oder vielleicht ein ganzes Buch enthält. Um einen solchen String zu bearbeiten, ist es sehr häufig nötig, ihn in kleinere Einheiten zu zerlegen, z.B. in einzelne Wörter. Zerlegt man einen solchen String überall dort, wo Leerzeichen, Tabs oder ganz allgemeine, nicht druckbare Zeichen stehen, hat man eine Zerlegung in Wörter, wenn man davon absieht, dass an manchen Wörtern noch Satzzeichen wie Kommas oder Punkte „kleben”. Eine solche Funktionalität gibt es in fast allen modernen Programmiersprachen. Python bietet für diesen Zweck mehrere String-Methoden. ■ split([sep[, maxsplit]]) ■ rsplit([sep[, maxsplit]]) ■ splitlines([keepends]) ■ partition(sep) ■ rpartition(sep) 19.2.1 Bild 19.2 Zerlegung von Strings split Die Methode split kann ohne Parameter aufgerufen werden. Wir demonstrieren die Arbeitsweise von split mit einer bissigen Definition von Ambrose Bierce1 : >>> mammon = "The god of the world's leading religion. The chief temple is in the holy city of New York." 1 aus „The Devil’s Dictionary” (Des Teufels Wörterbuch) von Ambrose Bierce 19.2 Aufspalten von Zeichenketten Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> mammon.split() ['The', 'god', 'of', 'the', "world's", 'leading', 'religion.', 'The', 'chief', 'temple', 'is', 'in', 'the', 'holy', 'city', 'of', 'New', 'York.'] Man kann erkennen, dass split den String in einzelne Komponenten aufgespalten und dafür Leerzeichen als Trenner verwendet hat. Was man nicht erkennen kann, ist eine andere Eigenschaft des Default-Verhaltens von split. Es werden auch Teilstrings, die nur aus Leerzeichen, Zeilenvorschub (’\n’), Tabs (’\t’) oder anderen, sogenannten Whitespaces (also nicht druckbaren Zeichen) bestehen, zu einer Trennstelle zusammengefasst: >>> mammon = "The god \t \t of the world's \t leading religion. \n\r The chief temple is in the holy city of New York." >>> mammon.split() ['The', 'god', 'of', 'the', "world's", 'leading', 'religion.', 'The', 'chief', 'temple', 'is', 'in', 'the', 'holy', 'city', 'of', 'New', 'York.'] Die split-Methode verfügt noch über einen optionalen Parameter „sep”2 . Mit diesem Parameter können wir das Standardverhalten von split ändern, d.h. ein neues Trennzeichen definieren. Folgende Adressen mit Telefonnummer3 könnten aus einer Excel-Datei stammen und sollen nun in ihre einzelnen Komponenten zerlegt werden. Frank;Meyer;Radolfzell;07732/43452 Peter;Rabe;Konstanz;07531/70021 Ottmar;Huber;Rosenheim;08031/7877-0 Anna;Rabe;Radolfzell;07732/2343 Oskar;Lindner;Konstanz;07531/890 Anna;List;München;089/3434544 Anna;list;München;089/3434544 Franziska;Huber;Rosenheim;08031/787878 Sarah;Rabe;Konstanz;07531/343454 Die Daten sind in der Datei „adressen.txt” gespeichert. Das folgende Python-Programm4 liest diese Datei Zeile für Zeile ein, spaltet die Zeilen an den Strichpunkten und gibt die Zeilen in der Form „Nachname, Vorname, Ort, Telefonnummer” wieder aus: fh = open("addresses.txt", encoding="iso-8859-1") for address in fh: address = address.strip() (lastname, firstname, city, phone) = address.split(';') print(firstname + " " + lastname + ", " + city + ", " + 2 3 4 phone) eine Abkürzung für separator Mögliche Ähnlichkeiten mit real existierenden Personen bzw. Telefonnummern sind rein zufällig und nicht beabsichtigt. Sie finden es unter dem Namen „split_addresses.py” in unserem Beispielverzeichnis. 177 178 19 Alles über Strings . . . Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Das Programm liefert folgende Ausgabe: bernd@saturn:~/bodenseo/python/beispiele$ python3 split_addresses.py Meyer Frank, Radolfzell, 07732/43452 Rabe Peter, Konstanz, 07531/70021 Huber Ottmar, Rosenheim, 08031/7877-0 Rabe Anna, Radolfzell, 07732/2343 Lindner Oskar, Konstanz, 07531/890 List Anna, München, 089/3434544 List Anna, München, 089/3434544 Huber Franziska, Rosenheim, 08031/787878 Rabe Sarah, Konstanz, 07531/343454 bernd@saturn:~/bodenseo/python/beispiele$ Nehmen wir an, dass wir lediglich den Vornamen und den Nachnamen aus den Zeilen der obigen Datei benötigen. Dann ist die Aufspaltung zwischen Ort und Telefonnummer unnötig. Für diesen Zweck stellt die Methode split den optionalen Parameter „maxsplit” zur Verfügung. Der Wert von „maxsplit” bestimmt, wie oft ein String aufgespalten werden soll, beginnend von links: >>> s = "red,green,blue,yellow,pink,brown,black,white" >>> for i in range(1,8): ... x = s.split(",",i) ... print(len(x), x) ... 2 ['red', 'green,blue,yellow,pink,brown,black,white'] 3 ['red', 'green', 'blue,yellow,pink,brown,black,white'] 4 ['red', 'green', 'blue', 'yellow,pink,brown,black,white'] 5 ['red', 'green', 'blue', 'yellow', 'pink,brown,black,white'] 6 ['red', 'green', 'blue', 'yellow', 'pink', 'brown,black,white'] 7 ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black,white'] 8 ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white '] 19.2.2 Standardverhalten und „maxsplit” Man möchte gerne die Anzahl der Aufsplittung begrenzen, aber gleichzeitig will man die Standardeinstellung für den Trenner erhalten. Dies ist eine Problemstellung, die häufig auftritt und häufig falsch gelöst wird: Viele kommen zuerst auf folgende Idee, die sich aber sofort als falsch erweist. Man versucht, „maxsplit” als Schlüsselwortparameter zu benutzen, aber die Fehlermeldung weist einen darauf hin, dass split keine Schlüsselwortparameter kennt: >>> s = "red\tgreen blue\nyellow \t pink brown" >>> s.split(maxsplit=2) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: split() takes no keyword arguments 19.2 Aufspalten von Zeichenketten Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Eingabe für den Parameter einfach leer zu lassen und zu hoffen, dass Python dann automatisch den Default-Wert einsetzt, funktioniert auch nicht. Dies ist übrigens nie möglich, also auch nicht in anderen Methoden und Funktionen: >>> s = "red\tgreen blue\nyellow \t pink brown" >>> s.split(,2) File "<stdin>", line 1 s.split(,2) ^ SyntaxError: invalid syntax Die nächste „Lösung” funktioniert zwar syntaktisch, löst aber unser Problem nur teilweise. Man setzt an die erste Stelle für den Separator ein Leerzeichen ein: >>> s = "red green blue yellow pink brown" >>> s.split(" ",2) ['red', 'green', 'blue yellow pink brown'] >>> s = "red green blue yellow pink brown" >>> s.split(" ",2) ['red', '', 'green blue yellow pink brown'] >>> s = "red\tgreen blue\nyellow \t pink brown" >>> s.split(" ",2) ['red\tgreen', 'blue\nyellow', '\t pink brown'] Man sieht, dass es nur im ersten Fall funktioniert hatte, d.h. wenn alle Wörter nur durch genau ein Leerzeichen getrennt sind. Gibt es mehrere Leerzeichen, Tab-Zeichen oder ein Newline-Zeichen, funktioniert es nicht mehr. Nun kommen wir endlich zur korrekten Lösung. Will man keinen Parameter angeben und stattdessen den Default-Wert nehmen, dann gibt man an der entsprechenden Position einfach den speziellen Wert „None” ein. Damit können wir alle obigen Beispiele korrekt aufspalten: >>> s = "red green blue yellow pink brown" >>> s.split(None,2) ['red', 'green', 'blue yellow pink brown'] >>> s = "red green blue yellow pink brown" >>> s.split(None,2) ['red', 'green', 'blue yellow pink brown'] >>> s = "red\tgreen blue\nyellow \t pink brown" >>> s.split(None,2) ['red', 'green', 'blue\nyellow \t pink brown'] 19.2.3 rsplit rsplit ohne den Parameter „maxsplit” zu betrachten, macht wenig Sinn. rsplit spaltet einen String von rechts beginnend auf. Gibt man für maxsplit keinen Parameter an, sind die Ergebnisse von split und rsplit nicht unterscheidbar. 179 180 19 Alles über Strings . . . >>> s.split(",") ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white'] >>> s.rsplit(",") ['red', 'green', 'blue', 'yellow', 'pink', 'brown', 'black', 'white'] Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Ganz anders sieht es aus, wenn wir mit „maxsplit” arbeiten. Zur Demonstration des Unterschieds wiederholen wir das vorletzte Beispiel mit rsplit: >>> s = "red,green,blue" >>> s.rsplit(",") ['red', 'green', 'blue'] >>> for i in range(1,3): ... x = s.rsplit(",",i) ... print(len(x), x) ... 2 ['red,green', 'blue'] 3 ['red', 'green', 'blue'] Es bringt einen großen Zeitvorteil, konsequent „maxsplit” zu benutzen, wenn man nur wenige Elemente eines Strings von links oder rechts braucht. Im folgenden Programm sammeln wir jeweils das erste Wort von jeder Zeile des Romans „Ulysses”. In der Funktion „splitter_total” splitten wir jeweils die komplette Zeile, während wir in der Funktion „splitter_maxsplit” nur das erste Wort abspalten: import time def splitter_total(): fh = open("ulysses.txt") res = [] for line in fh: x = line.split() if len(x): res.append(x[0]) return res def splitter_maxsplit(): fh = open("ulysses.txt") res = [] for line in fh: x = line.split(None, 1) if len(x): res.append(x[0]) return res for i in range(5): t1 = time.time() splitter_total() t1 = time.time() - t1 19.2 Aufspalten von Zeichenketten t2 = time.time() splitter_maxsplit() t2 = time.time() - t2 print(t1, t2, t1/t2) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Starten wir das Programm, erkennen wir aus der Ausgabe, dass die Version mit „maxsplit” ziemlich stabil in jedem Lauf um etwa den Faktor 1,5 schneller ist: bernd@saturn:~/bodenseo/python/beispiele$ python3 splitter_test.py 0.06152200698852539 0.03992414474487305 1.5409724461643197 0.06169414520263672 0.04128098487854004 1.4944930549539404 0.059509992599487305 0.039869070053100586 1.4926355824258624 0.06016898155212402 0.04076218605041504 1.476098006071276 0.05954909324645996 0.03962898254394531 1.502665206718968 19.2.4 Folge von Trennzeichen Wir hatten gezeigt, dass mehrere Leerzeichen, Tabs usw. beim Standardverhalten von split und rsplit als ein einziger Trenner aufgefasst werden. Wie sieht es aber aus, wenn man den Trenner explizit als Parameter angibt? >>> s = "red green blue yellow pink brown" >>> s.split() ['red', 'green', 'blue', 'yellow', 'pink', 'brown'] >>> s.split(" ") ['red', '', '', '', 'green', '', 'blue', 'yellow', 'pink', 'brown'] >>> s = "red;;;green;blue;;yellow;pink;brown" >>> s.split(";") ['red', '', '', 'green', 'blue', '', 'yellow', 'pink', 'brown'] Wir sehen, dass das Zusammenfassen mehrerer Trenner zu einem Trenner nur beim Standardtrenner von split funktioniert. Dieses Verhalten ist sinnvoll, wenn man beispielsweise an Dateien im CSV-Format5 denkt. Befinden sich zwischen zwei Trennern keine Zeichen, so heißt dies, dass dieses Datenfeld leer ist. Im folgenden Listing fehlt in der zweiten Zeile der Vorname, in der vierten Zeile der Ort und in der fünften Zeile die Telefonnummer: Frank;Meyer;Radolfzell;07732/43452 ;Rabe;Konstanz;07531/70021 Ottmar;Huber;Rosenheim;08031/7877-0 Anna;Rabe;;07732/2343 Oskar;Lindner;Konstanz; 5 Der Name des Dateiformats CSV steht für „comma-separated values”. Es handelt sich um ein Datenaustauschformat, in dem häufig Kommas zur Trennung von Datenfeldern (Spalten) in einer Zeile verwendet werden. Statt Kommas können natürlich auch andere Zeichen wie beispielsweise ein Strichpunkt zur Trennung benutzt werden. Die Dateinamenserweiterung lautet üblicherweise .csv. 181 182 19 Alles über Strings . . . Anna;List;München;089/3434544 Anna;List;München;089/3434544 Franziska;Huber;Rosenheim;08031/787878 Sarah;Rabe;Konstanz;07531/343454 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wenden wir split(";") darauf an, erhalten wir jeweils Listen der Länge 4. Fehlende Einträge sind mit leerem String markiert. Mit dem Programm „split_addresses_4.py” können wir dieses Verhalten überprüfen: fh = open("addresses2.txt", encoding="iso-8859-1") for address in fh: address = address.strip() elements = address.split(';') print(elements) Starten wir dieses Skript, erhalten wir die erwartete Ausgabe: bernd@saturn:~/bodenseo/python/beispiele$ python3 split_addresses_4.py ['Frank', 'Meyer', 'Radolfzell', '07732/43452'] ['', 'Rabe', 'Konstanz', '07531/70021'] ['Ottmar', 'Huber', 'Rosenheim', '08031/7877-0'] ['Anna', 'Rabe', '', '07732/2343'] ['Oskar', 'Lindner', 'Konstanz', ''] ['Anna', 'List', 'München', '089/3434544'] ['Anna', 'list', 'München', '089/3434544'] ['Franziska', 'Huber', 'Rosenheim', '08031/787878'] ['Sarah', 'Rabe', 'Konstanz', '07531/343454'] bernd@saturn:~/bodenseo/python/beispiele$ 19.2.5 splitlines splitlines ist eine String-Methode, die einen Text mit Zeilenbegrenzern – also „\n” unter Unix, „\r” beim Mac und „\r\n” unter Windows – in eine Liste von Zeilen zerlegt. Zeilenenden können auch von unterschiedlichen Betriebssystemen in einem String gemischt sein, und splitlines erkennt sie als Zeilenenden. Im folgenden Beispiel wird „\r\n” korrekterweise als Windows-Zeilenende erkannt, und „\n\r” hinter line2 wird als Unix- und als MacZeilenende erkannt, d.h. nach line2 kommt also eine Leerzeile, die als leerer String in die Ergebnisliste eingeht: >>> s = "line1\nline2\n\rline3\r\nline4\rline5\n" >>> s.splitlines() ['line1', 'line2', '', 'line3', 'line4', 'line5'] >>> s.splitlines(True) ['line1\n', 'line2\n', '\r', 'line3\r\n', 'line4\r', 'line5\n'] 19.3 Zusammenfügen von Stringlisten mit join 19.2.6 partition Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Mit der Methode „partition” wird ein String an der ersten Stelle von links nach rechts aufgespaltet, an der der Trennstring „sep” steht. Das Ergebnis ist ein Tupel, dessen erste Komponente der Teilstring vor dem sep ist, die zweite Komponente der String sep ist und die dritte Komponente der nach dem Vorkommen von sep stehende Teilstring: >>> spruch = "Eine Katze, die jagt, hungert nicht!" >>> spruch.partition(",") ('Eine Katze', ',', ' die jagt, hungert nicht!') >>> spruch.rpartition(",") ('Eine Katze, die jagt', ',', ' hungert nicht!') 19.3 join join ist eine String-Methode: s.join(iterable) Zurückgeliefert wird die Konkatenation der Elemente des iterierbaren Objekts „iterable”. Die Elemente von „iterable” werden dabei so zusammengefügt, dass zwischen den Elementen jeweils der String „s” eingefügt wird. „s” ist dabei das String-Objekt, auf das die Methode „join” angewendet wird. >>> x = ["Python","Perl","Java"] >>> "-".join(x) 'Python-Perl-Java' >>> x = "48772" >>> ".".join(x) '4.8.7.7.2' 19.4 Suchen von Teilstrings Für die Suche von Teilstrings gibt es verschiedene Möglichkeiten in Python: 19.4.1 „in” oder „not in” „in” ist ideal, um die reine Existenz eines Substrings zu testen, wenn man nicht daran interessiert ist zu wissen, wo der Unterstring vorkommt: >>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What a good boy am I!" ' 183 184 19 Alles über Strings . . . >>> "corner" in rhyme True >>> "corner" not in rhyme False Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 19.4.2 s.find(substring[, start[, end]]) Mit der Stringmethode „find” kann man prüfen, ob ein String „substring” in einem String „s” vorkommt. „find” liefert eine -1 zurück, wenn „substring” nicht vorkommt; ansonsten liefert sie die Position zurück, ab der „substring” in „s” beginnt: >>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What a good boy am I!" ' >>> rhyme.find("orner") 13 >>> rhyme.find("Jill") -1 Gefunden wird das „orner” in dem Wort „Horner”. Der Name „Jill” kommt nicht in unserem String vor, deshalb liefert „find” ein -1 zurück. Mit den optionalen Parametern „start” und „end” kann man die Suche innerhalb des String „s” einschränken. Wird nur ein Wert für „start” angegeben, wird in dem Teilstring s[start:] gesucht. Wird sowohl ein Wert für „start” und „end” angegeben, dann wird in dem Teilstring s[start:end] gesucht. Wir verdeutlichen dies in folgendem Beispiel: >>> >>> 3 >>> 3 >>> -1 19.4.3 s = "annxbny" s.find("x") s.find("x",2) s.find("x",2,3) s.rfind(substring[, start[, end]]) Die Stringmethode „rfind” funktioniert analog zu „find”, allerdings erfolgt die Suche von rechts, also vom Ende des Strings: >>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What a good boy am I!" ' >>> rhyme.rfind("orner") 29 19.4 Suchen von Teilstrings 19.4.4 s.index(substring[, start[, end]]) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Funktioniert genau wie „find”, außer wenn „substring” nicht in „s” vorkommt. In diesem Fall wird ein Ausnahmefehler generiert: >>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What a good boy am I!" ' >>> rhyme.index("orner") 13 >>> rhyme.index("orner") == rhyme.find("orner") True >>> rhyme.index("Jill") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: substring not found 19.4.5 s.rindex(substring[, start[, end]]) Funktioniert analog zu „index” und „rfind”. >>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What a good boy am I!" ' >>> rhyme.rindex("orner") 29 >>> rhyme.rindex("Jill") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: substring not found 19.4.6 s.count(substring[, start[, end]]) Zählt die Vorkommen eines Teilstrings „substring” in s. „start” und „end” verhalten sich genau wie bei „find” und „index”: >>> rhyme = 'Little Jack Horner sat in a corner,\nEating a Christmas pie;\nHe put in his thumb and pulled out a plum,\nAnd said, "What a good boy am I!" ' >>> rhyme.count("Jill") 0 >>> rhyme.count("orner") 2 >>> rhyme.count("Horner") 1 >>> rhyme.count("in") 3 185 186 19 Alles über Strings . . . 19.5 Suchen und Ersetzen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wenn man mit Texten arbeitet, besteht eine häufige Aufgabe darin, dass man eine Zeichenkette durch eine andere ersetzen will bzw. muss. Dies gilt auch für Bereiche, die nichts mit dem Programmieren zu tun haben: so beispielsweise beim Erstellen von Briefen oder Rechnungen. Man benutzt einen alten Brief und tauscht nur Name und Adresse des Empfängers aus. Aber zurück zu Python und zum Programmieren. Zum Ersetzen eines Teilstrings „old” in einem String „s” durch einen anderen Teilstring „new” gibt es die Methode replace: s.replace(old, new[, count]) Ohne Angabe des optionalen Parameters „count” werden alle Vorkommen von old durch new ersetzt. Mit dem optionalen Parameter „count” kann man steuern, wie viele Vorkommen von old durch new ersetzt werden sollen: >>> ch = "Drei Chinesen mit dem Kontrabass" >>> ch.replace("e","a") 'Drai Chinasan mit dam Kontrabass' >>> ch.replace("e","a").replace("i","a") 'Draa Chanasan mat dam Kontrabass' >>> ch.replace("e","a").replace("i","a").replace("o","a") 'Draa Chanasan mat dam Kantrabass' 19.6 Kleinbuchstaben und Großbuchstaben Man kann einen String mithilfe der Stringmethode „upper” komplett in Großbuchstaben wandeln. Ebenso kann man einen String mithilfe der Stringmethode „lower” komplett in Kleinbuchstaben wandeln: >>> ch = "Drei Chinesen mit dem Kontrabass" >>> ch.lower() 'drei chinesen mit dem kontrabass' >>> ch.upper() 'DREI CHINESEN MIT DEM KONTRABASS' 19.7 capitalize und title „capitalize” ist eine Funktion, die alle Buchstaben außer dem Anfangsbuchstaben eines Strings in Kleinbuchstaben wandelt. Der Anfangsbuchstabe wird in einen Großbuchstaben gewandelt, falls es sich um einen Kleinbuchstaben handelt. 19.8 Stripping Strings Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. „title” wandelt alle Buchstaben, die nicht Anfangsbuchstaben eines Wortes sind, in Kleinbuchstaben und alle Anfangsbuchstaben eines Wortes in einen Großbuchstaben um. >>> ch = "Drei Chinesen mit dem Kontrabass" >>> ch.capitalize() 'Drei chinesen mit dem kontrabass' >>> ch.title() 'Drei Chinesen Mit Dem Kontrabass' >>> ch = "Drei Chinesen mit dem kONTRABASS" >>> ch.title() 'Drei Chinesen Mit Dem Kontrabass' 19.8 Stripping Strings Häufig kommt es vor, dass unerwünschte Zeichen am Anfang oder am Ende eines Strings stehen. Meistens sind es „Whitespaces“ wie Zeilenende, Tabs usw., die auf der rechten Seite eines Strings stehen. Um diese Zeichen loszuwerden, gibt es die Stringmethoden „strip”, „lstrip” und „rstrip”. ■ ■ ■ s.strip([chars]) Unerwünschte Zeichen werden auf beiden Seiten des Strings entfernt. s.lstrip([chars]) Unerwünschte Zeichen werden nur auf der linken Seite des Strings entfernt. s.rstrip([chars]) Unerwünschte Zeichen werden nur auf der rechten Seite des Strings entfernt. >>> s = " \t \n \rMorgen kommt der Weihnachtsmann \t\n" >>> s.strip() 'Morgen kommt der Weihnachtsmann' >>> s.lstrip() 'Morgen kommt der Weihnachtsmann \t\n' >>> s.rstrip() ' \t \n \rMorgen kommt der Weihnachtsmann' >>> >>> s = "69023 Frankfurt" >>> s.strip("0123456789 ") 'Frankfurt' 19.9 ■ Strings ausrichten str.center(laenge[, fillchar]) Der String str wird mit fillchar bzw. Leerzeichen, falls kein fillchar gegeben ist, gleichermaßen von links und rechts auf die Länge laenge gebracht. 187 188 19 Alles über Strings . . . ■ ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ str.ljust(laenge[, fillchar]) Der String str wird mit fillchar bzw. Leerzeichen, falls kein fillchar gegeben ist, links auf die Länge laenge gebracht. str.rjust(laenge[, fillchar]) Analog zu ljust von rechts. str.zfill(laenge) Spezialfall für numerische Werte. Der String wird rechts ausgerichtet und von links mit Nullen gefüllt. >>> s = "Hallo" >>> s.center(15) ' Hallo ' >>> s.ljust(15) 'Hallo ' >>> s.rjust(15) ' Hallo' >>> >>> z = "123.99" >>> z.zfill(10) '0000123.99' 19.10 ■ String-Tests s.isalnum() True, wenn alle Zeichen in s Buchstaben oder Ziffern sind. >>> for word in ("mp3", "Hallo", "hello", "343", "767.43"): ... print("%6s : %s" % (word, word.isalnum())) ... mp3 : True Hallo : True hello : True 343 : True 767.43 : False ■ s.isalpha() True, wenn alle Zeichen in s Buchstaben sind. >>> for word in ("Hallo", "hello", "343", "767.43"): ... print("%6s : %s" % (word, word.isalpha())) ... Hallo : True hello : True 343 : False 767.43 : False 19.10 String-Tests Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ s.isdigit() True, wenn alle Zeichen in s Ziffern sind. >>> for word in ("Hallo", "hello", "343", "767.43"): ... print("%6s : %s" % (word, word.isdigit())) ... Hallo : False hello : False 343 : True 767.43 : False ■ s.islower() True, wenn alle Buchstaben in s Kleinbuchstaben sind. >>> for word in ("Hallo", "hello", "343", "767.43"): ... print("%6s : %s" % (word, word.islower())) ... Hallo : False hello : True 343 : False 767.43 : False ■ s.isupper() True, wenn alle Buchstaben in s Großbuchstaben sind. >>> for word in ("Hallo", "hello", "343", "767.43", "HALLO"): ... print("%6s : %s" % (word, word.isupper())) ... Hallo : False hello : False 343 : False 767.43 : False HALLO : True ■ s.isspace() True, wenn alle Zeichen in s Whitespaces sind. >>> for word in ("Hallo", "hello", "343", "767.43", " \t\n", " "): ... print("%6s : %s" % (word, word.isspace())) ... Hallo : False hello : False 343 : False 767.43 : False : True : True 189 190 19 Alles über Strings . . . Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ s.istitle() True, wenn alle Wörter in s groß geschrieben sind. >>> for word in ("Hallo", "hello", "343", "767.43", "HALLO"): ... print("%6s : %s" % (word, word.istitle())) ... Hallo : True hello : False 343 : False 767.43 : False HALLO : False 19.11 Aufgaben 1. Aufgabe: Folgende Adressendatei (siehe addresses_mixed.txt) ist gegeben: Frank,Meyer,Radolfzell;07732/43452 Peter,Rabe,Konstanz;07531/70021 Ottmar,Huber,Rosenheim;08031/7877-0 Anna,Rabe,Radolfzell;07732/2343 Oskar,Lindner,Konstanz;07531/890 Anna,List,München;089/3434544 Anna,List,München;089/3434544 Franziska,Huber,Rosenheim;08031/787878 Sarah,Rabe,Konstanz;07531/343454 Schreiben Sie ein Python-Programm, das diese Adressen zeilenweise in der Form „Vorname, Nachname, Ort, Telefonnummer” ausgibt. Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 499 2. Aufgabe: Für die folgende Aufgabe benutzen wir wieder die Daten der Datei „adressen.txt”: Frank;Meyer;Radolfzell;07732/43452 Peter;Rabe;Konstanz;07531/70021 Ottmar;Huber;Rosenheim;08031/7877-0 Anna;Rabe;Radolfzell;07732/2343 Oskar;Lindner;Konstanz;07531/890 Anna;List;München;089/3434544 Anna;List;München;089/3434544 Franziska;Huber;Rosenheim;08031/787878 Sarah;Rabe;Konstanz;07531/343454 19.11 Aufgaben Schreiben Sie ein Python-Programm, das aus dieser Adressendatei eine Liste mit Zweiertupels erstellt, die jeweils aus dem ersten und letzten Element einer Adresse, also dem Vornamen und der Telefonnummer, bestehen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. [('Frank', '07732/43452'), ('Peter', '07531/70021'), ('Ottmar', '08031/7877-0'), ('Anna', '07732/2343'), ('Oskar', '07531/890'), (' Anna', '089/3434544'), ('Anna', '089/3434544'), ('Franziska', '08031/787878'), ('Sarah', '07531/343454')] Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 499 3. Aufgabe: Der folgende String enthält Zeilenenden, die sowohl vom Mac als auch von Windows und Unix bzw. Linux kommen: Hat der alte Hexenmeister\nSich doch einmal wegbegeben!\r\nUnd nun sollen seine Geister\r Auch nach meinem Willen leben.\n Bringen Sie diesen String mittels Python in eine Linux/Unix-Form, d.h. dass jede Zeile nur von einem \n beendet wird. Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 500 4. Aufgabe: Schreiben Sie eine Funktion, die die Position des n-ten Vorkommens eines Strings „sub” in einem anderen String ausgibt. Falls „sub” nicht vorkommt, soll -1 zurückgeliefert werden. >>> >>> 4 >>> 12 >>> 20 from findnth import findnth findnth("abc xyz abc xyz abc xyz", "xyz", 1) findnth("abc xyz abc xyz abc xyz", "xyz", 2) findnth("abc xyz abc xyz abc xyz", "xyz", 3) Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 500 5. Aufgabe: Schreiben Sie eine Funktion, die das n-te Vorkommen eines Strings „sub” durch einen String „replacement” ersetzt. Die Funktion soll als Ergebnis den veränderten String zurückliefern, bzw. falls replacement nicht im String vorkommt, soll der unveränderte Original-String zurückgegeben werden. Lösung: Lösungen zu Kapitel 19 (Alles über Strings . . . ), Seite 501 191 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 20 Ausnahmebehandlung Fehler werden häufig in anderen Sprachen mit Fehlerrückgabewerten oder globalen Statusvariablen behandelt. Traditionelle Fehlerbehandlung bzw. Fehlervermeidung wird meistens in bedingten Anweisungen behandelt, so wie im folgenden Codefragment, in dem eine Division durch 0 verhindert werden soll: if y != 0: z = x / y Bild 20.1 Eleganter geht es mit den in Python vorhandenen Ausnahme- Ausnahmebehandlung behandlungen. Unter einer Ausnahmebehandlung1 versteht man ein Verfahren, die Zustände, die während einer Fehlersituation herrschen, an andere Programmebenen weiterzuleiten. Dadurch ist es möglich, per Programm einen Fehlerzustand gegebenenfalls zu „reparieren”, um anschließend das Programm weiter auszuführen. Ansonsten würden solche Fehlerzustände in der Regel zu einem Abbruch des Programms führen. Man verwendet den Begriff „Ausnahme”, um schon mit der sprachlichen Bezeichnung klarzumachen, dass es sich um einen außerordentlichen Zustand handelt, also die „Ausnahme von der Regel”. Viele Programmiersprachen wie C++, Objective-C, PHP, Java, Ruby und Python besitzen integrierte Mechanismen mit eigenen formalen syntaktischen Strukturen, um Ausnahmebehandlungen zu ermöglichen. Die Realisierung der Ausnahmebehandlung sieht meist so aus, dass wenn eine Ausnahmesituation auftritt, automatisch Informationen und Zustände gespeichert werden, die zum Zeitpunkt der Ausnahme bzw. vor der Ausnahme geherrscht hatten. In den meisten Sprachen, so in C++, Java, PHP und auch in Python, werden Codeteile, die mit der Ausnahmebehandlung ausgeführt werden sollen, in einem try-Block zusammengefasst. Die Ausnahmebehandlung in Python ist sehr ähnlich zu derjenigen in Java. Der Code, der das Risiko für eine Ausnahme beherbergt, wird in einen try-Block eingebettet. Aber während in Java Ausnahmen durch catch-Konstrukte abgefangen werden, geschieht dies in Python durch das Schlüsselwort except. Semantisch funktioniert es aber genauso. Man kann auch Ausnahmen selbst erzeugen: Mit der raise-Anweisung ist es möglich, eine bestimmte Ausnahme entstehen zu lassen. 1 Häufig auch unter dem englischen Begriff „exception handling” bekannt. 194 20 Ausnahmebehandlung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Schauen wir uns ein einfaches Beispiel an. Ein Benutzer soll eine Integer-Zahl eingeben. Wenn wir nur ein input() benutzen, wird die Eingabe als String interpretiert, den wir dann in ein Integer wandeln müssen. Bei der Anwendung des cast-Operators, also bei der Wandlung des eingegebenen Strings in eine Integer-Zahl, kann es jedoch zu einem Fehler kommen, wenn der String kein gültiges Integer-Format aufzeigt. Es wird dann der AusnahmeFehler „ValueError” generiert: >>> n = int(input("Please enter a number: ")) Please enter a number: 23.5 Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '23.5' >>> Das folgende Skript implementiert unter Verwendung einer Ausnahmebehandlung eine Lösung des obigen Problems in Form einer robusten Eingabeaufforderung. Man wird solange aufgefordert, eine Zahl einzugeben, bis diese einer korrekten Integer-Zahl entspricht: while True: try: zahl = input("Zahl eingeben: ") zahl = int(zahl) break except ValueError as e: print("error message: ", e) print("Error. Keine Zahl!") Funktionsweise: ■ ■ ■ ■ Ausführung der try-Klausel (Anweisungen zwischen den Schlüsselworten try und except) Die except-Klausel wird übersprungen, wenn keine Ausnahme auftritt. Tritt eine Ausnahme während der Ausführung der try-Klausel auf, wird der Rest der Klausel übersprungen. Stimmt der Typ der Ausnahme mit dem Schlüsselwort von except überein, wird die except-Klausel ausgeführt. Tritt eine Ausnahme auf, die nicht mit der Ausnahme in der except-Klausel übereinstimmt, wird sie nach außen an weitere try-Anweisungen weitergereicht. Wenn keine Behandlung erfolgt, so ist es eine unbehandelte Ausnahme. Ein Beispiel zum Abfangen von Divisionen durch die Zahl 0: >>> zahlen = [3.7832, 8.5, 1.9, 0, 4.5] >>> for x in zahlen: ... try: ... print(x, 1.0/x) ... except ZeroDivisionError: ... print(str(x) + " hat kein inverses Element") ... 3.7832 0.2643264960879679 8.5 0.11764705882352941 20.1 Abfangen mehrerer Exceptions 1.9 0.5263157894736842 0 hat kein inverses Element 4.5 0.2222222222222222 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 20.1 Abfangen mehrerer Exceptions Es können auch mehrere Exceptions nach einer try-Anweisung abgefangen werden, wie wir im folgenden Beispiel zeigen: import sys try: f = open('integers.txt') s = f.readline() i = int(s.strip()) except IOError as err: (errno, strerror) = err.args print("I/O error({0}): {1}".format(errno, strerror)) except ValueError: print("No valid integer in line.") except: print("Unexpected error:", sys.exc_info()[0]) raise Die Anweisung raise in der letzten Zeile führt die Ausnahme, die wir gerade abgefangen hatten, nochmals aus. Dadurch kann man diese Ausnahme an anderer Stelle wieder auffangen oder man erhält einen Programmabbruch mit der eigentlichen Fehlermeldung. In den beiden ersten except-Fällen behandeln wir bestimmte Fehler, also IOError und ValueError, und im letzten except sind wir unspezifisch. Da wir dort keine Fehlernamen angeben, spricht dieser Fall auf alle Fehler an, die noch nicht vorher in einer except-Klausel behandelt worden sind. Haben wir keinen solchen „unspezifischen” except-Fall und tritt ein Fehler auf, den wir nicht behandeln, dann wird die Standardausnahmebehandlung von Python ausgelöst, d.h. das Programm wird abgebrochen und die entsprechende Fehlermeldung ausgegeben. Das Programm verhält sich dann so, als hätte es überhaupt keine try-except-Anweisung gegeben. Jede try-Anweisung muss von mindestens einem exceptFall oder einer finally-Anweisung, die wir erst im Folgenden besprechen werden, gefolgt werden. 195 196 20 Ausnahmebehandlung 20.2 except mit mehrfachen Ausnahmen Eine einzelne except-Anweisung kann auch gleichzeitig mehrere Fehler abfangen. Die verschiedenen Fehlerarten werden dann in einem Tupel gelistet, wie wir im folgenden Beispiel sehen: try: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. f = open('integers.txt') s = f.readline() i = int(s.strip()) except (IOError, ValueError): print("An I/O error or a ValueError occurred") except: print("An unexpected error occurred") raise 20.3 Die optionale else-Klausel Das try ... except-Sprachkonstrukt hat eine optionale else-Klausel, die – falls vorhanden – nach allen except-Klauseln stehen muss. Dort befindet sich Code, der ausgeführt wird, wenn die try-Klausel keine Ausnahme auslöst. Im Folgenden verlangen wir solange die Eingabe eines Dateinamens, bis sich dieser zum Lesen öffnen lässt. Der else-Teil der try-Anweisung wird nur ausgeführt, wenn es keinen Ausnahmefehler gegeben hat. Deshalb dürfen wir dann auch auf das Datei-Handle f zugreifen: while True: filename = input("Dateiname: ") try: f = open(filename, 'r') except IOError: print(filename, " lässt sich nicht öffnen") else: print(filename, ' hat ', len(f.readlines()), ' Zeilen ') f.close() break 20.4 Fehlerinformationen über sys.exc_info 20.4 Fehlerinformationen über sys.exc_info Die genauen Fehlerinformationen kann man sich mit der Methode exc_info des sysModuls anzeigen lassen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. import sys try: i = int("Hallo") except: (type, value, traceback) = sys.exc_info() print("Unexpected error:") print("Type: ", type) print("Value: ", value) print("traceback: ", traceback) raise Die Ausgabe des obigen Programms: bernd@saturn:~/bodenseo/python/beispiele$ python3 exception_sys_exc_info.py Unexpected error: Type: <class 'ValueError'> Value: invalid literal for int() with base 10: 'Hallo' traceback: <traceback object at 0xb728834c> Traceback (most recent call last): File "exception_sys_exc_info.py", line 4, in <module> i = int("Hallo") ValueError: invalid literal for int() with base 10: 'Hallo' bernd@saturn:~/bodenseo/python/beispiele$ 20.5 Exceptions generieren Man kann auch selbst Exceptions generieren: >>> raise SyntaxError("Sorry, mein Fehler!") Traceback (most recent call last): File "<stdin>", line 1, in <module> SyntaxError: Sorry, mein Fehler! Man kann auch selbst Exception-Klassen definieren. Klassen werden wir jedoch erst in Kapitel 21 (Grundlegende Aspekte) behandeln, sodass das folgende Beispiel für viele noch unverständlich bleiben wird: 197 198 20 Ausnahmebehandlung class MyException(Exception): pass raise MyException("Was falsch ist, ist falsch!") Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Startet man dieses Programm, erhält man folgende Ausgabe: $ python3 exception_eigene_klasse.py Traceback (most recent call last): File "exception_eigene_klasse.py", line 4, in <module> raise MyException("Was falsch ist, ist falsch!") __main__.MyException: Was falsch ist, ist falsch! 20.6 Finalisierungsaktion Bisher haben wir die try-Anweisungen immer nur im Zusammenspiel mit except-Klauseln benutzt. Aber es gibt noch eine andere Möglichkeit für try-Anweisungen. Die try-Anweisung kann von einer finally-Klausel gefolgt werden. Man bezeichnet diese Form auch als Finalisierungs- oder Terminierungsaktionen, weil sie immer unter allen Umständen ausgeführt werden müssen, und zwar unabhängig davon, ob eine Ausnahme im try-Block aufgetreten ist oder nicht. try: x = float(input("Your number: ")) inverse = 1.0 / x finally: print("Ich werde immer ausgegeben, ob Fehler oder nicht") print("Mich sieht man nur, wenn es keinen Fehler gab!") In den folgenden Programmläufen demonstrieren wir einen Fehlerfall und einen Durchlauf ohne Ausnahmen: bernd@saturn:~/bodenseo/python/beispiele$ python3 finally_exception.py Your number: 42 Ich werde immer ausgegeben, ob Fehler oder nicht Mich sieht man nur, wenn es keinen Fehler gab! bernd@saturn:~/bodenseo/python/beispiele$ python3 finally_exception.py Your number: 0 Ich werde immer ausgegeben, ob Fehler oder nicht Traceback (most recent call last): File "finally_exception.py", line 3, in <module> inverse = 1.0 / x ZeroDivisionError: float division by zero bernd@saturn:~/bodenseo/python/beispiele$ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Teil II Objektorientierte Programmierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21 Grundlegende Aspekte In diesem Kapitel werden wir die wichtigsten Aspekte der Objektorientierung anhand eines Vergleichs mit Bibliotheken und mit einer einfachen Roboter-Klasse demonstrieren. 21.1 Bibliotheksvergleich Auch wenn Python ohne Wenn und Aber eine objektorientierte Programmiersprache ist, sind wir in den vorhergehenden Kapiteln nur indirekt auf die objektorientierte Programmierung (OOP) eingegangen. Wir haben Objekte und Methoden von Klassen benutzt, ohne eigentlich von ihrer Existenz zu wissen. Mit Python lassen sich kleine Skripte oder Programme einfach und effizient schreiben, auch ohne dass man sie objektorientiert model- Bild 21.1 Früchte liert. Gerade totale Programmieranfänger finden es erfahrungsgemäß einfacher, wenn sie nicht sofort mit allen Prinzipien der OOP konfrontiert werden. Sie haben genügend Probleme, Zuweisungen, bedingte Anweisungen oder Schleifen zu verstehen und vor allem richtig anzuwenden. Aber in vielen Situationen stellt die OOP eine deutliche qualitative Verbesserung der Implementierung eines Problems dar. In diesem Kapitel geben wir nun eine grundlegende Einführung in den objektorientierten Ansatz von Python. OOP ist eine der mächtigsten Programmiermöglichkeiten von Python, aber wie wir gesehen haben, muss man sie dennoch nicht nutzen, d.h. man kann auch umfangreiche und effiziente Programme ohne Verwendung von OOP-Techniken schreiben. Auch wenn viele Programmierer und Informatiker die OOP für eine moderne Errungenschaft halten, so gehen ihre Wurzeln bis in die 1960er-Jahre zurück. Die erste Programmiersprache, die Objekte verwendete, war „Simula 67” von Ole-Johan Dahl und Kirsten Nygard. 202 21 Grundlegende Aspekte Wir werden die fundamentalen Prinzipien der objektorientierten Programmierung und ihre Realisierung in Python kennenlernen, also Datenabstraktion, Datenkapselung, Verstecken von Informationen, Polymorphismus, Vererbung, Mehrfachvererbung, Metaklassen und abstrakte Klassen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Bevor wir mit der eigentlichen OOP in Python beginnen, wollen wir die grundsätzlichen Ideen der OOP am Beispiel von öffentlichen Bibliotheken verdeutlichen. Eine Bibliothek enthält eine geordnete Sammlung von Büchern, Zeitschriften und Zeitungen, Hörbüchern, Filmen und so weiter. Generell gibt es zwei verschiedene Möglichkeiten, wie man die Bestände in einer Bibliothek zur Nutzung bereitstellen kann. In der Freihandaufstellung handelt es sich um eine für alle Benutzer frei zugängliche Aufstellung der Medien. Die Magazinaufstellung ist hingegen eine Aufstellungsart, bei der die Bestände nur Bibliotheksmitarbeitern zugänglich sind. In einer solchen Magazinaufstellung werden Bücher häufig nicht nach Fachgruppen aufgestellt. So können Bücher beispielsweise aus Platzgründen auch unter Berücksichtigung ihres Formats aufgestellt werden. So können ebenfalls zur Platzoptimierung Kompaktregalanlagen oder sogar Hochregale eingesetzt werden. Hier beginnt nun unsere Analogie zur Objektorientierten Programmierung. Die Bücher, Hörbücher, Filme usw. entsprechen den Objekten, während die Bild 21.2 Eierschale und Bibliothek Bibliothek inklusive der Art, wie sie ihre Bestände („Daten”) bereitstellt, einer Klasse entspricht.1 Auf die Daten einer Klasse kann meistens nur über spezielle Methoden zugegriffen werden, was den Mitarbeiterinnen und Mitarbeitern in der Bibliothek entspricht. Die Tatsache, dass man auf die „Daten”, also die Bücher, nicht direkt zugreifen kann, bezeichnet man als Datenabstraktion. Einerseits sind die Daten gekapselt, d.h. Benutzer können und dürfen das Magazin nicht betreten, andererseits sind die Bücher auch nicht sichtbar, d.h. sie sind vor dem Benutzer versteckt, was man meistens auch in der OOP als „Geheimnisprinzip”2 bezeichnet. Die Vorteile in der Bibliothek sind denjenigen bei der Datenspeicherung in einer Klasse sehr ähnlich: ■ ■ ■ 1 2 Schutz vor falschem Einsortieren Entspricht der falschen Benutzung einer Datenstruktur. Diebstahl oder Vandalismus Der Benutzer von Daten einer Implementierung benutzt diese wissentlich oder unwissentlich falsch. Platzoptimierung Hat man komfortable Methoden als Schnittstellen für die Klassenbenutzer, kann man Genaugenommen müssten wir sagen eine bestimmte Bibliothek – in unserem Bild die „Library of Appeal for Ontario” – ist eine Instanz der Klasse Bibliothek, also dem „Konzept”. engl. ,Information Hiding” 21.2 Objekte und Instanzen einer Klasse die dahinterliegenden Daten auch in komplexeren und damit gegebenenfalls auch schwerer verständlichen Strukturen speichern. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ Raumklima Die Daten müssen nicht benutzerfreundlich repräsentiert sein, da die Benutzerfreundlichkeit in den Methoden der Klasse liegt. 21.2 Objekte und Instanzen einer Klasse Vielleicht war Ihnen bereits aufgefallen, dass bei der Verwendung der type-Funktion immer auch das Wort „class” auftaucht. Alle Datentypen, Funktionen, Module usw. sind in Python Klassen: >>> b = 24.1257 >>> type(b) <class 'float'> >>> >>> def foobar(x): ... return x + 42 ... >>> type(foobar) <class 'function'> >>> >>> import math >>> type(math) <class 'module'> Eine der vielen in Python integrierten Klassen ist die uns bestens vertraute list-Klasse. Sie stellt eine Fülle von Methoden zur Verfügung, mit deren Hilfe wir beispielsweise Listen aufbauen, Elemente anschauen, verändern und entfernen können: >>> >>> >>> 6 >>> >>> >>> >>> abc x = [3, 6, 9] y = [45, "abc"] print(x[1]) x[1] = 99 x.append(42) last = y.pop() print(last) Die Variablen x und y referenzieren zwei Instanzen der list-Klasse. Vereinfacht haben wir bisher gesagt, „x und y sind Listen”. Im Folgenden werden wir die Begriffe „Objekt” und „Instanz” synonym benutzen, wie dies auch in anderen Einführungen üblich ist.3 3 Nach der offiziellen Python-Referenz wird in Python eigentlich alles als Objekt bezeichnet, also beispielsweise Zahlen, Strings, Listen, aber auch Funktionen und Module. Im englischen Original heißt es: „Objects 203 204 21 Grundlegende Aspekte 21.3 Kapselung von Daten und Methoden Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. pop und append aus dem obigen Beispiel sind Methoden der list-Klasse. pop liefert uns das „oberste” bzw. das Element mit dem höchsten Index der Liste zurück und entfernt dieses Element. Wir wissen allerdings nicht, wie die Listen intern im Speicher abgelegt sind. Wir brauchen diese Information auch nicht, da uns die list-Klasse Methoden zur Verfügung stellt, auf die gespeicherten Daten „indirekt” zuzugreifen. Methoden sind von besonderer Wichtigkeit im Zusammenhang mit der Datenkapselung. Wir werden uns später genauer mit der Datenkapselung beschäftigen. Sie verhindert den direkten Zugriff auf die interne Datenstruktur, d.h. man kann nur über definierte Schnittstellen zugreifen, die Methoden bzw. die Properties. Die „internen Daten” einer Instanz sind die Attribute, auf die wir später zu sprechen kommen. 21.4 Eine minimale Klasse in Python Die wichtigsten Begriffe der objektorientierten Programmierung und ihrer Umsetzung in Python werden wir im Folgenden an einem Beispiel „Roboterklasse” demonstrieren. Wir beginnen mit der einfachstmöglichen Klasse in Python, die wir Roboter nennen. class Roboter: pass An diesem Beispiel können wir den grund- Bild 21.3 Evolution der Roboter legenden syntaktischen Aufbau einer Klasse erkennen: Eine Klassendefinition besteht aus zwei Teilen: dem Kopf und dem Körper. Der Kopf besteht meist nur aus einer Zeile: dem Schlüsselwort class, gefolgt von einem Leerzeichen, einem beliebigen Namen – in unserem Fall Roboter –, einer kommaseparierten Auflistung von Oberklassen in Klammern und als letztes Zeichen ein Doppelpunkt. Gibt es keine Oberklassen, entfällt die Angabe der Oberklassen und der Klammern.4 Der Körper einer Klasse besteht aus einer eingerückten Folge von Anweisungen die wie in unserem Beispiel auch nur aus einer einzigen pass-Anweisung bestehen kann. Damit haben wir bereits eine einfache Python-Klasse mit dem Namen Roboter definiert. Wir können diese Klasse auch benutzen: 4 are Python’s abstraction for data. All data in a Python program is represented by objects or by relations between objects. (In a sense, and in conformance to Von Neumann’s model of a “stored program computer“, code is also represented by objects.) Every object has an identity, a type and a value.” Prinzipiell kann auch ein leeres Klammernpaar vor dem Doppelpunkt stehen. In Python 2 musste der Klassenname von „(object)” gefolgt werden. Dies bedeutet, dass die Klasse von der allgemeinen Klasse „object” erbt. Dies kann man auch in Python 3 so schreiben, ist aber nicht mehr nötig, da alle Klassen automatisch von „object” erben. 21.5 Eigenschaften und Attribute Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class Roboter: pass if __name__ == "__main__": x = Roboter() y = Roboter() y2 = y print(y == y2) print(y == x) Wir haben in obigem Beispiel zwei verschiedene Roboter x und y geschaffen. Außerdem haben wir mit y2 = y ein Alias y2 für y erzeugt, d.h. ein weiterer Name für das gleiche Objekt, also eine Referenz.5 Das Programm liefert folgende Ausgaben: True False 21.5 Eigenschaften und Attribute Unsere Roboter haben keinerlei Eigenschaften – noch nicht einmal Namen, wie dies für „ordentliche” Roboter üblich ist. Als weitere Eigenschaften wären beispielsweise eine Typbezeichnung, Baujahr und so weiter denkbar. Eigenschaften werden in der objektorientierten Programmierung als Attribute bezeichnet. 6 Einer Instanz kann man beliebige Attributnamen zuordnen. Sie werden mit einem Punkt an den Namen der Instanz angeschlossen. Man bezeichnet diese Attribute als Instanzattribute. Im Folgenden erzeugen wir dynamisch Attribute für den Roboternamen name und das Baujahr baujahr. Bitte beachten Sie, dass dies noch nicht die Art und Weise ist, wie man normalerweise Attribute in Klassen verwendet: >>> ... ... >>> >>> >>> >>> >>> >>> >>> 5 6 class Roboter: pass x = Roboter() y = Roboter() x.name = "Marvin" x.baujahr = 1979 y.name = "Caliban" y.baujahr = 1993 In der if-Anweisung haben wir die Systemvariable __name__ benutzt. Sie wird automatisch entweder auf den String __ main__¨ gesetzt oder enthält den Namen des Moduls, wenn das Modul, in dem __name__ ¨ vorkommt, importiert wird. Näheres dazu in Kapitel Tests und Fehler, Seite 365. Der Begriff Attribut stammt vom lateinischen „attribuere”, was „zuweisen” oder „zuteilen” bedeutet. Dieser Begriff wird übrigens auch in der Philosophie verwendet, wo er die einem Gegenstand oder Objekt zugewiesene Eigenschaft bezeichnet. 205 206 21 Grundlegende Aspekte >>> print(x.name) Marvin Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Nur für diejenigen, die gerne wissen wollen, was „intern” vorgeht. Die Instanzen besitzen Dictionaries __dict__ , in denen die Attribute verwaltet werden: >>> x.__dict__ {'baujahr': 1979, 'name': 'Marvin'} >>> y.__dict__ {'baujahr': 1993, 'name': 'Caliban'} >>> Attribute können übrigens auch dem Klassenobjekt selbst zugeordnet werden. Diese bezeichnet man dann als Klassenattribute. Klassenattribute sind Attribute, die gewissermaßen gemeinsam für alle Instanzen sind. Beispielsweise können wir ein Klassenattribut anzahl einführen, das die Anzahl aller Instanzen beinhaltet. Es ist klar, dass dieses Attribut nichts mit einem einzelnen Roboter zu tun hat: >>> ... ... >>> >>> >>> >>> 1 class Roboter: pass Roboter.anzahl = 0 x = Roboter() Roboter.anzahl += 1 print(Roboter.anzahl) Klassenattribute können auch von Instanzen derselben Klasse angesprochen werden: >>> class Roboter: ... pass ... >>> Roboter.marke = "Kuka" >>> >>> x = Roboter() >>> x.marke 'Kuka' >>> y = Roboter() >>> y.marke = "Atlas" >>> x.marke 'Kuka' >>> y.marke 'Atlas' Aus dem obigen Beispiel können wir ersehen, dass wir bei einer Roboterinstanz solange dasselbe Markenattribut wie das Klassenattribut haben, bis wir für diese Instanz ein eigenes Attribut erstellen. Was genau passiert, können wir ersehen, wenn wir uns die entsprechenden __dict__-Dictionarys anschauen. Wir sehen, dass auf das Klassenattribut nur dann bei einer Instanz zugegriffen wird, wenn es kein solches Attribut im __dict__ der Instanz gibt: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.5 Eigenschaften und Attribute >>> x.__dict__ {} >>> y.__dict__ {'marke': 'Atlas'} >>> >>> Roboter.__dict__ mappingproxy({'__module__': '__main__', '__doc__': None, '__weakref__ ': <attribute '__weakref__' of 'Roboter' objects>, '__dict__': < attribute '__dict__' of 'Roboter' objects>, 'marke': 'Kuka'}) Auch Instanzen anderer Klassen, wie beispielsweise der Funktions- und Modulklassen, können wir dynamisch Attribute zuordnen:7 >>> ... ... >>> >>> 0 >>> >>> def f(x): return x + 42 f.calls = 0 print(f.calls) import math math.mathematicians = ["Evariste Galois", "Andrew Wiles"] Attribute bei Funktionen können zum Beispiel als Ersatz für statische Variablen benutzt werden. In der folgenden Funktion wird das Attribut aufrufe benutzt, um zu zählen, wie oft die Funktion aufgerufen wird: def f(x): if hasattr(f, "aufrufe"): f.aufrufe += 1 else: f.aufrufe = 1 return x + 3 for i in range(10): f(i) print(f.aufrufe) Alternativ können wir die Funktion f auch unter Benutzung der Funktion getattr schreiben. getattr liefert den Wert eines Attributs zurück, wenn ein Attribut existiert, ansonsten einen Default-Wert – in unserem Fall 0: def f(x): f.aufrufe = getattr(f, "aufrufe", 0) + 1 return x + 3 Wenn wir Instanzattribute für unsere Roboterklasse erzeugen wollen, so müssen wir dies unmittelbar in der Klassendefinition tun. Instanzattribute sind die Eigenschaften, die die 7 Allerdings funktioniert dies nicht für alle Klassen. Es geht beispielsweise nicht bei der int-, float- und strKlasse. Dies wird nach dem Kapitel über slots klar. 207 208 21 Grundlegende Aspekte einzelnen Instanzen beschreiben, d.h. so haben unsere Roboter im Allgemeinen verschiedene Namen und sicherlich eine verschiedene Seriennummer. Wir benötigen Methoden, um Instanzattribute in einer Klassendefinition zu erzeugen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.6 Methoden Um zu zeigen, wie man Methoden in einer Klasse definiert, werden wir unsere leere Roboterklasse um eine Methode sage_hallo erweitern. Eine Methode unterscheidet sich äußerlich nur in zwei Aspekten von einer Funktion: ■ ■ Sie ist eine Funktion, die innerhalb einer class-Definition definiert ist.8 Der erste Parameter einer Methode ist immer eine Referenz auf die Instanz, von der sie aufgerufen wird. Diese Referenz wird üblicherweise mit „self” benamt. 9 Wir erweitern nun unsere Roboterklasse um die Methode sage_hallo, die beim Aufruf einfach nur „Hallo” schreibt: Bild 21.4 Hi, ich bin Marvin class Roboter: def sage_hallo(self): print("Hallo") if __name__ == "__main__": x = Roboter() x.sage_hallo() Wir sehen im Code, dass der Parameter „self” nur bei der Definition einer Methode erscheint. Beim Aufruf wird er nicht angegeben. Im Vergleich zu Funktionsaufrufen ist das zunächst einmal befremdlich, d.h. wir definieren eine Methode mit einem Parameter „self” und rufen sie scheinbar ohne Parameter auf. Aber wenn wir genauer auf den Aufruf schauen, sehen wir, dass wir ja nicht nur sage_hallo() aufrufen, sondern dass die Instanz x vor dem Punkt erscheint. Darin liegt das Geheimnis: Es ist gewissermaßen so, als hätten wir sage_hallo(x) aufgerufen. In anderen Worten, wir übergeben eine Referenz auf die Instanz x an self. Zum weiteren Verständnis: Eigentlich müsste man die Methode einer Klasse über den Klassennamen aufrufen. In diesem Fall wird die Instanz als Argument übergeben, also Roboter.sage_hallo(x). Weil dies aber zum einen besonders unhandlich ist und außerdem nicht den üblichen Gepflogenheiten in der OOP entspricht, bindet Python alle Methoden automatisch an die Klasseninstanzen. 8 9 Der Name der Funktion ist ein Klassenattribut! Dies ist nur eine Konvention. Prinzipiell könnte man einen beliebigen Namen wählen, also auch „this”, was Java oder C++-Programmierern vielleicht besser gefallen würde. 21.7 Instanzvariablen 21.7 Instanzvariablen Unsere Roboter sollen noch freundlicher werden. Sie sollen nicht nur „Hallo” sagen, sondern sich dabei auch mit ihrem Namen und Baujahr vorstellen. Dazu ändern wir unsere Methode sage_hallo entsprechend ab. Damit sind wir wieder zurück bei den Instanzattributen. Denn eine Instanz muss sich ihren Namen und Baujahr merken können. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class Roboter: def sage_hallo(self): print("Hallo, mein Name ist " + self.name + ",") print("gebaut im Jahre " + str(self.baujahr)) if __name__ == "__main__": x = Roboter() x.name = "Marvin" x.baujahr = 1979 y = Roboter() y.name = "Caliban" y.baujahr = 1993 x.sage_hallo() y.sage_hallo() Auch wenn die Ausgabe des obigen Programms klar sein sollte, zeigen wir im Folgenden die Ergebnisse: Hallo, gebaut Hallo, gebaut mein Name ist Marvin, im Jahre 1979 mein Name ist Caliban, im Jahre 1993 In unserer obigen Implementierung der Roboterklasse verbirgt sich noch ein Designproblem. Wenn wir einen Roboter neu schaffen, müssen wir jedes Mal drei Anweisungen durchführen. Zuerst müssen wir einen Roboter mit x = Roboter() instanziieren und dann müssen wir die Attribute setzen, also x.name = "Marvin" und x.baujahr = 1979. Dieses Vorgehen ist umständlich und fehlerträchtig und entspricht vor allen Dingen nicht dem üblichen Vorgehen in der OOP. 21.8 Die __init__-Methode Schön wäre es, wenn wir direkt bei der Instanziierung den Namen und das Baujahr setzen bzw. übergeben könnten, also x = Roboter("Marvin", 1979). Für diesen Zweck bietet Python eine Methode mit dem Namen __init__. Der Name dieser Methode ist festgelegt und kann nicht frei gewählt werden! __init__ gehört zu den sogenannten magischen Methoden, von denen wir in den folgenden Kapiteln noch weitere kennenlernen werden. Die 209 210 21 Grundlegende Aspekte Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. __init__-Methode dient, wie der Name nahelegt, der Initialisierung einer Instanz. Python besitzt keinen expliziten Konstruktor bzw. Destruktor, wie man sie in Java oder C++ kennt. Der eigentliche Konstruktor wird implizit von Python gestartet. Die __init__-Methode wird jedoch unmittelbar nach dem eigentlichen Konstruktor gestartet, und dadurch entsteht der Eindruck, als handele es sich um einen Konstruktor. Die __init__-Methode kann an beliebiger Stelle in der Klassendefinition stehen, sollte aber nach Konvention immer die erste Methode direkt unter dem Klassenheader sein. class Roboter: def __init__(self, name, baujahr): self.name = name self.baujahr = baujahr def sage_hallo(self): print("Hallo, mein Name ist " + self.name) if __name__ == "__main__": x = Roboter("Marvin", 1979) y = Roboter("Caliban", 1993) x.sage_hallo() y.sage_hallo() Schreibt man x = Roboter("Marvin", 1979), dann verhält sich das logisch gesehen so, als würde man erst eine Instanz x instanziieren, also x = Roboter()10 , und dann die __init__-Methode aufrufen mit x.__init__("Bob", 2014). Der Benutzung der Klasse sieht nun deutlich „aufgeräumter” und klarer aus. Zuvor wollen wir jedoch noch eine Fehlermeldung demonstrieren, die oft bei Anfängern Fragen aufwirft. Wir versuchen, in der folgenden interaktiven Python-Shell einen Roboter zu erzeugen. Statt der korrekten Instanziierung x = Roboter("Marvin", 1979) rufen wir Roboter ohne Parameter auf: >>> from roboter import Roboter >>> x = Roboter() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() takes exactly 3 arguments (1 given) Wir wissen, dass wir einen Namen und ein Baujahr bei der Definition eines neuen Roboters angeben müssen. Auf den ersten Blick erscheint deshalb die Fehlermeldung „__init__() takes exactly 3 arguments (1 given)” verwunderlich. Wir hatten doch kein Argument übergeben, und zwei hätten wir übergeben müssen? Schauen wir auf die genaue Definition von __init__(), klärt sich der scheinbare Widerspruch sofort, denn diese Methode hat wirklich drei Parameter: self, name, baujahr „self” wird implizit generiert, daher die Meldung „1 given”. 10 Dies geht natürlich nicht mehr wegen unserer __init__-Methode, die zwei Argumente beim Aufruf zwingend fordert! 21.9 Destruktor Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.9 Destruktor Für eine Klasse kann man auch die Methode __del__ definieren. Sie wird aufgerufen, bevor eine Instanz zerstört wird. Sie wird häufig auch als Destruktor bezeichnet, obwohl es sich eigentlich nicht um den Destruktor handelt. Wenn man eine Instanz einer Klasse mit del löscht, wird die Methode __del__ vor dem eigentlichen Destruktor aufgerufen. Allerdings nur, falls es keine weitere Referenz auf diese Instanz gibt. Destruktoren werden selten benutzt, da man sich normalerweise nicht um das Aufräumen im Speicher kümmern muss. Im Folgenden sehen wir ein Beispiel mit __init__ und __del__: class Roboter(): def __init__(self, name): print(name + " wurde erschaffen!") def __del__(self): print ("Roboter wurde zerstört") if __name__ == "__main__": x = Roboter("Tik-Tok") y = Roboter("Jenkins") z = x print("Deleting x") del x print("Deleting z") del z del y Obiges Programm liefert folgendes Ergebnis: Tik-Tok wurde erschaffen! Jenkins wurde erschaffen! Deleting x Deleting z Deleting Robot Deleting Robot Die Verwendung der __del__-Methode ist sehr problematisch. Kommt man auf die Idee, den Robotern „ein persönliches Ende” zu bereiten wie im folgenden Programm, erhalten wir eine Fehlermeldung: class Roboter(): def __init__(self, name): print(name + " wurde erschaffen!") 211 212 21 Grundlegende Aspekte Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __del__(self): print(self.name + " sagt bye-bye. ") print("Es gibt " + self.name + " ihn nun nicht mehr!") if __name__ == "__main__": x = Roboter("Tik-Tok") y = Roboter("Jenkins") z = x print("Deleting x") del x print("Deleting z") del z del y Wir erhalten eine Ausgabe mit Fehlermeldungen: Tik-Tok wurde erschaffen! Jenkins wurde erschaffen! Deleting x Deleting z Exception AttributeError: "'Roboter' in <bound method Roboter.__del__ xb71c664c>> ignored Exception AttributeError: "'Roboter' in <bound method Roboter.__del__ object has no attribute 'name'" of <__main__.Roboter object at 0 object has no attribute 'name'" of <__main__.Roboter object at 0 xb71d732c>> ignored Wir greifen auf das Attribut „name” zu, was aber in __del__ in diesem Fall bereits nicht mehr vorhanden ist. Wir werden später nochmals intensiv auf diese Problematik eingehen. 21.10 21.10.1 Datenkapselung, Datenabstraktion und Geheimnisprinzip Definitionen Unter Datenkapselung versteht man den Schutz von Daten bzw. Attributen vor dem unmittelbaren Zugriff. Der Zugriff auf die Daten bzw. Attribute erfolgt meistens über entsprechende Methoden, die man auch als Zugriffsmethoden bezeichnet. Diese Methoden dienen dazu, invariante Interfaces für die Klassenbenutzung zu schaffen und Implementierungsdetails zu verbergen. Dadurch soll gewährleistet werden, dass man jederzeit Änderungen an der Implementierung vornehmen kann, ohne dass sich die Benutzersicht, also das Interface, ändert. Deshalb werden in der OOP für Attribute meistens zwei Zugriffsme- 21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. thoden bereitgestellt: eine Methode, die einem den Wert des Attributs liefert – als Getter11 oder als Abfragemethode bezeichnet –, und eine andere, mit deren Hilfe man den Wert eines Attributs verändern kann – als Setter12 oder als Änderungsmethode benannt. Wir werden in den weiteren Ausführungen zur OOP zeigen, dass es in Python eine überlegene Alternative zu dem „zwanghaften” Getter-Setter-Vorgehen von Java und C++ gibt. Bild 21.5 Datenabstraktion = Datenkapselung + Geheimnisprinzip Wenn wir von „Schutz von Daten bzw. Attributen vor dem unmittelbaren Zugriff” reden, dann bedeutet das natürlich nicht automatisch auch, dass die Daten außen nicht sichtbar sind. Ist dies gewünscht, dann spricht man vom Geheimnisprinzip13 . Unter dem Geheimnisprinzip versteht man also das Verbergen von internen Informationen und Implementierungsdetails nach außen. Eine andere Sichtweise des Geheimnisprinzips besagt, dass die Benutzer einer Klasse nicht mit unnötigen Implementierungsdetails belastet werden. Die außen unbedingt nötigen Daten und Informationen werden nur über definierte Schnittstellen bzw. Methoden nach außen sichtbar gemacht. Sind Daten „sichtbar”, bedeutet das allerdings nicht in jedem Fall, dass diese auch benutzbar, also lesbar bzw. veränderbar sind. Kann man auf Daten nicht zugreifen, spricht man von Datenkapselung. So gesehen beleuchten Datenkapselung und Geheimnisprinzip die zwei Seiten einer Medaille. Beide dienen dazu zu verhindern, dass Anwender der Klasse Implementierungsdetails nutzen. Um beiden Seiten gerecht zu werden, benutzen viele deshalb auch den allgemeineren Begriff „Datenabstraktion”. Man könnte folgende Gleichung aufstellen: Datenabstraktion = Datenkapselung + Geheimnisprinzip Allerdings werden die drei Begriffe in der Literatur zumeist synonym verwendet, was zu einem bestimmten Grad auch für dieses Buch gilt. Wie sieht es nun mit der Datenabstraktion in unserer Beispielklasse aus? Betrachten wir dazu die folgende interaktive Python-Session. Wir nehmen dabei an, dass unsere Roboterklasse als Datei unter dem Namen roboter.py existiert: >>> from roboter import Roboter >>> x = Roboter("Marvin", 1979) >>> print(x.name) Marvin 11 12 13 Englisch „to get” im Sinne von etwas „holen” oder „besorgen” Englisch „to set” im Sinne von etwas „festsetzen”, „festlegen” oder „einstellen” Im Englischen unter „Information Hiding” bekannt 213 214 21 Grundlegende Aspekte Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> x.name = "Egon" >>> print(x.name) Egon Wir sehen, dass wir als Benutzer der Klasse Roboter lesend und schreibend auf die Attribute zugreifen können. Dies ist auch in Ordnung so, da es sich bei den Attributen name und baujahr um öffentliche (engl. public) Attribute handelt, d.h. man kann und darf sie als Anwenderin oder Anwender der Klasse benutzen. Dies bedeutet, dass es in unserer Beispielklasse keine Datenabstraktion gibt. Es gab hierzu auch noch keine Notwendigkeit. Stellen wir uns nun vor, dass uns als Designer der Roboterklasse eine Beschwerde der Robotergewerkschaft erreicht. Sie verlangen, dass wir verhindern, dass Roboter „Egon” genannt werden, da sie diesen Namen als nicht statthaft für Roboter empfinden. Zur Lösung dieses Problems gibt es in Python generell zwei Möglichkeiten. Einmal kann man es über spezielle Zugriffsmethoden, die Änderungsmethoden (auch Getter genannt) und die Abfragemethoden (häufig auch als Setter-Methoden bezeichnet) bewerkstelligen; zum anderen kann man – was der pythonische Weg ist – dies mit Properties erreichen. 21.10.2 Zugriffsmethoden Die eigentlichen Attribute, also in unserem Beispiel der Name und das Baujahr, werden nun in private-Attributen versteckt, d.h. __name. Zwei Unterstriche vor einem Attributnamen machen den Namen zu einem privaten Attribut. Damit können Benutzer der Klasse nicht mehr darauf zugreifen. Dennoch kann man die Werte als Benutzer der Klasse über die Zugriffsmethoden abfragen und verändern. In unserem Fall get_name und set_name. Wir sehen im folgenden Programm, dass wir auch in der __init__-Methode set_name benutzen, da wir auch dort sicherstellen wollen, dass ein Roboter nicht „Egon” genannt werden kann. Das Problem mit der Robotergewerkschaft haben wir gelöst, indem ein Roboter zwangsweise in „Marvin” getauft wird, falls ihn jemand „Egon” nennen sollte. class Roboter: def __init__(self, name, baujahr): self.set_name(name) self.baujahr = baujahr def sage_hallo(self): print("Hallo, mein Name ist " + self.get_name()) def get_name(self): return self.__name def set_name(self, name): if name == "Egon": self.__name = "Marvin" else: self.__name = name 21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip if __name__ == "__main__": x = Roboter("Egon", 1979) x.sage_hallo() x.set_name("Henry") x.sage_hallo() Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Als Ergebnis erhalten wir: Hallo, mein Name ist Marvin Hallo, mein Name ist Henry Mit diesem Vorgehen haben wir nun zwar die Robotergewerkschaft zufriedengestellt, aber wir haben die Prinzipien des objektorientierten Programmierens verletzt. Eine Klasse kann man auch als Schnittstelle ansehen, die zwar in neuen Versionen durch Hinzunahme von weiteren Methoden erweitert werden darf, aber bestehende Methoden oder Attribute dürfen ihr Schnittstellenverhalten nicht ändern. In unserem Fall ist dies aber passiert. Wir hatten ein public-Attribut name, was es nun nicht mehr gibt. Dies bedeutet, dass Programme, die unsere Roboter-Klasse benutzen (zum Beispiel Zuweisungen der Art y = x.name" enthalten) Ausnahmen erzeugen. Im nächsten Unterkapitel lernen wir die Properties kennen, mit denen sich dieses Problem vermeiden lässt. 21.10.3 Properties Wir haben unsere Roboter-Klasse so abgeändert, dass wir auf den Roboternamen nur noch über die Methoden get_name und set_name zugreifen können. Stellen wir uns vor, dass wir einen Roboter x und y haben. Nun soll x so wie y heißen. Dies können wir mit der Anweisung x.set_name(y.get_name()) erreichen. Deutlich bequemer und vielleicht auch ein wenig leichter lesbar gestaltete sich die Umbenennung, als wir noch direkt auf ein public-Attribut zugreifen konnten, also als wir die Umbenennung über x.name = y.name vornehmen durften. Mit den Properties können wir diese bequeme Schreibweise wieder ermöglichen, ohne die Kapselung des privaten Attributs __name zu verletzen. Durch den Aufruf name = property(get_name, set_name) erzeugen wir das propertyObjekt name. Das erste Argument von property muss der Name des Getters sein und das zweite entsprechend der Name des Setters. Man kann nun name wie ein public-Attribut nutzen, aber in Wirklichkeit werden entsprechend die Getter und Setter und Funktionen verwendet. Wenn wir also x.name = y.name schreiben, dann wird diese Zuweisung über den Property-Mechanismus in x.set_name(y.get_name()) gewandelt. Auch wenn es nun wieder so aussieht, als erzeugten wir in der __init__-Methode ein public-Attribut name, handelt es sich auch hier um den Aufruf der Property, d.h. es verhält sich so, als hätten wir self.set_name(name) geschrieben. 215 216 21 Grundlegende Aspekte class Roboter: def __init__(self, name, baujahr): self.name = name self.baujahr = baujahr Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def sage_hallo(self): print("Hallo, mein Name ist " + self.name) def get_name(self): return self.__name def set_name(self, name): if name == "Egon": self.__name = "Marvin" else: self.__name = name name = property(get_name, set_name) if __name__ == "__main__": x = Roboter("Marvin", 2017) y = Roboter("Eva", 2017) x.sage_hallo() x.name = "Adam" y.name = x.name y.sage_hallo() Wenn wir das Programm laufen lassen, erkennen wir, dass unsere Namensumbenennung erfolgreich gewesen ist: Hallo, mein Name ist Marvin Hallo, mein Name ist Adam In unserer obigen Implementierung gibt es aber noch einen Design-Fehler. Klassennutzer können nun sowohl über die Property name als auch über die Methoden get_name und set_name den Namen eines Roboters lesen und ändern. Es ist aber ein Grundsatz der Python-Philosophie, dass es nur einen Weg geben sollte. Wir erreichen dies, indem wir die beiden Methoden zu privaten machen. Bevor wir jedoch unser Beispiel entsprechend anpassen, wollen wir zunächst die Unterschiede zwischen privaten, geschützten und öffentlichen Attributen im folgenden Kapitel erklären. 21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.10.4 Public-, Protected- und Private-Attribute Wer kennt nicht die Klischees in Filmen und Erzählungen von schießwütigen Farmern, die sofort losballern, wenn jemand ihr Grundstück betritt. Natürlich hat diese Person willentlich oder auch versehentlich das ansonsten kaum zu übersehene Schild mit der Aufschrift „Private Propterty NO Trespassing” übersehen. Besonders irritierend ist dabei häufig die Bild 21.6 No Tatsache, dass die Grundstücke in keinster Weise eingezäunt sind. Aber Trespassing es gibt auch die Variante, in denen die Grundstücke mit hohen Zäunen, manchmal gar mit Stacheldraht gesichert sind. Aber egal ob der Zugang durch bauliche Maßnahmen erschwert wurde oder nicht, sobald man ein solches Grundstück betritt, begeht man Hausfriedensbruch.14 Im Gegensatz zum Privatbesitz, zu dem einem der Zutritt verwehrt werden kann, gibt es auch öffentliche Räume wie Straßen oder Plätze, die von allen genutzt werden dürfen. Dazwischen gibt es noch eine besondere Form: Privatbesitz darf genutzt werden, aber nur auf eigene Gefahr. Sie ahnen es sicherlich schon. Wir brauchen diese Arten von Zugangsberechtigungen für unsere Attribute. Wählt man Namen ohne führenden oder führende Unterstriche als Attribute einer Klasseninstanz, so sind diese öffentlich zugänglich, d.h. von Bild 21.7 Auf außen, also außerhalb der Klassendefinition. Wichtig ist vor allen Dingen eigene Gefahr die Tatsache, dass sie nicht nur von außen, also von den Benutzern der Klasse, genutzt werden können, sondern dass es sich bei dieser Nutzung um einen ordnungsgemäßen Gebrauch der Klasse handelt. Möchte man verhindern, dass ein Attribut lesend oder schreibend von den Benutzern der Klasse genutzt werden kann, stellt Python zwei Möglichkeiten zur Verfügung. Jedes Attribut, welches mit genau einem Unterstrich beginnt, ist „protected”. In diesem Fall kann man zwar immer noch von außen lesend und schreibend auf das Attribut zugreifen, aber durch den Unterstrich hat man klar gemacht, dass dies „verboten” oder „nicht erwünscht” ist. Dies entspricht in etwa unserem „No Trespassing”-Schild auf einem Grundstück, das ansonsten keinerlei bauliche Maßnahmen hat, die ein Eindringen verhindern. Die „baulichen Maßnahmen” erfolgen erst, wenn man den Namen eines Attributs mit zwei Unterstrichen beginnen lässt. Ein solches Attribut ist ein „private”-Attribut, auf das von außen nicht zugriffen werden kann.15 In der Tabelle auf der nächsten Seite haben wir die verschiedenen Attributarten nochmals zusammengefasst: Schauen wir uns das Verhalten der verschiedenen Attribute in einer Beispielklasse an: class A(): def __init__(self): self.__priv = "Ich bin privat" self._prot = "Ich bin protected" self.pub = "Ich bin öffentlich" 14 15 Naja, ist sicherlich nicht ganz juristisch korrekt, und wir haben auch bewusst offen gelassen, um welches Land oder welchen Staat es geht. Es gibt jedoch einen Weg, wie man dennoch von außen zugreifen kann: Auf ein Attribut wie in unserem Beispiel __baujahr kann man für eine Instanz x mittels x._Roboter__baujahr zugreifen. Dies sollte man jedoch keinesfalls tun! 217 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 218 21 Grundlegende Aspekte Namen Bezeichnung Bedeutung name Public Attribute ohne führende Unterstriche sind sowohl innerhalb einer Klasse als auch von außen les- und schreibbar. _name Protected Man kann zwar auch von außen lesend und schreibend zugreifen, aber der Entwickler macht damit klar, dass man diese Member nicht benutzen sollte. Protected-Attribute sind insbesondere bei Vererbungen wichtig. __name Private Sind von außen nicht sichtbar und nicht benutzbar. Obige Beispielklasse speichern wir unter attributes.py und testen sie wie folgt: >>> from attributes import A >>> x = A() >>> x.pub 'Ich bin öffentlich' >>> x.pub = "Man kann meinen Wert ändern und das ist gut so" >>> x.pub 'Man kann meinen Wert ändern und das ist gut so' >>> >>> x._prot 'Ich bin protected' >>> x._prot = "Das darf man nur in erbenden Klassen tun!" >>> x._prot 'Das darf man nur in erbenden Klassen tun!' >>> >>> x.__priv Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object has no attribute '__priv' Interessant ist vor allen Dingen die Fehlermeldung AttributeError: ’A’ object has no attribute ’__priv’. Eigentlich würde man als Fehlermeldung erwarten, dass man auf das Attribut __priv nicht zugreifen darf, da es „private” ist. Stattdessen kommt die obige Meldung, die so aussieht, als gäbe es kein Attribut __priv in der Klasse A. 21.10.5 Weitere Möglichkeiten der Properties Wir kommen nun zurück auf unser Beispiel vom Unterkapitel 21.10.3 (Properties): class Roboter: def __init__(self, name, baujahr): self.name = name self.baujahr = baujahr 21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip def sage_hallo(self): print("Hallo, mein Name ist " + self.name) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __get_name(self): return self.__name def __set_name(self, name): if name == "Egon": self.__name = "Marvin" else: self.__name = name name = property(__get_name, __set_name) Aus dem bisher Gesagten und aus den ansonsten in der Literatur und anderen Tutorials vorherrschenden Beispielen könnte man leicht den Eindruck gewinnen, dass es zwischen Properties und privaten Attributen immer eine Eins-zu-eins-Beziehung gibt. Also zu einer Property gibt es genau ein privates Attribut und umgekehrt. Dass dem nicht so ist, wollen wir im folgenden Beispiel zeigen. Wir definieren dazu zwei private Attribute self.__leistung_koerper, als Maß für das körperliche Wohlbefinden, und self.__leistung_psyche, als Maß für das psychische Wohlbefinden. Die Property „befinden” verknüpft dann diese beiden privaten Attribute unter Benutzung der privaten Methode __befinden, die in salopper Weise das allgemeine Wohlbefinden des Roboters verkündet: class Roboter: def __init__(self, name, baujahr, lk = 0.5, lp = 0.5 ): self.name = name self.baujahr = baujahr self.__leistung_koerper = lk self.__leistung_psyche = lp def __befinden(self): s = self.__leistung_koerper + self.__leistung_psyche if s <= -1: return "Fühle mich miserabel!" elif s <= 0: return "Fühle mich schlecht!" elif s <= 0.5: return "Könnte besser sein!" elif s <= 1: return "Es geht mir gut!" else: return "Super!" befinden = property(__befinden) 219 220 21 Grundlegende Aspekte if __name__ == "__main__": x = Roboter("Marvin", 1979, 0.2, 0.4 ) y = Roboter("Caliban", 1993, -0.4, 0.3) print(x.befinden) print(y.befinden) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Obiges Programm liefert folgende Ergebnisse zurück: Es geht mir gut! Fühle mich schlecht! Eine Property kann neben Getter und Setter auch eine Deleter-Methode als drittes Argument sowie einen Docstring als viertes Argument aufnehmen: property(fget=None, fset=None, fdel=None, doc=None) Wir demonstrieren dies im folgenden Beispiel: class P: def __init__(self,x): self.__x = x print(x) def __getX(self): try: return self.__x except AttributeError: return None def __setX(self, x): self.__x = x print(hasattr(self,"__x")) def __delX(self): del self.__x doc = "Die Eigenschaft x, wozu auch immer sie gut sein mag!" x = property(__getX, __setX, __delX, doc) Importieren wir das Programm mit from properties import P16 , erhalten wir mit help(P) am Schluss folgende Ausgabe: | Data and other attributes defined here: | | doc = 'Die Eigenschaft x, wozu auch immer sie gut sein mag!' (END) 16 Programm muss unter properties.py abgespeichert sein. 21.10 Datenkapselung, Datenabstraktion und Geheimnisprinzip Wegen der Deleter-Methode mussten wir die __getX-Methode umschreiben, da es nun möglich ist, dass das Attribut __x nicht existiert, wenn es vorher durch __delX gelöscht worden ist. Wir geben nun None zurück, wenn das Attribut __x nicht existiert. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.10.6 Properties mit Dekorateuren Properties lassen sich mithilfe von Dekorateuren deutlich einfacher schreiben. Wir demonstrieren dies exemplarisch an unserer Roboter-Klasse. Die Namen der Getter- und der Setter-Funktionen benennt man in den gewünschten Namen der Property um, also in unserem Fall schreiben wir name statt __get_name und __set_name. Außerdem dekorieren wir die Getter-Methode mit @property, d.h. wir schreiben diese Zeile über den Funktionsheader. Die Setter-Methode wird mit "@" + gewünschter Property-Name + "setter" dekoriert. Die Zeile name = property(__get_name, __set_name) ist nun nicht mehr nötig bzw. nicht mehr möglich. Der Code für unsere Roboter-Klasse sieht nun wie folgt aus: @property def name(self): return self.__name @name.setter def name(self, name): if name == "Egon": self.__name = "Marvin" else: self.__name = name Die P-Klasse, die auch einen deleter und einen Docstring enthält, können wir wie folgt mit der Dekorationstechnik schreiben. In diesem Fall dient der Docstring der Setter-Methode als Docstring für die Property: class P: def __init__(self,x): self.x = x @property def x(self): """ Die Eigenschaft x, wozu auch immer sie gut sein mag!""" try: return self.__x except AttributeError: return None @x.setter def x(self, x): self.__x = x 221 222 21 Grundlegende Aspekte @x.deleter def x(self): del self.__x Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.11 __str__- und __repr__ Wir schauen uns nun an, was passiert, wenn wir eine Roboter-Instanz in einer printFunktion verwenden: >>> x = Roboter("Marvin", 2017) >>> print(x) <robots.Roboter object at 0x7f5e5e9110b8> Es ist erfreulich, dass wir keinen Fehler erzeugt haben, aber die Ausgabe ist weder schön noch aussagekräftig. Die print-Funktion kann eigentlich nur Strings ausgeben; deshalb hat print str(x) aufgerufen, die dann obigen String generiert hatte. Die str-Funktion ist uns im Laufe des Buches schon öfters begegnet. Wir hatten gesehen, dass wir mit ihr verschiedenste Datentypen in eine Stringdarstellung wandeln können. Es gibt noch eine weitere Funktion, die eine Stringdarstellung eines Objekts zurückliefert. Dies ist die repr-Funktion. In vielen Fällen sind die Ergebnisse beider Funktionen sogar identisch. >>> farben = ["rot", "grün", "blau"] >>> str(farben) "['rot', 'grün', 'blau']" >>> repr(farben) "['rot', 'grün', 'blau']" >>> print(farben) ['rot', 'grün', 'blau'] >>> s = "Aller Anfang\nist schwer!" >>> str(s) 'Aller Anfang\nist schwer!' >>> repr(s) "'Aller Anfang\\nist schwer!'" >>> print(s) Aller Anfang ist schwer! Wendet man auf ein Objekt die Funktion str oder repr an, sucht Python in der Klassendefinition dieses Objekts nach Methoden mit den entsprechenden Namen __str__ und __repr__. Sind sie vorhanden, werden sie entsprechend aufgerufen. Im Folgenden definieren wir eine Klasse A, in der wir weder __repr__ noch __str__ definieren. In diesem Fall wird sowohl bei str und repr, aber auch bei einem einfachen print oder der direkten Ausgabe über die interaktive Shell, eine Default-Ausgabe gewählt, d.h. <__main__.A object at 0xb720a64c>: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.11 Die __str__- und die __repr__-Methode >>> class A: ... pass ... >>> a = A() >>> print(repr(a)) <__main__.A object at 0xb720a64c> >>> print(str(a)) <__main__.A object at 0xb720a64c> >>> a <__main__.A object at 0xb720a64c> Besitzt eine Klasse eine Funktion __str__, dann wird diese angewendet, wenn auf ein Objekt dieser Klasse str angewendet oder wenn ein Objekt dieser Klasse in einer printFunktion verwendet wird. __str__ wird jedoch nicht angewendet, wenn man repr auf ein Objekt anwendet oder wenn man sich direkt den Wert eines Objekts in der interaktiven Python-Shell angeben lässt: >>> class A: ... def __str__(self): ... return "42" ... >>> a = A() >>> print(repr(a)) <__main__.A object at 0x7f3c46b54710> >>> print(str(a)) 42 >>> a <__main__.A object at 0x7f3c46b54710> Besitzt eine Klasse nur eine Funktion __repr__, aber nicht __str__, dann wird __repr__ immer angewendet, d.h. wenn auf ein Objekt dieser Klasse str oder repr angewendet wird oder wenn ein Objekt dieser Klasse in einer print-Funktion oder direkt in der Python-Shell für eine Ausgabe verwendet wird: >>> ... ... ... >>> >>> 42 >>> 42 >>> 42 class A: def __repr__(self): return "42" a = A() print(repr(a)) print(str(a)) a Eine häufig gestellte Frage lautet, wann man __repr__ und wann __str__ benutzen bzw. implementieren sollte. __str__ ist immer dann die richtige Methode, wenn es um die Ausgabe der Daten für Endbenutzer geht. __repr__ hingegen ist wichtig, wenn es um die in- 223 224 21 Grundlegende Aspekte terne Darstellung von Daten geht. Wenn wir auf ein Objekt o die Funktion repr anwenden, dann erhalten wir eine Stringrepräsentation des Objekts o, aus der man durch Anwendung der Funktion eval wieder das ursprüngliche Objekt o erzeugen kann, genauer gesagt erzeugt man natürlich eine Kopie des ursprünglichen Objekts o. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Es gilt also: type(o) == type(eval(repr(o))) Eigentlich müsste auch o == eval(repr(o)) gelten, aber da wir noch keine Vergleichsmethoden für unsere Klassenobjekte definiert haben, wird nur die Gleichheit auf die ObjektID geprüft, d.h. Kopien von Objekten gelten also als ungleich. Obige Typgleichheit gilt nicht in jedem Fall, aber dennoch für die meisten Standardklassen. Bei __repr__ liegt der Fokus auf der Eindeutigkeit und Unmissverständlichkeit der Datendarstellung! Ideal ist, wenn sich ein durch repr erzeugter String wieder mittels eval in eines dem ursprünglichen Objekt äquivalentes Objekt wandeln lässt. str andererseits soll einen für menschliche Benutzer leicht verständlichen String liefern! Das bisher Gesagte wollen wir nochmals an einem Beispiel verdeutlichen. Dazu benutzen wir das datetime-Modul: >>> import datetime >>> today = datetime.datetime.now() >>> str_s = str(today) >>> eval(str_s) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 2017-07-12 14:29:37.235032 ^ SyntaxError: invalid token >>> repr_s = repr(today) >>> t = eval(repr_s) >>> type(t) <class 'datetime.datetime'> Wir sehen, dass wir den String repr_s , den wir mittels repr erhalten, wieder mit eval in ein datetime.datetime-Objekt wandeln können. Den durch str erzeugten String str_s kann man jedoch nicht mehr rückwandeln. Wir können nun unsere Roboterklasse um eine repr-Methode erweitern. Um die Darstellung übersichtlich zu halten, haben wir im folgenden Code alle anderen vorher definierten Methoden weggelassen: class Roboter: def __init__(self, name, baujahr): self.name = name self.baujahr = baujahr def __repr__(self): return "Roboter('"+self.name+"',"+ str(self.baujahr)+ ")" if __name__ == "__main__": x = Roboter("Marvin", 1979) 21.11 Die __str__- und die __repr__-Methode Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. x_str = str(x) print(x_str) print("Typ von x_str: ", type(x_str)) neu = eval(x_str) print(neu) print("Typ von neu:", type(neu)) Die Anweisung print(x_str) gibt den String Roboter("Marvin",1979) aus. Wir können diesen String wieder mittels eval(x_str) in eine Instanz neu der Klasse Roboter wandeln. Starten wir das Programm, erhalten wir folgende Ausgaben: Roboter("Marvin",1979) Typ von x_str: <class 'str'> Roboter("Marvin",1979) Typ von neu: <class '__main__.Roboter'> Nun erweitern wir unsere Klasse noch um eine benutzerfreundliche __str__-Methode: class Roboter: def __init__(self, name, baujahr): self.name = name self.baujahr = baujahr def __repr__(self): return "Roboter('"+self.name+"',"+ str(self.baujahr)+ ")" def __str__(self): return "Name: "+self.name+", Baujahr: "+ str(self.baujahr) if __name__ == "__main__": x = Roboter("Marvin", 1979) x_str = str(x) print(x_str) print("Typ von x_str: ", type(x_str)) neu = eval(x_str) print(neu) print("Typ von neu:", type(neu)) Wenn wir das Programm starten, erhalten wir einen Fehler, da sich der String x_str nun nicht mehr mittels eval in ein Objekt der Klasse Roboter wandeln lässt. Name: Marvin, Baujahr: 1979 Typ von x_str: <class 'str'> Traceback (most recent call last): File "/home/data/workspace/PyRobots/robots3.py", line 20, in <module > neu = eval(x_str) 225 226 21 Grundlegende Aspekte File "<string>", line 1 Name: Marvin, Baujahr: 1979 SyntaxError: invalid syntax ^ Dies liegt daran, dass wir nun eine eigene __str__-Methode definiert haben, die eine andere Ausgabe erzeugt, die sich nicht mehr mit eval verwenden lässt. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im folgenden Programm erzeugen wir mit der Anweisung x_repr = repr(x) einen „evalfähigen” String: class Roboter: def __init__(self, name, baujahr): self.name = name self.baujahr = baujahr def __repr__(self): return "Roboter('"+self.name+"',"+ str(self.baujahr)+ ")" def __str__(self): return "Name: "+self.name+", Baujahr: "+ str(self.baujahr) if __name__ == "__main__": x = Roboter("Marvin", 1979) x_str = str(x) print(x_str) print("Typ von x_str: ", type(x_str)) x_repr = repr(x) print(x_repr, type(x_repr)) neu = eval(x_repr) print(neu) print("Typ von neu:", type(neu)) Dieses Programm liefert uns nun die gewünschten Ausgaben: Name: Marvin, Baujahr: 1979 Typ von x_str: <class 'str'> Roboter("Marvin",1979) <class 'str'> Name: Marvin, Baujahr: 1979 Typ von neu: <class '__main__.Roboter'> Wir erweitern nun unsere Roboterklasse um die Methode __eq__. Diese Methode wird aufgerufen, wenn zwei Roboterobjekte mittels == auf Gleichheit getestet werden. class Roboter: def __init__(self, name, baujahr): self.name = name self.baujahr = baujahr 21.12 Klassenattribute def __repr__(self): return "Roboter('" + self.name + \ "',"+ str(self.baujahr)+ ")" Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __str__(self): return "Name: " + self.name + \ ", Baujahr: "+ str(self.baujahr) def __eq__(self, other): return self.name == other.name and self.baujahr == other. baujahr if __name__ == "__main__": x = Roboter("Marvin", 1979) neu = eval(repr(x)) print(x == neu) print(id(x) == id(neu)) Die Ausgabe des Programms zeigt nun, dass wir repr korrekt implementiert haben, d.h. das nun die Bedingung x == eval(repr(x)) erfüllt ist: True False 21.12 Klassenattribute Bisher hatte jede Instanz einer Klasse ihre eigenen Attribute, die sich von denen anderer Instanzen unterschieden. Man bezeichnet dies als „nicht-statisch” oder „dynamisch”, da sie für jede Instanz einer Klasse dynamisch erstellt werden. So hatten wir beispielsweise den Namen eines Roboters mithilfe des Instanzattributs self.__name gespeichert. Instanzattribute sind Attribute, die für jede Instanz in der Regel einen verschiedenen Wert annehmen, so wie ja jeder Roboter sinnvollerweise einen anderen Namen haben sollte. Bild 21.8 Asimovs Gesetze als Klassenattribut Wie kann man jedoch Informationen speichern, die sich nicht auf ein bestimmtes Objekt beziehen, sondern für die ganze Klasse relevant sind? Also Attribute, die für alle Instanzen gleich sind. Solche Attribute könnten für unsere Roboterklasse beispielsweise der Name des Herstellers, die Anzahl aller erzeugten Roboter oder wie in unserem folgenden Beispiel 227 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 228 21 Grundlegende Aspekte die Asimovschen Gesetze17 sein. Diese Attribute bezeichnet man als statische Attribute. Sie existieren unabhängig von den Instanzen oder anders ausgedrückt sind sie für alle Instanzen gleich. Statische Attribute werden auch als Klassenattribute bezeichnet, weil sie, wie bereits gesagt, Eigenschaften bezeichnen, die für die ganze Klasse gelten und nicht nur für einzelne Objekte der Klasse. Ein Klassenattribut existiert pro Klasse nur einmal, wird also nur einmal angelegt. Instanzattribute werden für jedes Objekt angelegt. Statische Attribute werden außerhalb der Instanzmethoden direkt im class-Block definiert. Außerdem muss jedem Klassenattribut ein Initialwert zugewiesen werden. Es ist Usus, die statischen Member direkt unterhalb der class-Anweisung zu positionieren. Wir könnten beispielsweise die Asimovschen Gesetze als Klassenattribut in unserer Roboterklasse formulieren, denn diese Gesetze gelten für jede Instanz, also für jeden Roboter, gleichermaßen. Wir benutzen für die Implementierung ein Tupel mit Strings, d.h. jedes Element des Tupels entspricht einem Gesetz: class Roboter: Gesetze = ( """Ein Roboter darf kein menschliches Wesen wissentlich verletzen oder durch Untätigkeit gestatten, dass einem menschlichen Wesen wissentlich Schaden zugefügt wird.""", """Ein Roboter muss den ihm von einem Menschen gegebenen Befehlen gehorchen - es sei denn, ein solcher Befehl würde mit Regel eins kollidieren.""", """Ein Roboter muss seine Existenz beschützen, so lange dieser Schutz nicht mit Regel eins oder zwei kollidiert.""") def __init__(self, name, baujahr): self.__name = name self.__baujahr = baujahr # weitere Definitionen wie gehabt Wir können entweder direkt über den Klassennamen mit print(Roboter.Gesetze) oder über eine Instanz eines Roboters auf die Robotergesetze zugreifen: >>> from robots import Roboter >>> for nummer, text in enumerate(Roboter.Gesetze): ... print(str(nummer+1) + ":\n" + text) ... 1: Ein Roboter darf kein menschliches Wesen wissentlich verletzen oder durch Untätigkeit gestatten, dass einem menschlichen Wesen wissentlich Schaden zugefügt wird. 2: Ein Roboter muss den ihm von einem Menschen gegebenen 17 Isaac Asimov formulierte in seiner Kurzgeschichte „Runaround” (Astounding, 1942) die Robotergesetze. Sie werden deshalb auch als „Asimovsche Gesetze” bezeichnet. 21.13 Statische Methoden Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Befehlen gehorchen - es sei denn, ein solcher Befehl würde mit Regel eins kollidieren. 3: Ein Roboter muss seine Existenz beschützen, so lange dieser Schutz nicht mit Regel eins oder zwei kollidiert. Im folgenden Beispiel zeigen wir, wie man Instanzen mittels einer Klassenvariablen zählen kann. Dazu erhöhen wir die Variable counter bei der Initialisierung jeder neuen Instanz. Wird eine Instanz gelöscht, wird die Methode __del__ aufgerufen, in der in unserem Beispiel die Klassenvariable counter um 1 vermindert wird: class C: counter = 0 def __init__(self): type(self).counter += 1 def __del__(self): type(self).counter -= 1 if __name__ == "__main__": x = C() print("Anzahl der Instanzen: y = C() print("Anzahl der Instanzen: del x print("Anzahl der Instanzen: del y print("Anzahl der Instanzen: " + str(C.counter)) " + str(C.counter)) " + str(C.counter)) " + str(C.counter)) Im Prinzip hätten wir auch C.counter statt type(self).counter schreiben können, denn type(self) wird zu „C” ausgewertet. Wir erhalten folgende Ausgabe: $ python3 counter.py Anzahl der Instanzen: Anzahl der Instanzen: Anzahl der Instanzen: Anzahl der Instanzen: 21.13 1 2 1 0 Statische Methoden Der Dekorator zur Erzeugung von statischen Methoden ist sehr umstritten in der PythonWelt. Die Kritik konzentriert sich vor allem darauf, dass es keine praktischen Anwendungen für diese Dekoration gäbe. In den meisten Fällen wird diese Technik für Funktionen 229 230 21 Grundlegende Aspekte verwendet, die nichts mit den Instanzen der Klasse zu tun haben, die man aber dennoch gerne innerhalb der Klasse definieren will, weil man sie dort gut gebrauchen kann. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.13.1 Einleitendes Beispiel Wir definieren eine Funktion die_antwort18 und eine Klasse Philosoph. Beide haben nichts miteinander zu tun, außer dass sie im gleichen Modul definiert sind. Allerdings ist die_antwort natürlich eine wichtige Funktion für eine Philosophenklasse, da sie ja den Sinn des Lebens berechnet. def die_antwort(): return 42 class Philosoph: pass Machen wir die Funktion die_antwort zu einer Methode der Klasse Philosoph, dann können wir sie nur über eine Philosophen-Instanz aufrufen: class Philosoph: def die_antwort(self): return 42 plato = Philosoph() print(plato.die_antwort()) print(Philosoph.die_antwort(plato)) Das ergibt natürlich keinen Sinn, da die „Berechnungen” dieser Funktion nicht von Instanzen abhängen. Wir können jedoch die Methode die_antwort als @staticmethod dekorieren. Dann brauchen wir keine Instanz mehr zu definieren. Wir können die Funktion direkt über den Klassennamen ohne Instanzparameter aufrufen, wie wir im folgenden Beispiel sehen können: class Philosoph: @staticmethod def die_antwort(): return 42 print(Philosoph.die_antwort()) plato = Philosoph() print(plato.die_antwort()) 18 Im Roman „Per Anhalter durch die Galaxis” des Autors Douglas Adam ist „42” die von einem Supercomputer nach einigen Millionen Jahren Rechenzeit gegebene Antwort auf die Frage „nach dem Leben, dem Universum und dem ganzen Rest”. 21.13 Statische Methoden Wie wir sehen, können wir die Methode weiterhin über eine Instanz aufrufen – auch wenn das nicht sinnvoll ist. Der bessere Weg besteht im direkten Aufruf von Philosoph.die_antwort(). Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.13.2 Getter und Setter für private Klassenattribute Im vorigen Abschnitt hatten wir Klassenattribute als öffentliche Attribute verwendet. Selbstverständlich können und sollten wir auch Klassenattribute als private Attribute definieren können, also mit doppeltem vorangestellten Unterstrich. Das bedeutet aber, dass diese privaten Klassenattribute von außen nicht mehr gelesen oder manipuliert werden können. Soll dies möglich sein, müssen wir entsprechende Methoden bereitstellen. Dazu könnte man auch Instanzmethoden verwenden: class Roboter: __counter = 0 def __init__(self): Roboter.__counter += 1 @staticmethod def get_anzahl(): return Roboter.__counter @staticmethod def set_anzahl(anzahl): Roboter.__counter = anzahl if __name__ == "__main__": x = Roboter() print(x.get_anzahl()) y = Roboter() print(x.get_anzahl()) Roboter.set_anzahl(12) print(x.get_anzahl()) Wir erhalten die Ausgabe: 1 2 12 Neben statischen Methoden gibt es auch Klassenmethoden. Anders als statische Methoden sind Klassenmethoden an eine Klasse gebunden. Das erste Argument einer Klassenmethode ist eine Referenz auf die Klasse, d.h. das Klassenobjekt. Aufrufen kann man sie über den Klassennamen oder eine Instanz. Wir werden jedoch erst im Kapitel Vererbung auf Klassenmethoden zu sprechen kommen, da man zu ihrem Verständnis auch das Prinzip der Vererbung verstanden haben muss. 231 232 21 Grundlegende Aspekte Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 21.14 Public-Attribute statt private Attribute In den meisten objektorientierten Programmiersprachen ist es ein ungeschriebenes Gesetz, dass man möglichst alle Attribute als „private” deklariert und für sie „Getter” und „Setter” schreibt. Häufig sieht es dann aber so aus wie in unseren anfänglichen primitiven Beispielen. Mit dem Getter und Setter reichten wir den entsprechenden Wert nur durch, also zum Beispiel so: class A: def __init__(self, val): self.__x = val def getx(self): return self.__x def setx(self,val): self.__x = val Der ideale Python-Weg sieht wegen der Möglichkeiten, die sich durch die Properties ergeben, ganz anders aus. In Python definiert man diese Klasse zunächst einmal mit einem public-Attribut: class A: def __init__(self, val): self.x = val Wir können direkt – also ohne Benutzung von Methoden – auf dieses Attribut zugreifen, wie wir im Folgenden sehen: >>> a = A(10) >>> print(a.x) 10 >>> a.x = 102 Eine Horrorvorstellung für C++- oder Java-Programmierer, denn sie sehen Probleme, wenn man später doch Zugriffsfunktionen braucht, um bestimmte Tests oder Wandlungen in andere Datenformate vorzunehmen. Eine unberechtigte Sorge, denn man kann einfach ein public-Attribut in eine Property wandeln. Die eigentliche Information speichern wir in unserem Beispiel dann in einem privaten Attribut __x, für das wir Getter- und SetterMethoden schreiben. Diese Getter und Setter werden aber auch als private Methoden definiert, denn wir wollen die Information nur über die zu definierende Property x für die Benutzer der Klasse verfügbar machen.19 Die Property x verhält sich für die Benutzer der 19 Üblicherweise benutzt man die Dekoratorsyntax für Properties, die wir im letzten Abschnitt eingeführt haben. In der hier verwendeten Syntax erkennt man jedoch leichter den Zusammenhang zu den Gettern und Settern. 21.15 Magische Methoden und Operatorüberladung Klasse völlig gleich wie das bisherige öffentliche Attribut x. Sie haben also das Gefühl, es weiterhin mit einem öffentlichen Attribut zu tun zu haben. Stellen wir uns im obigen Beispiel vor, dass wir sicherstellen wollen, dass die Werte sich nur zwischen 0 und 100 bewegen dürfen.20 Wir schreiben unsere Klasse entsprechend um: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class A: def __init__(self, val): self.__x = val def __getx(self): return self.__x def __setx(self,val): if val > 100: self.__x = 100 else: self.__x = val x = property(__getx, __setx) Wie wir sehen, haben die Benutzer unserer Klasse immer noch die gleiche „Sichtweise”, d.h. das Interface ist gleich geblieben. Für sie gibt es weiterhin ein „öffentliches” Attribut x, obwohl es sich jetzt eigentlich um eine Property handelt: >>> a = A(10) >>> print(a.x) 10 >>> a.x = 102 21.15 21.15.1 Operatorüberladung Einführung Die sogenannten magischen Methoden sind weder obskur noch haben sie etwas mit Magie zu tun. Wir haben bereits einige kennengelernt. Instanzen werden beispielsweise mit der __init__-Methode initialisiert, nachdem eine andere Methode __new__, die man normalerweise nicht definieren muss, eine Instanz erzeugt hatte. 20 Wir gehen davon aus, dass numerische Werte übergeben werden! 233 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 234 21 Grundlegende Aspekte Bei der Benutzung einer Klasse erzeugt man eine Instanz, indem man nur den Klassennamen mit einer gegebenenfalls leeren Parameterliste aufruft, also beispielsweise x = A(). Diese Anweisung wird automatisch („magisch”) von Python durch entsprechende Aufrufe von __new__ und __init__ ersetzt. Andere „magische” Methoden, die wir bereits kennengelernt haben, sind __str__ und __repr__. Die Operatorüberladung ist uns bereits häufig im Laufe dieses Buches begegnet, ohne dass es uns vielleicht bewusst war. Wir haben beispielsweise das Plus-Zeichen „+” sowohl für die Addition von verschiedenen numerischen Werten, also Float- und Integer-Werten, aber auch für die Konkatenation von Strings benutzt: >>> a = 3 + 4 >>> a 7 >>> 3 + 5.543 8.543 >>> s = "Hello" >>> print(s + " World") Hello World Bild 21.9 Einige magische Methoden Bild 21.10 Überladen des +-Operators Python erlaubt es, dass wir auch für eigene Klassen den „+”-Operator überladen können. Um dies tun zu können, müssen wir jedoch den internen Mechanismus verstehen, der die Überladung bewirkt. Für jedes Operatorzeichen gibt es eine spezielle Methode. Für „+” lautet der Name der Methode beispielsweise __add__, und für „-” lautet der Name __sub__. Bei __add__ und __sub__ handelt es sich um binäre Operatoren, weshalb die Methoden auch zwei Parameter benötigen: „self” und „other”. Steht in einem Skript x + y und sind x und y von der Klasse K, dann ruft Python die Methode __add__ mit x.__add__(y) auf, falls es eine solche Methode in der Klasse K gibt, ansonsten erfolgt die Fehlermeldung Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'K' and 'K' 21.15 Magische Methoden und Operatorüberladung 21.15.2 Übersicht magische Methoden Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Binäre Operatoren: Operator Methode + object.__add__(self, other) - object.__sub__(self, other) * object.__mul__(self, other) / object.__truediv__(self, other) // object.__floordiv__(self, other) % object.__mod__(self, other) ** object.__pow__(self, other[, modulo]) « object.__lshift__(self, other) » object.__rshift__(self, other) & object.__and__(self, other) ^ object.__xor__(self, other) | object.__or__(self, other) Erweiterte Zuweisungen: Operator Methode += object.__iadd__(self, other) -= object.__isub__(self, other) *= object.__imul__(self, other) /= object.__itruediv__(self, other) //= object.__ifloordiv__(self, other) %= object.__imod__(self, other) **= object.__ipow__(self, other[, modulo]) «= object.__ilshift__(self, other) »= object.__irshift__(self, other) &= object.__iand__(self, other) ^= object.__ixor__(self, other) |= object.__ior__(self, other) Vergleichsoperatoren: Operator Methode < object.__lt__(self, other) <= object.__le__(self, other) == object.__eq__(self, other) != object.__ne__(self, other) >= object.__ge__(self, other) > object.__gt__(self, other) 235 236 21 Grundlegende Aspekte Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Unäre Operatoren: Operator Methode - object.__neg__(self) + object.__pos__(self) abs() object.__abs__(self) ~ object.__invert__(self) complex() object.__complex__(self) int() object.__int__(self) long() object.__long__(self) float() object.__float__(self) oct() object.__oct__(self) hex() object.__hex__(self) 21.15.3 Beispielklasse: Length In der folgenden Beispielklasse „Length” wollen wir exemplarisch zeigen, wie man für eine eigene Klasse eine Addition mittels des „+”-Operators durch Überladung der __add__Methode definieren kann. In der Klassendefinition befinden sich auch die magischen Methoden __str__ und __repr__, die wir bereits am Anfang des Kapitels besprochen hatten. Instanzen der Klasse Length sind Entfernungen. Die Attribute einer Instanz stellen die Länge der Strecke self.value und die Maßeinheit für diese Länge self.unit dar. Die Klasse ermöglicht einem das einfache Ausrechnen von Ausdrücken mit gemischten Maßeinheiten der folgenden Art: 2.56 m + 3 yd + 7.8 in + 7.03 cm Unter Benutzung unserer zu schreibenden Klasse sieht das dann so aus: >>> from unit_conversions import Length as L >>> print(L(2.56,"m") + L(3,"yd") + L(7.8,"in") + L(7.03,"cm")) 5.57162 Die Klasse sieht wie folgt aus: class Length: __metric = {"mm" : 0.001, "cm" : 0.01, "m" : 1, "km" : 1000, "in" : 0.0254, "ft" : 0.3048, "yd" : 0.9144, "mi" : 1609.344 } 21.15 Magische Methoden und Operatorüberladung def __init__(self, value, unit = "m" ): self.value = value self.unit = unit Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def converse2metres(self): return self.value * Length.__metric[self.unit] def __add__(self, other): l = self.converse2metres() + other.converse2metres() return Length(l / Length.__metric[self.unit], self.unit ) def __str__(self): return str(self.converse2metres()) def __repr__(self): return str((self.value, self.unit)) if __name__ == "__main__": x = Length(4) print(x) print(repr(x)) y = Length(4.5, "yd") + x print(repr(y)) print(y) Starten wir das Programm, erhalten wir folgende Ausgaben: 4 (4, 'm') (8.87445319335083, 'yd') 8.114799999999999 Wir benötigen die Methode __iadd__, um die erweiterte Zuweisung zu implementieren: def __iadd__(self, other): l = self.converse2metres() + other.converse2metres() self.value = l / Length.__metric[self.unit] return self Damit sind wir dann in der Lage, Zuweisungen der folgenden Art durchzuführen: x += Length(1) x += Length(4, "yd") In der ersten erweiterten Zuweisung wird 1 Meter zum Length-Objekt x hinzugezählt. Eigentlich wäre es doch viel schöner, wenn wir direkt eine Integer- oder eine Float-Zahl zu einem Length-Objekt hinzuzählen könnten, und unsere Implementierung würde diese Zahl gewissermaßen wie ein Length-Objekt in der Einheit „Meter” behandeln. Dies lässt sich 237 238 21 Grundlegende Aspekte Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ganz einfach realisieren. Dazu ändern wir lediglich unsere __add__-Methode so ab, dass sie den Typ von „other” überprüft: def __add__(self, other): if type(other) == int or type(other) == float: l = self.converse2metres() + other else: l = self.converse2metres() + other.converse2metres() return Length(l / Length.__metric[self.unit], self.unit ) def __iadd__(self, other): if type(other) == int or type(other) == float: l = self.converse2metres() + other else: l = self.converse2metres() + other.converse2metres() self.value = l / Length.__metric[self.unit] return self Wenn man mit dieser Klasse eine Weile arbeitet, stellt sich mit Sicherheit eine neue Begehrlichkeit ein. Versucht man, eine Integer- oder eine Float-Zahl auf der linken Seite und ein Length-Objekt auf der rechten Seite bei der Addition zu verwenden, erhält man eine Fehlermeldung: >>> from unit_conversions import Length >>> x = Length(3, "yd") + 5 >>> x = 5 + Length(3, "yd") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'Length' Auch für diese Problemstellung gibt es natürlich eine Lösung in Python, und zwar in Form der Methode __radd__. Führt der Aufruf von int.__add__(5,Length(3, "yd")) zu einer Ausnahme, sucht Python nach der Methode __radd__ in der Klasse Length, da unser zweiter Parameter (Length(3, "yd")) eine Instanz der Klasse Length ist. Existiert diese Methode, wird sie mit Length.__radd__(Length(3, "yd"), 5) aufgerufen, was dem Aufruf 5 + Length(3, "yd") entspricht. Bild 21.11 Vererbung von Personeneigenschaften Im nebenstehenden Diagramm veranschaulichen wir den Zusammenhang zwischen __add__ und __radd__. Die Implementierung von __radd__ würde damit exakt gleich aussehen wie __add__: def __radd__(self, other): if type(other) == int or type(other) == float: l = self.converse2metres() + other 21.15 Magische Methoden und Operatorüberladung else: l = self.converse2metres() + other.converse2metres() return Length(l / Length.__metric[self.unit], self.unit ) Es empfiehlt sich deshalb, die Implementierung von __radd__ auf einen Aufruf von __add__ zu reduzieren: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __radd__(self, other): return Length.__add__(self,other) 239 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 22 22.1 Bruchklasse Brüche à la 1001 Nacht Bevor wir mit der Arbeit an unserem Modul und mit den Brüchen beginnen, möchten wir Sie noch in die Welt Aladins und Sinbads entführen. An einem Zwischenstopp an einer Oase finden wir drei Männer mit einer Herde Kamele. Sie jammern und wehklagen. Der älteste der drei mit langem Bart und Turban wendet sich an Sie und sagt: „Wir sind drei Brüder und haben diese 35 Kamele als unser Erbe erhalten. Nach dem Wunsch meines Vaters soll die Hälfte der Herde mir als dem ältesten Sohn gehören.” Bild 22.1 Walter Heubach: Lagernde Karawane mit Dromedaren „Und ein Drittel soll mir gehören!”, sagt der mittlere der drei Brüder. „Ich als Jüngster soll mich mit einem Neuntel zufriedengeben!”, sagt der letzte der drei. „Die Hälfte von 35 ist 17 und ein halb, und wir wollen kein Kamel zerteilen!”, fährt der Älteste fort. „Die Schwierigkeiten, ein Drittel und ein Neuntel der Herde zu bestimmen, sind auch nicht kleiner!”, jammern die beiden anderen unisono. Plötzlich haben Sie einen genialen Einfall und schlagen den ratlosen Brüdern Folgendes vor: Sie geben Ihr einziges Kamel, das Sie noch weiter durch die Wüste tragen soll, der Herde hinzu und sagen zum ältesten Bruder: „Vorher hättest du 17 und ein halbes Kamel erhalten sollen, doch nun sind es 18!” Zum mittleren der Brüder sagen Sie: „Vorher hättest du 11 und zwei Drittel Kamele erhalten sollen, nun werden es 12 sein. Du hast also auch keinen Grund zum Klagen.” Und zum Jüngsten sagen Sie: „Du wirst nun 4 Kamele statt 3 und noch einen Teil von einem weiteren erhalten und hast auch keinen Grund zu Klage!” Die Brüder ziehen nun voller Freude mit ihren 34 Kamelen von dannen, und Sie haben nun zwei Kamele statt einem! 242 22 Bruchklasse 22.2 Zurück in die Gegenwart Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Ja, damals zu Sinbads Zeiten war Bruchrechnen noch eine hohe und wenig erforschte Kunst, und mit solchen Geschichten konnte man die Leute leicht verzaubern. Sicherlich haben Sie das Problem gleich erkannt. 1 1 1 + + =? 2 3 9 Wir wollen nun eine Python-Klasse für das RechBild 22.2 Brüche als Kuchennen mit Brüchen entwickeln, wobei wir uns aber auf diagramm die vier Grundrechenarten „Addition”, „Subtraktion”, „Multiplikation” und „Division” beschränken wollen. Zunächst müssen wir uns aber die Frage stellen, wie wir einen Bruch in Python darstellen wollen. Ein Bruch besteht ja bekanntlich aus einem Zähler, einem Bruchstrich und einem Nenner. Mathematisch relevant sind natürlich nur Zähler und Nenner, während der Bruchstrich bloß ein Repräsentationsdetail ist. Zur Repräsentierung eines Bruchs in unserer Bruchklasse genügt es also, wenn wir ein Attribut für den Nenner und eines für den Zähler definieren. Im folgenden Beispiel finden Sie die __init__-Methode, und wir erzeugen Instanzen für die Brüche 13 und 25 : class Bruch(object): def __init__(self, z, n): self.zaehler = z self.nenner = n if __name__ == "__main__": x = Bruch(1, 3) y = Bruch(2, 5) Wir benötigen nun noch die Methode __str__1 , damit wir unsere Brüche in schöner Form ausgeben können: def __str__(self): return str(self.zaehler) + '/' + str(self.nenner) Die Ausgabe von Brüchen sieht nun wie folgt aus: $ python3 Python 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from brueche import Bruch >>> x = Bruch(2, 3) 1 siehe 21.11 (Die __str__- und die __repr__-Methode) 22.2 Zurück in die Gegenwart >>> print(x) 2/3 Zur Bruchrechnung gehört das Kürzen. Unter dem Kürzen eines Bruchs versteht man, dass man Zähler und Nenner des Bruchs durch die gleiche Zahl dividiert. Kürzen dient der Vereinfachung von Brüchen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Der Wert des Bruchs bleibt beim Kürzen gleich. Man erhält lediglich eine neue Darstellung derselben Bruchzahl. So sind zum Beispiel die folgenden Brüche alle gleich: 1 2 3 , , , 3 6 9 und so weiter. Die Zahl, durch die man kürzt, wird als Kürzungszahl bezeichnet. Ein Bruch ist in vollständig gekürzter Form, wenn Zähler und Nenner teilerfremd sind. Also in obiger Folge der Bruch 13 . Um einen Bruch in eine vollständig gekürzte Form zu wandeln, muss man den größten gemeinsamen Teiler, abgekürzt ggT, ermitteln. Der Euklidische Algorithmus ist ein effizientes Verfahren, um den größten gemeinsamen Teiler zweier Zahlen zu berechnen. Bei diesem Algorithmus wird in aufeinanderfolgenden Schritten jeweils eine Division mit Rest durchgeführt, wobei der Rest im nächsten Schritt zum neuen Divisor wird und der vorige Divisor zum Dividenden. Der Divisor, bei dem sich Rest 0 ergibt, ist der größte gemeinsame Teiler der Ausgangszahlen. Beispiel: >>> 170 >>> 51 >>> 17 >>> 0 561 % 391 391 % 170 170 % 51 51 % 17 Also ist 17 der größte gemeinsame Teiler der Zahlen 561 und 391. Obige Berechnung hätte man interessanterweise auch mit 391%561 starten können. Im nächsten Schritt hat man dann 561%391, und die weitere Rechnung läuft wie oben weiter. Wir können unser Programm nun um eine Methode ggT, die obiges Verfahren anwendet, und eine Methode kuerze erweitern. Außerdem rufen wir kuerze bei der Initialisierung eines Bruchs auf, damit ein Bruch immer in vollständig gekürzter Form gespeichert wird. Da die Methode ggT nichts mit Bruchinstanzen zu tun hat, definieren wir sie als staticmethod. Unsere Klasse sieht nun wie folgt aus: class Bruch(object): def __init__(self, z, n): self.__z = z self.__n = n self.kuerze() 243 244 22 Bruchklasse def __str__(self): return str(self.__z) + '/' + str(self.__n) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. @staticmethod def ggT(a, b): while b != 0: a, b = b, a%b return a def kuerze(self): g = Bruch.ggT(self.__z, self.__n) self.__z = self.__z // g self.__n = self.__n // g if __name__ == "__main__": x = Bruch(2, 6) y = Bruch(391, 561) print(x, y) Startet man obiges Programm, erhält man folgende Ausgabe: $ python3 brueche.py 1/3 23/33 Man sieht, dass die Brüche vollständig gekürzt wurden. Aber das Wichtigste fehlt noch in unserer Bruchklasse: Wir können Brüche definieren und ausgeben, aber wir können keine Operationen auf den Brüchen ausführen. 22.3 Rechenregeln Wie auch beim Rechnen mit natürlichen Zahlen2 oder ganzen Zahlen3 werden beim Rechnen in den vier Grundrechenarten jeweils zwei Brüche miteinander verknüpft, und man erhält als Ergebnis einen neuen Bruch. Wir haben bereits im vorigen Abschnitt gezeigt, dass sich der Wert eines Bruchs nicht ändert, wenn man Zähler und Nenner durch einen gemeinsamen Teiler dividiert. Man spricht dann von Kürzen. Ebenso verändert sich ein Bruch nicht, wenn man Zähler und Nenner mit der gleichen ganzen Zahl multipliziert. In diesem Fall spricht man von Erweitern. 22.3.1 Multiplikation von Brüchen Als Erstes zeigen wir, wie man Brüche multipliziert, da dies am einfachsten ist: 2 3 N = 1, 2, 3, ... Z = −3, −2. − 1, 01, 2, 3, ... 22.3 Rechenregeln Brüche werden multipliziert, indem man ihre Zähler und Nenner miteinander multipliziert. Das Produkt der Zähler wird zum Zähler des Ergebnisses, das Produkt der Nenner wird zum Nenner des Ergebnisses: Beispiel: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 2 3 2·3 6 · = = 3 5 3 · 5 15 Wie wir sehen, kann das Ergebnis der obigen Multiplikation noch durch den größten gemeinsamen Teiler von 6 und 15, also 3, gekürzt werden: 6 2 = 15 5 Nun wissen wir genug, um unsere Bruchklasse um eine Multiplikation zu erweitern. Dazu müssen wir die Methode __mul__ bereitstellen4 : def __mul__(self,other): p = Bruch(self.__z * other.__z, self.__n * other.__n) return p In der Methode __mul__ erzeugen wir einen neuen Bruch P. Dieser wird mit der Initialisierung sofort auf das Ergebnis der Multiplikation gesetzt und dabei auch gekürzt: Python 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from brueche import Bruch >>> x = Bruch(2, 3) >>> y = Bruch(4, 15) >>> z = x * y >>> print(z) 8/45 Intern wird ein Ausdruck „x * y” in den Aufruf x.__mul__(y) gewandelt, d.h. damit entspricht x dem „self” und y dem „other”. 22.3.2 Division von Brüchen Die Division von Brüchen ist ähnlich einfach wie die Multiplikation. Durch einen Bruch wird dividiert, indem man mit seinem Kehrwert multipliziert. Beispiel: 2 5 2 3 2·3 6 : = · = = 3 3 3 5 3 · 5 15 4 Siehe Kapitel Magische Methoden und Operatorüberladung, Seite 233 245 246 22 Bruchklasse Unsere Methode für die Division zweier Brüche lässt sich nahezu analog zur Multiplikation realisieren. Der Name für die Division in Python 3 lautet __truediv__5 : def __truediv__(self, other): p = Bruch(self.__z * other.__n, self.__n * other.__z) return p Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im Folgenden zeigen wir, wie man Brüche in unserer Bruchklasse dividieren kann: $ python3 Python 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from brueche import Bruch >>> x = Bruch(2, 3) >>> y = Bruch(15, 4) >>> print(x/y) 8/45 22.3.3 Addition von Brüchen Im Vergleich zur Multiplikation und Division von Brüchen gestaltet sich die Addition ebenso wie die Subtraktion etwas schwieriger. Bevor man zwei Brüche addieren kann, müssen sie zuerst gleichnamig gemacht werden, d.h. sie müssen so erweitert werden, dass anschließend die beiden Nenner gleich sind. Dann können die Zähler einfach addiert werden: 2 3 2 4 3 3 2 · 4 + 3 · 3 17 + = · + · = = 3 4 3 4 4 3 3·4 12 Entsprechend implementieren wir die Python-Methode für die Addition (__add__): def __add__(self,other): s = Bruch(self.__z*other.__n + other.__z * self.__n, self.__n*other.__n) return s 22.3.4 Subtraktion von Brüchen Die Subtraktion läuft analog zur Addition, d.h. erst müssen die Brüche gleichnamig gemacht werden und dann kann man die Zähler subtrahieren: 5 Vor Python 3 hieß diese Methode __div__. 22.4 Integer plus Bruch def __sub__(self,other): s = Bruch(self.__z*other.__n - other.__z * self.__n, self.__n * other.__n) return s Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 22.3.5 Vergleichsoperatoren Im Folgenden wollen wir noch die Methoden für die Vergleichsoperatoren von Brüchen angeben, also für „==”, „!=”, „>”, „>=”, „<”, „<=”: def __eq__(self, other): return self.__z * other.__n == other.__z * self.__n def __ne__(self, other): return not self.__eq__(other) def __gt__(self, other): return self.__z * other.__n > other.__z * self.__n def __ge__(self, other): return self.__z * other.__n >= other.__z * self.__n def __lt__(self, other): return self.__z * other.__n < other.__z * self.__n def __le__(self, other): return self.__z * other.__n <= other.__z * self.__n 22.4 Integer plus Bruch Mit unserer Bruchklasse sind wir in der Lage, jeweils zwei Brüche zu addieren, zu subtrahieren, zu multiplizieren und zu dividieren. Wie sieht es aber aus, wenn wir einen Bruch mit einer ganzen Zahl verknüpfen wollen, also beispielsweise ein Ausdruck der Form 13 + 5 oder umgekehrt herum 5 + 13 ? Wir schreiben jetzt unsere bisherige __add__-Methode so um, dass Sie auch mit einer Integer-Zahl als Argument für den other-Operator klarkommt. Wir machen dies, indem wir aus der Integer-Zahl einen Bruch mit dem Nenner 1 erzeugen. Außerdem geben wir im Fall, dass das rechte Argument vom +-Operator weder ein Bruch noch eine Integer-Zahl ist, den speziellen Wert NotImplemented zurück. Dies ist ein build-in-Wert von Python: def __add__(self, other): if type(other) == int: return self + Bruch(other, 1) elif type(other) == Bruch: return Bruch(self.__z*other.__n + other.__z * self.__n, self.__n * other.__n) else: return NotImplemented 247 248 22 Bruchklasse Damit unsere Bruchklasse auch Ausdrücke der Form „Integer-Zahl + Bruch” ausrechnen kann, müssen wir noch die Methode __radd__ definieren. Da self beim Aufruf von __radd__ vom Typ int ist und other vom Typ „Bruch”, können wir einfach self + other ausrechnen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __radd__(self, other): return self + other 22.4.1 Die Bruchklasse im Überblick Unsere Bruchklasse ist nun „fertig”. Allerdings müsste man noch die Methoden von „add” analog für die anderen Operatoren vollständig implementieren: class Bruch(object): def __init__(self, z, n): self.__z = z self.__n = n self.kuerze() def __repr__(self): return "Bruch(" + str(self.__z)+', ' + str(self.__n) + ")" def __str__(self): return str(self.__z) + '/' + str(self.__n) @staticmethod def ggT(a,b): while b != 0: a, b = b, a % b return a def kuerze(self): g = Bruch.ggT(self.__z, self.__n) self.__z = self.__z // g self.__n = self.__n // g def __mul__(self, other): p = Bruch(self.__z * other.__z, self.__n * other.__n) return p def __truediv__(self, other): p = Bruch(self.__z * other.__n, self.__n * other.__z) return p 22.4 Integer plus Bruch Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __add__(self, other): if type(other) == int: return self + Bruch(other, 1) elif type(other) == Bruch: return Bruch(self.__z*other.__n + other.__z * self.__n, self.__n * other.__n) else: return NotImplemented def __radd__(self, other): return self + other def __sub__(self,other): s = Bruch(self.__z*other.__n - other.__z * self.__n, self.__n * other.__n) return s def __eq__(self, other): return self.__z * other.__n == other.__z * self.__n def __ne__(self, other): return not self.__eq__(other) def __gt__(self, other): return self.__z * other.__n > other.__z * self.__n def __ge__(self, other): return self.__z * other.__n >= other.__z * self.__n def __lt__(self, other): return self.__z * other.__n < other.__z * self.__n def __le__(self, other): return self.__z * other.__n <= other.__z * self.__n if __name__ == "__main__": x = Bruch(2, 3) y = Bruch(3, 4) z = x * y print(z) print(x + y) print(x + 3) print(3 + x) if x < y: print("Es gilt: x < y") else: print("Es gilt: x >= y") 249 250 22 Bruchklasse Lässt man obiges Programm laufen, erhält man folgende Ausgaben: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. $ python3 brueche.py 1/2 17/12 11/3 11/3 Es gilt: x < y 22.5 Fraction-Klasse Wie so häufig haben wir uns die Arbeit mit der Bruchklasse umsonst gemacht, denn Python bietet eine umfangreiche Bruchklasse Fraction in dem Modul fractions: >>> from fractions import Fraction >>> x = Fraction(1, 3) >>> y = Fraction(2, 5) >>> print(x) 1/3 >>> x + y Fraction(11, 15) >>> print((x - y) * Fraction(4, 7)) -4/105 Der besondere Vorteil der Fraction-Klasse liegt in der Genauigkeit, da die Integers nahezu beliebig groß werden können. Im folgenden Beispiel erhalten wir einen Fehler von 5.329070518200751e-15, wenn wir mit float statt mit Fraction rechnen: from fractions import Fraction x = y = xf, for Fraction(19, 599) Fraction(17, 171) yf = float(x), float(y) i in range(1, 10000): x += y / i xf += yf / i print(i, float(x), xf, float(x) - xf) Als Ausgabe erhalten wir: 9999 2.370739206885817 2.3707392068858106 6.217248937900877e-15 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 23 23.1 Vererbung Oberbegriffe und Oberklassen Jeder kennt Oberbegriffe aus der natürlichen Sprache. Wenn wir von Fahrzeugen reden, dann können dies Autos, Lastwagen, Motorräder, Busse, Züge, Schiffe, Flugzeuge oder auch Fahrräder und Roller sein. Autos kann man auch wieder nach verschiedenen Kriterien klassifizieren, z.B. nach der Antriebsart, also Benziner, mit Diesel oder mit Strom angetriebene Autos. In der objektorientierten Programmierung gibt es etwas Ähnliches. Man spricht in der OOP von Vererbung und von Ober- und Unterklassen. Statt Oberklasse benutzt man auch die Synonyme Basisklasse, Elternklasse und Superklasse. Für Unterklasse gibt es auch die Synonyme abgeleitete Klasse, Kindklasse und Subklasse. Ein weiteres Beispiel eines Oberbegriffs stellt Person dar. Häufig werden auch Klassen modelliert, die Eigenschaften von Personen in Datenstrukturen abbilden. Allgemeine Daten für Personen sind Name, Vorname, Wohnort, Geburtsdatum, Geschlecht und so weiter. Wahrscheinlich würde man jedoch nicht Daten wie Personalnummer, Steuernummer, Religionszugehörigkeit oder Ähnliches aufnehmen. Nehmen wir nun an, dass man die allgemeine Personenklasse für die Firmenbuchhaltung benutzen will. Man merkt schnell, dass nun die Information über die Religionszugehörigkeit, Bild 23.1 Vererbung von Personendie Steuerklasse oder Kinderzahl benötigt wird, eigenschaften um das Nettogehalt korrekt zu berechnen. Vor allen Dingen benötigt man firmenspezifische Daten für die Angestellten wie beispielsweise Personalnummern und Abteilungszugehörigkeiten. Die Personendaten können wir aber auch beispielsweise für eine Klasse Clubmitglieder verwenden. Auch hier benötigen wir gegebenenfalls zusätzliche Informationen, z.B. Eintrittsdatum, Funktion im Verein und so weiter. Ganz allgemein kann man sagen, dass die Vererbung eine Beziehung zwischen einer allgemeinen Klasse (einer Oberklasse oder einer Basisklasse) und einer spezialisierten Klasse (der Unterklasse, manchmal auch Subklasse genannt) definiert. 252 23 Vererbung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 23.2 Ein einfaches Beispiel Wir wollen nun die Personenklasse implementieren. Wir beschränken uns allerdings nur auf die Instanzattribute für den Vornamen (vorname), Nachnamen (nachname) und das Geburtsdatum (geburtsdatum) für eine Person. Die Klasse Angestellter erbt von Person und wird um ein zusätzliches Instanzattribut Personalnummer (personalnummer) ergänzt. Die Methode __str__ von Person wird in der Klasse Angestellter überlagert. Allerdings benutzen wir in der Definition der Methode __str__ von Angestellter die Methode __str__ von Person. Der Aufruf kann entweder über super().__str__() oder über Person.__str__(self) erfolgen: class Person: def __init__(self, vorname, nachname, geburtsdatum): self.vorname = vorname self.nachname = nachname self.geburtsdatum = geburtsdatum def __str__(self): ret = self.vorname + " " + self.nachname ret += ", " + self.geburtsdatum return ret class Angestellter(Person): def __init__(self, vorname, nachname, geburtsdatum, personalnummer ): Person.__init__(self, vorname, nachname, geburtsdatum) # alternativ: #super().__init__(vorname, nachname, geburtsdatum) self.personalnummer = personalnummer def __str__(self): #return super().__str__() + " " + self.personalnummer return Person.__str__(self) + " " + self.personalnummer if __name__ == "__main__": x = Angestellter("Homer", "Simpson", "09.08.1969", "007") print(x) Das obige Programm liefert die folgende Ausgabe: Homer Simpson, 09.08.1969 007 23.3 Überladen, Überschreiben und Polymorphie 23.3 Überladen, Überschreiben und Polymorphie Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Es ist möglich, in der überlagernden Methode die Methode, die man überlagert hat, aufzurufen. Im folgenden Beispiel zeigen wir, wie eine Funktion f überschrieben wird. Man sieht dabei, dass dies nichts mit Objektorientierung zu tun hat: >>> def f(x): ... return x + 3 ... >>> f(4) 7 >>> >>> f <function f at 0x7ff8bff1a158> >>> >>> # Wir überschreiben nun die Funktion f: ... >>> def f(x): ... return x * 3.4 ... >>> f(3) 10.2 Nach dem Überschreiben gibt es keine Möglichkeit mehr, auf die alte Definition zurückzugreifen! Im Zusammenhang mit objektorientierter Programmierung haben Sie möglicherweise auch schon einmal etwas von „Überladen” gehört. Überladen kennt man beispielsweise in C++ und Java. Um es gleich vorweg zu sagen: Es gibt kein Überladen von Funktionen und Methoden in Python. Es wird auch nicht benötigt, wie wir im Folgenden zeigen wollen. Überladen von Methoden wird in statisch getypten Sprachen wie Java und C++ benötigt, um die gleiche Funktion mit verschiedenen Typen zu definieren. Betrachten wir beispielsweise die Funktion multi, die wir im folgenden Code definieren: >>> def multi(irgendwas, faktor): ... return factor * irgendwas ... >>> multi(3, 2) 6 >>> multi(3.4, 2) 6.8 >>> multi(".", 10) '..........' >>> multi(["spam"], 5) ['spam', 'spam', 'spam', 'spam', 'spam'] 253 254 23 Vererbung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Parameter irgendwas und faktor sind nicht mit einem Typ versehen. Beim Parameter faktor kann man gegebenenfalls darauf schließen, dass die Funktion hier eine ganze Zahl erwartet, aber irgendwas kann wie der Name sagt, „irgendetwas” sein. Wir können die Funktion multi also mit beliebigen Argumenten aufrufen. Dies bedeutet aber nicht immer, dass dies im Sinne des Programmierers ist oder dass es fehlerfrei vonstattengeht: >>> multi(5, ["spam"]) ['spam', 'spam', 'spam', 'spam', 'spam'] >>> multi("spam", "spam") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in multi TypeError: can't multiply sequence by non-int of type 'str' In Java und C++ müssen für alle gewollten Typkombinationen jeweils Funktionen definiert werden, die zwar den gleichen Namen haben, aber unterschiedliche Parametertypen. Wir zeigen dies exemplarisch für zwei Typkombinationen in C++: #include <iostream> #include <cstdlib> using namespace std; int multi(int irgendwas, int faktor) { return irgendwas * faktor; } double multi(double irgendwas, int faktor) { return irgendwas * faktor; } int main() { cout << multi(10, 3) << endl; cout << multi(10.3, 4) << endl; return 0; } Parameterüberladung gibt es in C++ und Java auch in der Form, dass die gleiche Funktion oder Methode mehrfach definiert wird, jedoch mit einer unterschiedlichen Zahl von Parametern. Im folgenden C++-Programm definieren wir eine Funktion f, die ein oder zwei Integer-Parameter nehmen kann: #include <iostream> using namespace std; int f(int n) return n } int f(int n, return n } int main() { { + 42; int m) { + m + 42; 23.3 Überladen, Überschreiben und Polymorphie cout << "f(3): " << f(3) << endl; cout << "f(3, 4): " << f(3, 4) << endl; return 0; Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. } Bei den letzten beiden C++-Beispielen handelt es sich um Polymorphismen. Das Wort Polymorphie oder Polymorphismus stammt aus dem Griechischen und bedeutet Vielgestaltigkeit. Polymorphismus bezeichnet bei Methoden die Möglichkeit, dass man bei gleichem Namen Methoden mit verschiedenen Parametern definieren kann. So wurde die Funktion „nachfolger” zweimal definiert: einmal mit einem int und das andere Mal mit double als Parameter. Im zweiten Beispiel wurde f einmal mit einem Parameter und beim zweiten Mal mit zwei Parametern definiert. Methoden und Funktionen in Python haben bereits eine implizite Typpolymorphie wegen des dynamischen Typkonzepts von Python. Die zweite Art von Polymorphie, d.h. gleicher Name bei unterschiedlicher Parameteranzahl, lässt sich in Python nicht direkt implementieren. Das folgende Beispiel zeigt, was passiert, wenn wir obiges C++-Programm mit der f-Funktion in Python analog nachbilden: >>> def f(n): ... return n + 42 ... >>> def f(n,m): ... return n + m + 42 ... >>> f(3,4) 49 >>> f(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() takes exactly 2 arguments (1 given) >>> In Python kann man das polymorphe Verhalten jedoch mit Default-Parameter realisieren: def f(n, m=None): if m: return n + m +42 else: return n + 42 Wir haben aber damit nur eine Funktionsdefinition und können nicht von Polymorphie sprechen. Ganz allgemein lassen sich Polymorphien in der Parameteranzahl in Python mit einem *-Argument für eine beliebige Anzahl von Parametern realisieren: def f(*x): if len(x) == 1: return x[0] + 42 else: return x[0] + x[1] + 42 255 256 23 Vererbung 23.4 Vererbung in Python Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Einige Konzepte der Vererbung möchten wir zunächst an einer abstrakten Oberklasse mit abstrakten Unterklassen demonstrieren. Dazu definieren wir eine Klasse A mit einer __init__-Methode und einer Methode m. Dann definieren wir eine Unterklasse B, die von A erbt und keine eigenen Methoden hat: class A: def __init__(self): self.content = "42" print("__init__ von A wurde ausgeführt") def m(self): print("m von A wurde aufgerufen") class B(A): pass if __name__ == "__main__": x = A() x.m() y = B() y.m() Ruft man diese Klasse direkt auf, erhalten wir folgende Ausgabe: $ python3 inheritance1.py __init__ von A wurde ausgeführt m von A wurde aufgerufen __init__ von A wurde ausgeführt m von A wurde aufgerufen Die Ergebnisse der ersten beiden Anweisungen hinter der if-Anweisung stellen nichts Neues dar. Aber wenn wir ein Objekt der Klasse B erzeugen, wie dies in der Anweisung y = B() geschieht, dann können wir an der Ausgabe erkennen, dass die __init__Methode der Klasse A ausgeführt wird, da B keine eigene __init__-Methode besitzt. Ebenso erkennen wir, dass beim Aufruf der Methode m für das Objekt y der Klasse B die Methode m von A aufgerufen wird, denn B hat keine Methode m. Was passiert, wenn wir die Klasse B nun auch mit einer __init__-Methode und einer Methode m ausstatten? Wir testen dies im folgenden Beispiel. Die Klasse A und unsere Tests bleiben gleich, wir ändern lediglich die Klasse B wie folgt: class B(A): def __init__(self): self.content = "43" print("__init__ von B wurde ausgeführt") def m(self): print("m von B wurde aufgerufen") 23.4 Vererbung in Python Rufen wir das veränderte Skript nun auf, erhalten wir die folgende Ausgabe: $ python3 inheritance2.py __init__ von A wurde ausgeführt m von A wurde aufgerufen __init__ von B wurde ausgeführt Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. m von B wurde aufgerufen Wir erkennen nun, dass die __init__-Methode von B und nicht mehr die von A beim Erzeugen einer Instanz von B aufgerufen wird. Außerdem wird nun die Methode m von B statt der Methode m von A benutzt, d.h. wir überschreiben die Methode m von A in B. Im folgenden Beispiel ändern wir nun die Instanzattribute der Klassen A und B. Wir haben in A ein Attribut self.contentA und in B eines mit dem Namen self.contentB. class A(object): def __init__(self): self.contentA = "42" print("__init__ von A wurde ausgeführt") def m(self): print("m von A wurde aufgerufen") class B(A): def __init__(self): self.contentB = "43" print("__init__ von B wurde ausgeführt") def m(self): print("m von B wurde aufgerufen") if __name__ == "__main__": x = A() x.m() y = B() y.m() print("self.contentB von y: " + str(y.contentB)) print("self.contentA von y: " + str(y.contentA)) Die Ausgabe des Programms zeigt, was passiert: $ python3 inheritance3.py __init__ von A wurde ausgeführt m von A wurde aufgerufen __init__ von B wurde ausgeführt m von B wurde aufgerufen self.contentB von y: 43 Traceback (most recent call last): File "inheritance3.py", line 25, in <module> print("self.contentA von y: " + str(y.contentA)) AttributeError: 'B' object has no attribute 'contentA' 257 258 23 Vererbung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wie erwartet, erhalten wir für self.contentB den Wert 43 als Ausgabe. Da die __init__Methode der Klasse A nicht aufgerufen wird, sollte es uns nicht wundern, dass wir beim Versuch, self.contentA auszugeben, einen AttributeError mit dem Text ’B’ object has no attribute ’contentA’ erhalten, denn es gibt ja kein Attribut self.contentB in den Instanzen von B. Wir können uns auch vorstellen, dass wir spezielle Attribute, die wir bei der Initialisierung eines Objekts einer Basisklasse einführen, auch in der Unterklasse verwenden wollen. Also hätten wir in unserem Beispiel vielleicht gerne das Attribut contentA genutzt. Wir können dies erreichen, indem wir in der __init__-Methode von B die __init__-Methode von A aufrufen. Dies geschieht durch die Anweisung A.__init__(self), d.h. Name der Basisklasse plus Aufruf der __init__-Methode. Die Klasse A und die Tests bleiben gleich. Wir müssen lediglich die __init__-Methode in B wie folgt ändern: class B(A): def __init__(self): A.__init__(self) self.contentB = "43" print("__init__ von B wurde ausgeführt") Rufen wir das Programm mit obiger Änderung auf, sehen wir, dass unser AttributeError verschwunden ist. Außerdem erkennen wir, dass die Instanz von y sowohl ein Attribut contentB als auch ein Attribut contentA enthält: $ python3 inheritance4.py __init__ von A wurde ausgeführt m von A wurde aufgerufen __init__ von A wurde ausgeführt __init__ von B wurde ausgeführt m von B wurde aufgerufen self.contentB von y: 43 self.contentA von y: 42 Dieses Vorgehen lässt sich analog auf andere Methoden übertragen. Im Folgenden rufen wir in m von B auch die Methode m von A auf: def m(self): A.m(self) print("m von B wurde aufgerufen") Wir testen dieses Skript, das unter dem Namen inheritance5.py abgespeichert ist, in der interaktiven Python-Shell: >>> from inheritance5 import A, B >>> y = B() __init__ von A wurde ausgeführt __init__ von B wurde ausgeführt >>> y.m() m von A wurde aufgerufen m von B wurde aufgerufen 23.5 Klassenmethoden 23.5 Klassenmethoden Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Statische Methoden darf man nicht mit Klassenmethoden verwechseln. Klassenmethoden sind auch nicht an Instanzen gebunden, aber anders als statische Methoden sind Klassenmethoden an eine Klasse gebunden. Das erste Argument einer Klassenmethode ist eine Referenz auf die Klasse, d.h. das Klassenobjekt. Aufrufen kann man sie über den Klassennamen oder eine Instanz: class Roboter: counter = 0 def __init__(self): type(self).counter += 1 @classmethod def anzahl_roboter(cls): print("Wert von cls: ", cls, end=", ") return cls.counter if __name__ == "__main__": print(Roboter.anzahl_roboter()) x = Roboter() print(Roboter.anzahl_roboter()) print(x.anzahl_roboter()) Das Ergebnis sieht nun wie folgt aus: Wert von cls: Wert von cls: Wert von cls: <class '__main__.Roboter'>, 0 <class '__main__.Roboter'>, 1 <class '__main__.Roboter'>, 1 Für Klassenmethoden gibt es zwei Einsatzgebiete: zum einen die sogenannte Fabrikmethode1 , auf die wir hier nicht näher eingehen, und zum anderen die Fälle, in denen aus statischen Methoden auf andere statische Methoden zugegriffen werden muss. Ohne Klassenmethoden, also in statischen Methoden, müsste man den Klassennamen fest kodieren, was insbesondere bei der Vererbung ein Nachteil sein kann. In der folgenden Bruchklasse2 benutzen wir eine statische Methode ggT, die den größten gemeinsamen Teiler von zwei Zahlen berechnet, und eine Klassenmethode kuerze, die zwei Zahlen in gekürzter Form zurückgibt. Bevor wir in __init__ die Werte für Zähler und Nenner abspeichern, berechnen wir die gekürzte Form von beiden durch den Aufruf self.kuerze(z,n). Hätten wir kuerze als statische Methode definiert oder, wie man normalerweise sagt, mit @staticmethod dekoriert, dann müssten wir die Zeile g = cls.ggT(zaehler, nenner) durch g = Bruch.ggT(zaehler, nenner) austauschen: 1 2 Dies sind Methoden, die neue Instanzen generieren. Wir werden später eine vollständige Bruchklasse definieren. 259 260 23 Vererbung class Bruch(object): def __init__(self, z, n): self.__z, self.__n = self.kuerze(z, n) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __str__(self): return str(self.__z)+'/' + str(self.__n) @staticmethod def ggT(a,b): while b != 0: a, b = b, a%b return a @classmethod def kuerze(cls, zaehler, nenner): g = cls.ggT(zaehler, nenner) return (zaehler // g, nenner // g) x = Bruch(8, 24) print(x) Wir erhalten „1/3” als Ergebnis. In unserem nächsten Beispiel wollen wir den Vorteil der Klassenmethoden bei der Verarbeitung demonstrieren. Wir definieren eine Klasse „Pets” mit einer Methode „about”. Die Klassen „Dogs” und „Cats” erben von dieser Klasse. Sie erben auch die Methode „about”. In unserer ersten Implementierung dekorieren wir die „about”-Methode als „staticmethod”, um die Nachteile dieses Vorgehens zu zeigen: class Pets: name = "Haustiere" @staticmethod def about(): print("In dieser Klasse geht es um {}!".format(Pets.name)) class Dogs(Pets): name = "Hunde" class Cats(Pets): name = "Katzen" p = Pets() p.about() d = Dogs() d.about() c = Cats() c.about() 23.6 Standardklassen als Basisklassen Wir erhalten die folgende Ausgabe: In dieser Klasse geht es um Haustiere! In dieser Klasse geht es um Haustiere! In dieser Klasse geht es um Haustiere! Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Insbesondere im Fall von c.about() und d.about() hätten wir aussagekräftigere Sätze erwartet oder uns gewünscht. Das „Problem” ist, dass die Methode „about” nicht weiß, dass sie von einer Instanz der Klasse Dogs oder Cats aufgerufen wird. Im folgenden Code dekorieren wir die Methode nun mit dem „classmethod”-Dekorator: class Pets: name = "Haustiere" @classmethod def about(cls): print("In dieser Klasse geht es um {}!".format(cls.name)) class Dogs(Pets): name = "Hunde" class Cats(Pets): name = "Katzen" p = Pets() p.about() d = Dogs() d.about() c = Cats() c.about() Die Ausgabe sieht nun wie gewünscht aus: In dieser Klasse geht es um Haustiere! In dieser Klasse geht es um Hunde! In dieser Klasse geht es um Katzen! 23.6 Standardklassen als Basisklassen Statt selbstdefinierter Klassen kann man auch Standardklassen wie beispielsweise int, float, dict oder list als Basisklasse verwenden, um neue Klassen abzuleiten. Man kann beispielsweise zusätzliche Methoden für Standardklassen definieren. 261 262 23 Vererbung Die Verarbeitung von Stapelspeichern3 wird in Python, wie wir gesehen haben, mittels append und pop realisiert. Üblicherweise, d.h. in anderen Programmiersprachen, werden meistens die Funktionen push4 und pop5 verwendet. Wir wollen im folgenden Beispiel die Klasse „list” um eine Methode „push” erweitern, die genau das Gleiche macht wie append: class Plist(list): Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __init__(self, l): list.__init__(self, l) def push(self, item): self.append(item) if __name__ == "__main__": x = Plist([3,4]) x.push(47) print(x) 3 4 5 auch Kellerspeicher genannt, im Englischen stack deutsch: einkellern deutsch: auskellern Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 24 24.1 Mehrfachvererbung Einführung Erbt eine abgeleitete Klasse direkt von mehr als einer Basisklasse, spricht man in der objektorientierten Programmierung von Mehrfachvererbung. Ein sequentielles, mehrstufiges Erben wird dagegen nicht als Mehrfachvererbung bezeichnet. Nicht alle objektorientierten Programmiersprachen unterstützen Mehrfachverarbeitung. So kennt Java1 sie beispielsweise nicht, wohingegen C++ sie unterstützt. Ein Argument gegen die Einbeziehung einer Mehrfachverarbeitung im Design einer Programmiersprache besteht darin, dass sie das Design von Klassen unnötig verkompliziert und undurchsichtig machen. Besonders bekannt sind die Mehrdeutigkeiten, die im sogenannten Diamond-Problem beschrieben werden. Wir gehen auf dieses Problem später ein. Syntaktisch realisiert Python die Mehrfachvererbung wie folgt: Soll eine Klasse von mehreren Basisklassen erben, gibt man die Basisklassen, durch Kommata getrennt, zwischen dem Klammernpaar hinter dem Klassennamen an: class UnterKlasse(Basis1, Basis2, Basis3, ...): pass Wie bei der einfachen Vererbung können Attribute und Methoden mit gleichen Namen in der Unterklasse und in Basisklassen vorkommen. Die obigen Basisklassen Basis1, Basis2 usw. können natürlich ihrerseits wieder von anderen Basisklassen geerbt haben. Dadurch erhält man eine Vererbungshierarchie, die man sich als einen Baum vorstellen kann, den Vererbungsbaum. Bild 24.1 Vererbungsbaum bei Mehrfachvererbung 1 Man hat das Problem in Java mit Mehrfach-Schnittstellenvererbung umgangen. Eine Klasse kann in Java von beliebig vielen Schnittstellen erben. 264 24 Mehrfachvererbung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 24.2 Beispiel: CalendarClock Wir wollen die Prinzipien der Mehrfachvererbung an einem Beispiel einführen. Dazu implementieren wir zuerst zwei voneinander unabhängige Klassen: eine Klasse „Clock” und eine Klasse „Calendar”. Dann schreiben wir eine Klasse „CalendarClock”, die aus einer Uhr und einem Kalender besteht. Dazu lassen wir die Klasse „CalendarClock” von den Klassen „Clock” und „Calendar” erben. Die Klasse Clock simuliert das sekunden- Bild 24.2 CalendarClock mäßige Ticken einer Uhr. In Kapitel 9 (Verzweigungen) hatten wir in einer Übungsaufgabe das Ticken, also das Vorwärtsschreiten der Uhr um eine Sekunde, bereits simuliert (siehe Seite 74). Der Code der Lösung dieser Übungsaufgabe (siehe Seite 482) befindet sich nun in unserer Methode tick(). Nachdem wir das „Ticken” recyceln können, gestaltet sich die Implementierung des Rests der Klasse „Clock” relativ einfach: Eine Instanz dieser Klasse enthält eine Uhrzeit, die wir in den Attributen self.hours , self.minutes und seconds speichern. Im Prinzip hätten wir die __init__-Methode und die set-Methode zum Neusetzen einer Uhrzeit wie folgt schreiben können: def __init__(self,hours=0, minutes=0, seconds=0): self._hours = hours self.__minutes = minutes self.__seconds = seconds def set(self,hours, minutes, seconds=0): self._hours = hours self.__minutes = minutes self.__seconds = seconds Wir wollten aber gerne noch Plausibilitätsprüfungen für die Eingaben durchführen. Damit wir den dazu nötigen Code nicht gleichzeitig in der __init__-Methode und der setMethode vorhalten müssen, rufen wir in der __init__-Methode nur die set-Methode auf, die dann die eigentliche Initialisierung inklusive Überprüfungen vornimmt. Die komplette Klasse „Clock”: """ Die Klasse Clock dient der logischen Simulation einer Uhr. Die Uhrzeit kann mit einer Methode sekundenweise weiterbewegt werden. """ class Clock(object): Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 24.2 Beispiel: CalendarClock def __init__(self, hours, minutes, seconds): """ Die Parameter hours, minutes, seconds müssen Ganzzahlen sein, und es muss gelten: 0 <= h < 24 0 <= m < 60 0 <= s < 60 """ self.set_Clock(hours, minutes, seconds) def set_Clock(self, hours, minutes, seconds): """ Die Parameter hours, minutes, seconds müssen Ganzzahlen sein, und es muss gelten: 0 <= h < 24 0 <= min < 60 0 <= sec < 60 """ if type(hours) == int and 0 <= hours and hours < 24: self._hours = hours else: raise TypeError("Stunden müssen Ganzzahlen zwischen 0 und 23 sein!") if type(minutes) == int and 0 <= minutes and minutes < 60: self.__minutes = minutes else: raise TypeError("Minuten müssen Ganzzahlen zwischen 0 und 59 sein!") if type(seconds) == int and 0 <= seconds and seconds < 60: self.__seconds = seconds else: raise TypeError("Sekunden müssen Ganzzahlen zwischen 0 und 59 sein!") def __str__(self): """ Diese Methode überlädt die eingebaute Funktion str(), d.h. es wird eine Methode zur Verfügung gestellt, um ein Objekt der Klasse Clock in einen String zu wandeln. Die Methode __str__ wird auch von der Print-Funktion genutzt, um ein Objekt der Klasse Clock auszugeben. """ return "{0:02d}:{1:02d}:{2:02d}".format(self._hours, self.__minutes, self.__seconds) 265 266 24 Mehrfachvererbung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def tick(self): """ Diese Methode lässt die Uhr 'ticken', d.h. die interne Uhrzeit eines Objektes, d.h. die Stunden-, Minuten- und Sekunden-Attribute werden um eine Sekunde weitergerechnet. Beispiele: >>> x = Clock(12,59,59) >>> print(x) 12:59:59 >>> x.tick() >>> print(x) 13:00:00 >>> x.tick() >>> print(x) 13:00:01 """ if self.__seconds == 59: self.__seconds = 0 if self.__minutes == 59: self.__minutes = 0 if self._hours == 23: self._hours = 0 else: self._hours += 1 else: self.__minutes += 1 else: self.__seconds += 1 if __name__ == "__main__": x = Clock(23,59,59) print(x) x.tick() print(x) y = str(x) print(type(y)) Ruft man das Modul clock standalone auf, erhält man folgende Ausgabe: $ python3 clock.py 23:59:59 00:00:00 <class 'str'> Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 24.2 Beispiel: CalendarClock Für unsere Klasse „CalendarClock”, die eine Uhrzeitfunktion mit Kalenderfunktion kombiniert, benötigen wir noch eine Klasse „Calendar”. Während eine Instanz einer Clock-Klasse mittels der Methode „tick” sekundenweise ihren Zustand ändert, geschieht dies bei der „Calendar”-Klasse tageweise. Die Methode zum Wechseln oder „Weiterblättern” der Tage nennen wir nicht „tick”, sondern „advance”. Sie zählt zu einem gegebenen Kalenderdatum einen Tag hinzu. Dabei müssen wir die Anzahl der Tage für die Monate berücksichtigen, also z.B. nach dem 30. April kommt der 1. Mai, und nach dem 31. Januar kommt der 1. Februar. Apropos Februar, hier haben wir ein weiteres Problem, denn wir müssen nun wissen, ob es sich um ein Datum in einem Schaltjahr handelt oder nicht. Auch bei der Schaltjahrberechnung können wir von bereits getaner Arbeit profitieren: Die Aufgabenstellung und die Beschreibung, wie man Schaltjahre ermittelt, befindet sich auf der Seite 74. Die dazugehörige Lösung befindet sich auf Seite 480. In unserer Klasse implementieren wir diese Funktionalität unter dem Namen „leapyear” als Klassenmethode, die True zurückliefert, wenn es sich um ein Schaltjahr handelt, und ansonsten False. Für die Anzahl der Monate benutzen wir ein Tupel2 als Klassenattribut. """ Die Klasse Calendar implementiert einen Kalender. Ein Kalenderdatum kann auf ein bestimmtes Datum gesetzt werden oder kann um einen Tag weitergeschaltet werden. """ class Calendar(object): months = (31,28,31,30,31,30,31,31,30,31,30,31) @staticmethod def leapyear(jahr): """ Die Methode leapyear liefert True zurück, wenn jahr ein Schaltjahr ist, und False, wenn nicht. """ if jahr % 4 == 0: if jahr % 100 == 0: if jahr % 400 == 0: schaltjahr = True else: schaltjahr = False else: schaltjahr = True else: schaltjahr = False return schaltjahr 2 Wir haben uns für den Typ tuple statt list entschieden, da sich ja die Anzahl der Tage in einem Monat nicht ändert, wenn man vom Februar absieht. 267 268 24 Mehrfachvererbung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __init__(self, d, m, y): """ d, m, y have to be integer values and year has to be a four digit year number """ self.set_Calendar(d,m,y) def set_Calendar(self, d, m, y): """ d, m, y have to be integer values and year has to be a four digit year number """ if type(d) == int and type(m) == int and type(y) == int: self.__days = d self.__months = m self.__years = y else: raise TypeError("d, m, y müssen ganze Zahlen sein!") def __str__(self): """ Diese Methode überlädt die eingebaute Funktion str(), d.h. es wird eine Methode zur Verfügung gestellt, um ein Objekt der Klasse Calendar in einen String zu wandeln. Die Methode __str__ wird auch von der Print-Funktion genutzt, um ein Objekt der Klasse Calendar auszugeben. """ return "{0:02d}.{1:02d}.{2:4d}".format(self.__days, self.__months, self.__years) def advance(self): """ setzt den Kalender auf den nächsten Tag unter Berücksichtigung von Schaltjahren """ max_days = Calendar.months[self.__months-1] if self.__months == 2 and Calendar.leapyear(self.__years): max_days += 1 if self.__days == max_days: self.__days= 1 if self.__months == 12: self.__months = 1 self.__years += 1 24.2 Beispiel: CalendarClock Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. else: self.__months += 1 else: self.__days += 1 if __name__ == "__main__": x = Calendar(31,12,2012) print(x, end=" ") x.advance() print("nach advance: ", x) print("2012 war ein Schaltjahr:") x = Calendar(28,2,2012) print(x, end=" ") x.advance() print("nach advance: ", x) x = Calendar(28,2,2013) print(x, end=" ") x.advance() print("nach advance: ", x) print("1900 war kein Schaltjahr: Zahl durch 100, aber nicht durch 400 teilbar:") x = Calendar(28,2,1900) print(x, end=" ") x.advance() print("nach advance: ", x) print("2000 war ein Schaltjahr, weil die Zahl durch 400 teilbar ist:") x = Calendar(28,2,2000) print(x, end=" ") x.advance() print("nach advance: ", x) Obiges Skript liefert folgende Ausgabe, wenn man es selbständig startet: $ python3 calendar.py 31.12.2012 nach advance: 01.01.2013 2012 war ein Schaltjahr: 28.02.2012 nach advance: 29.02.2012 28.02.2013 nach advance: 01.03.2013 1900 war kein Schaltjahr: Zahl durch 100, aber nicht durch 400 teilbar : 28.02.1900 nach advance: 01.03.1900 2000 war ein Schaltjahr, weil die Zahl durch 400 teilbar ist: 28.02.2000 nach advance: 29.02.2000 In unserem Test haben wir keine Fehlertests eingebaut. Wir sehen aber im Code der Methode set_Calendar, dass wir einen Fehler erheben, wenn einer der Werte für das Datum keine Ganzzahl ist. Wir testen die Fehlerfälle in der interaktiven Python-Shell: 269 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 270 24 Mehrfachvererbung >>> from calendar import Calendar >>> x = Calendar(31,12,2012) >>> x = Calendar("31",12,2012) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "calendar.py", line 34, in __init__ self.set_Calendar(d,m,y) File "calendar.py", line 47, in set_Calendar raise TypeError("d, m, y müssen ganze Zahlen sein!") TypeError: d, m, y müssen ganze Zahlen sein! >>> x = Calendar(12,2012) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() takes exactly 4 arguments (3 given) Nun geht es darum, eine Klasse CalendarClock zu implementieren, die eine Uhr mit integriertem Kalender repräsentiert. Die Klasse CalendarClock erbt von den Klassen Clock und Calendar. Die Methode tick von Clock wird von CalendarClock überschrieben. Die neue tick-Methode muss jedoch auf die tick-Methode von Clock zugreifen. Man kann diese jedoch nicht mit self.tick() aufrufen, da dies die tick-Methode von CalendarClock repräsentiert. Der Aufruf erfolgt deshalb mit Clock.tick(self). Die advance-Methode von Calendar kann jedoch direkt mit self.advance() aufgerufen werden. Natürlich wäre der Aufruf auch mit Calendar.advance(self) möglich. """ Modul, das die Klasse CalendarClock implementiert. """ from clock import Clock from calendar import Calendar class CalendarClock(Clock, Calendar): """ Die Klasse CalendarClock implementiert eine Uhr mit integrierter Kalenderfunktion. Die Klasse erbt sowohl von der Klasse Clock als auch von der Klasse Calendar. """ def __init__(self,day, month, year, hour, minute, second): """ Zur Initialisierung der Uhrzeit wird der Konstruktor der Clock-Klasse aufgerufen. Zur Initialisierung des Kalenders wird der Konstruktor der Calendar-Klasse aufgerufen. CalendarClock enthält dann die vereinigten Attribute der Clock- und Calendar-Klasse: self.day, self.month, self.year, self.hour, self.minute, self. second 24.2 Beispiel: CalendarClock Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. """ Clock.__init__(self,hour, minute, second) Calendar.__init__(self,day, month, year) def tick(self): """ Die Position der Uhr wird um eine Sekunde weiterbewegt, der Kalender wird, falls Mitternacht überschritten wird, um einen Tag weiterbewegt. """ previous_hour = self._hours Clock.tick(self) if (self._hours < previous_hour): self.advance() def __str__(self): """ Erzeugt die Stringdarstellung eines CalendarClock-Objekts """ return Calendar.__str__(self) + ", " + Clock.__str__(self) if __name__ == "__main__": x = CalendarClock(31,12,2013,23,59,59) print("One tick from ",x, end=" ") x.tick() print("to ", x) x = CalendarClock(28,2,1900,23,59,59) print("One tick from ",x, end=" ") x.tick() print("to ", x) x = CalendarClock(28,2,2000,23,59,59) print("One tick from ",x, end=" ") x.tick() print("to ", x) x = CalendarClock(7,2,2013,13,55,40) print("One tick from ",x, end=" ") x.tick() print("to ", x) Ruft man obiges Modul standalone auf, erhält man folgende Ausgabe, die gleichzeitig auch die Arbeitsweise nochmals ein wenig erklärt: $ python3 calendar_clock.py One tick from 31.12.2013 23:59:59 One tick from 28.02.1900 23:59:59 One tick from 28.02.2000 23:59:59 One tick from 07.02.2013 13:55:40 to to to to 01.01.2014 01.03.1900 29.02.2000 07.02.2013 00:00:00 00:00:00 00:00:00 13:55:41 271 272 24 Mehrfachvererbung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 24.3 Diamond-Problem Bei dem Diamond-Problem3 handelt es sich um ein Mehrdeutigkeitsproblem, was bei Mehrfachvererbung in der objektorientierten Programmierung entstehen kann. Es kann auftreten, wenn eine Klasse D auf zwei verschiedenen Vererbungspfaden über zwei Klassen B und C von der gleichen Basisklasse A abstammt. Das Problem tritt auf, falls in D eine Methode, sagen wir „m”, aufgerufen wird, für die gilt: ■ ■ ■ m wird in A definiert. Die Methode m wird entweder in A oder in B oder auch in beiden überschrieben. Bild 24.3 Diamond-Problem m wird nicht in D überschrieben. Die Frage, die sich dann stellt: Von welcher Klasse wird m vererbt? In Python hängt es zunächst einmal von der Reihenfolge der Klassen bei der Definition von D ab: Wir können die verschiedenen Möglichkeiten mit einfachen Klassendefinitionen testen. Zuerst wollen wir uns den Fall anschauen, dass sowohl B als auch C die Methode m überschreiben: class A: def m(self): print("m of A called") class B(A): def m(self): print("m of B called") class C(A): def m(self): print("m of C called") class D(B,C): pass Ruft man x.m() für eine Instanz x der Klasse D auf, erhalten wir die Ausgabe „m of B called”. Vertauschen wir nun die Reihenfolge der Klassen B und C im Klassenheader von D, also „class D(C,B):”, dann erhalten wir die Ausgabe „m of C called”. Von besonderem Interesse ist jedoch der Fall, wenn m nur entweder in B oder in C überschrieben wird: class A: def m(self): print("m of A called") 3 englisch: „diamond problem” oder „deadly diamond of death” 24.4 super und MRO class B(A): pass Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class C(A): def m(self): print("m of C called") class D(B,C): pass x = D() x.m() Im obigen Fall sind prinzipiell beide Möglichkeiten denkbar, d.h. „m von C” oder „m von A” wird aufgerufen. Ruft man dieses Programm mit einer 2er-Version von Python auf, wird m von A aufgerufen: $ python diamond_problem.py m of A called Vor der Einführung der sogenannten „New Style”-Klassen4 in Python 2 erfolgte eine Tiefensuche5 im Vererbungsbaum, d.h. man geht solange in der Suche nach der Methode m nach oben, bis es nicht mehr weiter nach oben geht. Dann sucht man von links nach rechts weiter. Rufen wir das obige Skript nun unter Python 3 auf, wird – anders als bei Python 2 – die Methode m der Klasse C aufgerufen: $ python3 diamond_problem.py m of C called Ändert man die Vererbungsreihenfolge bei der Definition von D, also class D(C,B): statt class D(B,C):, dann wird sowohl in Python 3 als auch in Python 2 die Methode m von C für eine Instanz aus D benutzt. Sicherlich fragen Sie sich noch, woher der Name „Diamond-Problem” kommt: Zeichnet man die Vererbungsbeziehungen zwischen den Klassen in einem Diagramm, so sieht das Ergebnis wie eine Raute aus, was im Englischen auch als „diamond” bezeichnet wird. 24.4 super und MRO Wir haben eben gesehen, wie Python das Diamond-Problem löst, d.h. in welcher Reihenfolge die Basisklassen durchsucht werden. Diese Reihenfolge wird durch die sogenannte „Method Resolution Order”, kurz MRO, festgelegt.6 4 5 6 In Python 2 wird die obige Definition der Klasse A als „Old Style”-Klasse betrachtet, da die New-Style-Klassen immer von der Klasse „object” erben müssen, wenn sie nicht von einer anderen Klasse erben. Die Definition in Python 2 für New Style würde also so aussehen: class A(object): pass englisch: depth-first search, DFS Python benutzt ab Version 2.3 den „C3 superclass linearization”-Algorithmus, um die MRO festzulegen. 273 274 24 Mehrfachvererbung Im Folgenden wollen wir zeigen, wie man MRO in Verbindung mit super beim DiamondProblem sinnvoll einsetzen kann. Dazu erweitern wir alle Klassen unseres vorigen Beispiels um eine Methode m. Die Methode m von D ruft die Methoden m von B und C auf. Die Methoden m von B und C rufen jeweils m von A auf: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class A: def m(self): print("m of A called") class B(A): def m(self): print("m of B called") A.m(self) class C(A): def m(self): print("m of C called") A.m(self) class D(B,C): def m(self): print("m of D called") B.m(self) C.m(self) Wir haben aber nun das Problem, dass unsere Methode m von A zweimal aufgerufen wird, d.h. einmal über B und einmal über C: >>> from diamond_problem_super_m import D >>> d = D() >>> d.m() m of D called m of B called m of A called m of C called m of A called Eine Lösungsmöglichkeit – allerdings eine nicht-pythonische – besteht darin, die Methoden m in B und C in zwei Teile zu splitten, und zwar in dem speziellen Code, der für die Klasse spezifisch ist (_m) und die eigentliche Methode m, die _m und m von A aufruft. In der Methode m von D rufen wir dann die speziellen Methoden von B und C auf. Insgesamt haben wir das Problem – auf Kosten eines aufwendigen und schwer verständlichen Designs – gelöst: class A: def m(self): print("m of A called") class B(A): def _m(self): print("m of B called") 24.4 super und MRO Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def m(self): self._m() A.m(self) class C(A): def _m(self): print("m of C called") def m(self): self._m() A.m(self) class D(B,C): def m(self): print("m of D called") B._m(self) C._m(self) A.m(self) Wir können sehen, dass es so funktioniert, wie wir es uns gewünscht hatten: >>> from super4 import D >>> x = D() >>> x.m() m of D called m of B called m of C called m of A called Der optimale oder der „super” pythonische Weg besteht in der Benutzung der superFunktion von Python: class A: def m(self): print("m of A called") class B(A): def m(self): print("m of B called") super().m() class C(A): def m(self): print("m of C called") super().m() class D(B,C): def m(self): print("m of D called") super().m() 275 276 24 Mehrfachvererbung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir erhalten wieder die gewünschte Ausgabe, aber diesmal mit einem perfekten Design: >>> from super5 import D >>> x = D() >>> x.m() m of D called m of B called m of C called m of A called Die super-Funktion wird sehr häufig bei der Initialisierung von Instanzen, also bei der __init__-Methode verwendet. Im Folgenden sehen wir ein Beispiel: class A: def __init__(self): print("A.__init__") class B(A): def __init__(self): print("B.__init__") super().__init__() class C(A): def __init__(self): print("C.__init__") super().__init__() class D(B,C): def __init__(self): print("D.__init__") super().__init__() Die Arbeitsweise verdeutlichen wir in der folgenden Benutzung der Klassen: >>> from super_init import A,B,C,D >>> d = D() D.__init__ B.__init__ C.__init__ A.__init__ >>> c = C() C.__init__ A.__init__ >>> b = B() B.__init__ A.__init__ >>> a = A() A.__init__ 24.4 super und MRO Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Es stellt sich nun folgende Frage: Wie entscheidet die Funktion super, welche Klasse sie benutzt? Dazu benutzt sie die eingangs erwähnte Method Resolution Order (MRO), die auf dem „C3 superclass linearization”-Algorithmus aufbaut. Man spricht auch von einer Linearisierung, weil aus der Baumstruktur eine lineare Reihenfolge wird, also eine (geordnete) Liste. Um diese Liste zu generieren, bietet Python die mro-Methode: >>> from super_init import A,B,C,D >>> D.mro() [<class 'super_init.D'>, <class 'super_init.B'>, <class 'super_init.C '>, <class 'super_init.A'>, <class 'object'>] >>> B.mro() [<class 'super_init.B'>, <class 'super_init.A'>, <class 'object'>] >>> A.mro() [<class 'super_init.A'>, <class 'object'>] 277 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 25 25.1 Slots Erzeugung von dynamischen Attributen verhindern Die Attribute eines Objekts werden in einem Dictionary __dict__ gespeichert. Wie bei jedem anderen Dictionary auch ist die Anzahl der Elemente nicht begrenzt. Mit anderen Worten: Man kann einem Dictionary weitere Elemente hinzufügen, nachdem es definiert worden ist. Aus diesem Grund kann man auch dem Objekt einer Klasse, so wie wir sie bisher definiert hatten, beliebige weitere Attribute zufügen. >>> ... ... >>> >>> >>> class A: pass a = A() a.x = 66 a.y = "dynamically created attribute" Das Dictionary, welches die Attribute von „a” enthält, kann folgendermaßen angesprochen werden: >>> a.__dict__ {'x': 66, 'y': 'dynamically created attribute'} Einige werden sich sicherlich gewundert haben, dass man zu den bisher von uns definierten Klassen dynamisch Attribute hinzufügen kann, während dies bei den meisten built-inKlassen wie zum Beispiel „int” oder „list” nicht möglich ist: >>> x = 42 >>> x.a = "not possible to do it" Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'int' object has no attribute 'a' >>> >>> lst = [34, 999, 1001] >>> lst.a = "forget it" 280 25 Slots Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'list' object has no attribute 'a' Ein Dictionary zu benutzen, um Attribute zu speichern, ist eine geeignete Möglichkeit. Daraus folgt aber auch, dass für Objekte mit wenigen Instanzattributen trotzdem viel Speicher benötigt wird. Der Speicherbedarf kann kritisch werden, wenn viele Instanzen erstellt werden. Hier kommen die sogenannten Slots ins Spiel. Mit Slots haben wir die Möglichkeit, dem Speicherkonsum entgegenzuwirken. Statt eines dynamischen Dictionarys stellen Slots eine statische Struktur zur Verfügung, die ein weiteres Hinzufügen von Attributen verhindert, sobald eine Instanz erstellt worden ist. Um Slots in einer Klasse zu benutzen, muss man eine Liste mit dem Namen __slots__ definieren. Diese Liste muss alle Attribute beinhalten, die man benutzen will. Im folgenden Beispiel demonstrieren wir eine Klasse, deren Slot-Liste nur den Namen des Attributs val enthält. class S(object): __slots__ = ['val'] def __init__(self, v): self.val = v x = S(42) print(x.val) x.new = "not possible" Wenn wir das Programm starten, werden wir bemerken, dass es nicht möglich ist, ein weiteres Attribut dynamisch zu erzeugen. Beim Versuch, das Attribut new zu erzeugen, stoßen wir auf einen Fehler: 42 Traceback (most recent call last): File "slots_ex.py", line 12, in <module> x.new = "not possible" AttributeError: 'S' object has no attribute 'new' Wie bereits erwähnt, helfen Slots dabei, den Speicher nicht mit der Bildung unnötiger Objekte zu verschwenden. Seit Python 3.3 ist dieser Vorteil aber nicht mehr so besonders beeindruckend. Mit der Einführung von Python 3.3 werden Key-Sharing-Dictionaries für die Speicherung von Objekten benutzt. Die Attribute der Instanzen sind in der Lage, einen Teil ihres genutzten Speichers mit anderen zu teilen. So kann der Speicherbedarf von Programmen gesenkt werden, die viele Instanzen von Klassen erstellen, die nicht „built-in” sind. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 26 26.1 Dynamische Erzeugung von Klassen Beziehung zwischen „class” und „type” In diesem Kapitel möchten wir Sie etwas tiefer mit dem „magischen” Verhalten von Python vertraut machen, wenn wir eine Klasse definieren oder eine Instanz einer Klasse erstellen. Möglicherweise fragen Sie sich: „Muss ich die Details wirklich wissen?” Vermutlich nicht . . . oder Sie gehören zu denen, die Klassen auf einem sehr fortgeschrittenem Niveau erstellen. Zunächst konzentrieren wir uns auf die Verbindung zwischen „type” und „class”. Als Sie eine Klasse definiert haben, haben Sie sich vielleicht gefragt, was hinter den Kulissen passiert. Wir haben bereits gesehen, dass type(object) die Klasse einer Instanz zurückliefert: >>> x = 42 >>> y = ["hi", 42, [8, 99]] >>> print(type(x), type(y)) <class 'int'> <class 'list'> Wenden wir type nochmals auf das Ergebnis an, so erhalten wir „type” als Ergebnis. >>> print(type(type(x)), type(type(list))) <class 'type'> <class 'type'> >>> >>> print(type(int), type(list)) <class 'type'> <class 'type'> Dies zeigt uns, dass Klassen wie list und int Instanzen der Klasse type sind. Allgemein kann man sagen, dass alle bisher von uns selbst erstellten Klassen Instanzen der Klasse type sind. Python unterscheidet nicht wie andere Sprachen zwischen Klassen und Typen. In den meisten Fällen werden die Begriffe synonym verwendet. Die Tatsache, dass Klassen Instanzen der Klasse „type” sind, erlaubt es uns, Meta-Klassen zu programmieren. Wir können Klassen kreieren, die von der Klasse „type” erben. Somit ist eine Meta-Klasse eine Subklasse der Klasse „type”. 282 26 Dynamische Erzeugung von Klassen Zunächst wollen wir jedoch zeigen, wie man mit type eine Klasse erzeugen kann. Statt mit nur einem Argument kann type auch mit drei Argumenten aufgerufen werden: type(classname, superclasses, attributes_dict) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wenn type mit drei Argumenten aufgerufen wird, liefert es ein neues type-Objekt zurück, was einer Klasse entspricht. Das bietet uns eine dynamische Form des Klassen-Statements, d.h. wir können so Klassen dynamisch erzeugen. ■ classname ist der String, der den Klassennamen angibt und zum name-Attribut wird. ■ superclasses ist eine Liste oder ein Tupel mit den Oberklassen unserer Klassen. Die Liste oder das Tupel wird zum bases-Attribut. ■ attributes_dict ist ein Dictionary, welches als Namensraum unserer Klasse fungiert. Es beinhaltet die Definitionen des Klassenkörpers und wird zum dict-Attribut. Schauen wir uns eine einfache Klassendefinition an: class A: pass x = A() print(type(x)) Führt man diesen Programmcode aus, erhält man folgende Ausgabe: <class '__main__.A'> Mit type können wir ebenfalls die obige Klasse definieren: >>> A = type("A", (), {}) >>> x = A() >>> print(type(x)) <class '__main__.A'> >>> Generell bedeutet dies, dass wir Klassen mit type(classname, superclasses, attributedict) zur Laufzeit definieren können. Wenn wir type() aufrufen, wird die __call__-Methode von type aufgerufen. Die __call__Methode ruft zwei weitere Methoden auf: __new__ und __init__. type.__new__(typeclass, classname, superclasses, attributedict) type.__init__(cls, classname, superclasses, attributedict) Die __new__-Methode erstellt und liefert ein neu geschaffenes Klassenobjekt zurück. Anschließend initialisiert die __init__-Methode das erstellte Objekt. class Robot: counter = 0 def __init__(self, name): self.name = name 26.1 Beziehung zwischen „class” und „type” def sayHello(self): return "Hi, I am " + self.name Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def Rob_init(self, name): self.name = name Robot2 = type("Robot2", (), {"counter":0, "__init__": Rob_init, "sayHello": lambda self: "Hi, I am " + self.name}) x = Robot2("Marvin") print(x.name) print(x.sayHello()) y = Robot("Marvin") print(y.name) print(y.sayHello()) print(x.__dict__) print(y.__dict__) Nach Ausführung des Codes erhalten wir folgende Ausgabe: Marvin Hi, I am Marvin Hi, I am {'name': {'name': Marvin Marvin 'Marvin'} 'Marvin'} Die Klassendefinitionen von Robot und Robot2 sind syntaktisch völlig verschieden, aber logisch gesehen implementieren beide genau das Gleiche. Python sorgt im ersten Beispiel dafür, dass alle Methoden und Attribute der Klasse Robot gesammelt werden, um sie dann als Parameter dem Argument attributes_dict beim type-Aufruf zu übergeben. Python macht also das Gleiche, wie wir es mit der Klasse Robot2 gemacht haben. 283 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 27 27.1 Metaklassen Motivation In diesem Kapitel möchten wir einige Ideen und Anreize zur Nutzung von Metaklassen präsentieren. Um ein paar Designprobleme zu demonstrieren, die mit Metaklassen gelöst werden können, erstellen wir diverse Philosopher-Klassen. Jede Philosopher-Klasse (Philosopher1, Philosopher2 und so weiter) benötigt die gleiche „Menge” an Methoden. In Bild 27.1 Plato unserem Beispiel nur eine - the_answer - als Basis ihres Grübelns und Nachdenkens. Ein schlechte Implementierung erreichen wir, indem wir in jede Philosopher-Klasse den identischen Code schreiben: class Philosopher1: def the_answer(self, *args): return 42 class Philosopher2: def the_answer(self, *args): return 42 class Philosopher3: def the_answer(self, *args): return 42 plato = Philosopher1() print(plato.the_answer()) kant = Philosopher2() # let's see what Kant has to say :-) print(kant.the_answer()) Das Programm liefert die folgende Ausgabe, was wohl nicht schwer nachzuvollziehen ist: 286 27 Metaklassen 42 42 Wir stellen fest, dass wir mehrere Kopien der Methode the_answer implementiert haben. Das ist ziemlich fehleranfällig und bedeutet mehr Aufwand zum Warten des Programms. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Bisher wissen wir, dass man redundanten Code am einfachsten dadurch vermeidet, dass man eine Basis schafft, welche die Methode the_answer enthält. Jede Philosopher-Klasse erbt dann von dieser Basisklasse: class Answers: def the_answer(self, *args): return 42 class Philosopher1(Answers): pass class Philosopher2(Answers): pass class Philosopher3(Answers): pass plato = Philosopher1() print(plato.the_answer()) kant = Philosopher2() # let's see what Kant has to say :-) print(kant.the_answer()) Auch hier erhalten wir wieder die gleiche Ausgabe wie im oberen Beispiel, also zweimal die Zahl 42. Auf diese Art hat jede Philosopher-Klasse immer die Methode the_answer. Nehmen wir an, dass wir noch nicht wissen, ob die Methode gebraucht wird. Nehmen wir weiter an, dass die Entscheidung darüber, ob die Methode gebraucht wird, zur Laufzeit getroffen wird. Diese Entscheidung kann abhängig sein von Konfigurationsdateien, Benutzereingaben oder Berechnungen. # the following variable would be set as the result of a runtime calculation: x = input("Do you need the answer? (y/n): ") if x == "y": required = True else: required = False def the_answer(self, *args): return 42 27.1 Motivation class Philosopher1: pass if required: Philosopher1.the_answer = the_answer Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class Philosopher2: pass if required: Philosopher2.the_answer = the_answer class Philosopher3: pass if required: Philosopher3.the_answer = the_answer plato = Philosopher1() kant = Philosopher2() # let's see what Plato and Kant have to say :-) if required: print(kant.the_answer()) print(plato.the_answer()) else: print("The silence of the philosophers") Das Programm liefert folgende Ausgabe: Do you need the answer? (y/n): y 42 42 Falls wir die Frage nicht mit „y” beantworten, sieht das Ergebnis so aus: Do you need the answer? (y/n): n The silence of the philosophers Auch bei dieser Lösung gibt es noch Nachteile. Sie ist ebenfalls fehleranfällig, weil wir wieder den gleichen Code zu jeder Klasse schreiben müssen. Wenn wir viele Methoden hinzufügen wollen, kann das ziemlich unübersichtlich werden. Wir können unseren Ansatz verbessern, indem wir eine Manager-Funktion definieren, um redundanten Code zu vermeiden. Die Manager-Funktion übernimmt die Aufgabe, die Klassen entsprechend zu erweitern. # the following variable would be set as the result of a runtime calculation: x = input("Do you need the answer? (y/n): ") 287 288 27 Metaklassen if x == "y": required = True else: required = False Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def the_answer(self, *args): return 42 # manager function def augment_answer(cls): if required: cls.the_answer = the_answer class Philosopher1: pass augment_answer(Philosopher1) class Philosopher2: pass augment_answer(Philosopher2) class Philosopher3: pass augment_answer(Philosopher3) plato = Philosopher1() kant = Philosopher2() # let's see what Plato and Kant have to say :-) if required: print(kant.the_answer()) print(plato.the_answer()) else: print("The silence of the philosophers") Dies ist eine brauchbare Lösung für unser Problem. Jedoch müssen wir darauf achten, dass wir den Aufruf der Manager-Funktion augment_answer nicht vergessen. Der Code sollte automatisch aufgerufen werden. Wir brauchen eine Möglichkeit, um sicherzustellen, dass „bestimmter” Code automatisch im Anschluss einer Klassendefinition ausgeführt wird. Im Folgenden benutzen wir einen Klassendekorator, d.h. wir schreiben die augment_answer-Funktion als Dekoratorfunktion um. Die Philosopher-Klassen können wir nun entsprechend dekorieren: 27.1 Motivation Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. # the following variable would be set as the result of a runtime calculation: x = input("Do you need the answer? (y/n): ") if x == "y": required = True else: required = False def the_answer(self, *args): return 42 def augment_answer(cls): if required: cls.the_answer = the_answer # we have to return the class now: return cls @augment_answer class Philosopher1: pass @augment_answer class Philosopher2: pass @augment_answer class Philosopher3: pass plato = Philosopher1() kant = Philosopher2() # let's see what Plato and Kant have to say :-) if required: print(kant.the_answer()) print(plato.the_answer()) else: print("The silence of the philosophers") Das Programm liefert folgende Ausgabe: Do you need the answer? (y/n): y 42 42 289 290 27 Metaklassen 27.2 Definition Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Eine Metaklasse ist eine Klasse, deren Instanzen Klassen sind. Wie eine „gewöhnliche” Klasse das Verhalten ihrer Instanzen definiert, so definiert auch eine Metaklasse das Verhalten ihrer Instanzen, aber die Instanzen sind in diesem Fall Klassen. Metaklassen werden nicht von allen objektorientierten Programmiersprachen unterstützt. Die Programmiersprachen, die sie implementiert haben, unterscheiden sich jedoch erheblich in der Art, wie die Metaklassen implementiert sind. Manche Programmierer sehen Metaklassen in Python als „Lösungen, die auf ein Problem warten”. Dennoch gibt es zahlreiche mögliche Anwendungsgebiete für Metaklassen. Um nur einige zu nennen: ■ Messwerterfassung ■ Profiling ■ Konsistenzprüfungen von Schnittstellen ■ Registrierung von Klassen nach ihrer Entstehungszeit ■ Automatisches Hinzufügen von Methoden ■ Automatische Erzeugung von Properties ■ Proxies ■ Automatisches Locking und Synchronisation 27.3 Definition von Metaklassen in Python Prinzipiell werden Metaklassen genau so definiert wie jede andere Klasse in Python. Jedoch erben Metaklassen von type. Ein weiterer Unterschied ist, dass eine Metaklasse automatisch aufgerufen wird, wenn das Klassen-Statement eine Metaklassen-Endung bekommt. Mit anderen Worten: Wenn kein metaclass-Schlüsselwort im Klassen-Header benutzt wird, so wird type() aufgerufen. Wenn metaclass im Klassen-Header steht, wird die zugewiesene Klasse aufgerufen statt type(). Wir erstellen eine einfache Metaklasse. Sie ist nutzlos, außer dass sie den Inhalt der Argumente in der __new__ Methode ausgibt und die Ergebnisse von type.__new__ zurückgibt: class LittleMeta(type): def __new__(cls, clsname, superclasses, attributedict): print("clsname: ", clsname) print("superclasses: ", superclasses) print("attributedict: ", attributedict) return type.__new__(cls, clsname, superclasses, attributedict) 27.3 Definition von Metaklassen in Python Im folgenden Beispiel benutzen wir die Metaklasse „LittleMeta”: class S: pass Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class A(S, metaclass=LittleMeta): pass a = A() Die Ausgabe bestätigt das bisher Gesagte: clsname: A superclasses: (<class '__main__.S'>,) attributedict: {'__qualname__': 'A', '__module__': '__main__'} Wir sehen, dass LittleMeta.__new__ aufgerufen wird und nicht type.__new__. Kommen wir auf das letzte Kapitel zurück: Wir definieren eine Metaklasse „EssentialAnswers”, die fähig ist, die Methode augment_answer automatisch anzufügen: x = input("Do you need the answer? (y/n): ") if x == "y": required = True else: required = False def the_answer(self, *args): return 42 class EssentialAnswers(type): def __init__(cls, clsname, superclasses, attributedict): if required: cls.the_answer = the_answer class Philosopher1(metaclass=EssentialAnswers): pass class Philosopher2(metaclass=EssentialAnswers): pass class Philosopher3(metaclass=EssentialAnswers): pass plato = Philosopher1() print(plato.the_answer()) kant = Philosopher2() # let's see what Kant has to say :-) print(kant.the_answer()) 291 292 27 Metaklassen Die folgende Ausgabe stellt keine Überraschung dar: Do you need the answer? (y/n): y 42 42 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir haben im Kapitel „Beziehungen zwischen Klasse und Typ” gelernt, dass Python nach der Klassendefinition folgenden Aufruf macht: type(classname, superclasses, attributes_dict) Das passiert nicht, wenn eine Metaklasse im Header definiert wurde. Das haben wir im vorigen Beispiel gemacht. Unsere Klassen Philosopher1, Philosopher2 und Philosopher3 wurden mit der Metaklasse EssentialAnswers gekoppelt. Darum wird EssentialAnswers ausgeführt statt type. EssentialAnswers(classname, superclasses, attributes_dict) Genauer gesagt werden die Argumente des Aufrufs wie folgt gesetzt: EssentialAnswers('Philopsopher1', (), {'__module__': '__main__', '__qualname__': ' Philosopher1'}) Analog dazu natürlich auch die anderen Philosopher-Klassen. 27.4 Singletons mit Metaklassen erstellen Als weiteres Beispiel der Anwendung einer Metaklasse implementieren wir nun eine Singleton-Klasse. Das Singleton-Muster ist ein Entwurfsmuster, welches die Erstellung von Instanzen auf eine beschränkt. Es wird dann genutzt, wenn nur ein Objekt gebraucht wird. Das Konzept kann, etwas verallgemeinert, auf die Instanziierung einer bestimmten Anzahl von Objekten begrenzt werden. In der Mathematik werden Singletons für Mengen mit exakt einem Element benutzt. class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args , **kwargs) return cls._instances[cls] class SingletonClass(metaclass=Singleton): pass class RegularClass(): pass 27.5 Beispiel – Methodenaufrufe zählen x = SingletonClass() y = SingletonClass() print(x == y, id(x), id(y)) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. x = RegularClass() y = RegularClass() print(x == y, id(x), id(y)) Die Prüfung auf Gleichheit ist im vorigen Beispiel äquivalent mit der Prüfung, ob die Identitäten gleich sind. Es wird also geprüft, ob es sich um dasselbe Objekt handelt, wie wir in der Ausgabe des Programms sehen können: True 140311429714720 140311429714720 False 140311429714776 140311429714832 27.5 27.5.1 Beispiel – Methodenaufrufe zählen Einführung Nach der Durcharbeit des Kapitels „Metaklassen” fragen Sie sich vielleicht, was mögliche Anwendungsfälle für Metaklassen sein könnten. Es gibt ein paar interessante Anwendungsfälle, und es ist nicht, wie wir anfangs zitiert hatten, „eine Lösung, die auf ein Problem wartet”. Ein paar Beispiele hatten wir bereits genannt. In diesem Kapitel möchten wir ein Metaklassenbeispiel ausarbeiten, das die Methoden der Subklasse dekoriert. Die dekorierte Funktion, die vom Dekorateur zurückgeliefert wird, macht es möglich, die Anzahl der Aufrufe der Subklasse zu zählen. Das ist normalerweise eine der Aufgaben, die wir von einem Profiler erwarten. Wir können also die Metaklasse für einfaches Profiling benutzen. Die Metaklasse ist einfach zu erweitern für weitere Profiling-Aufgaben. 27.5.2 Vorbereitungen Bevor wir uns des Problems annehmen, möchten wir noch daran erinnern, wie auf Attribute einer Klasse zugegriffen wird. Wir demonstrieren dies nochmal anhand einer list-Klasse. Wir erhalten die Liste aller nicht privaten Attribute einer Klasse – hier die random-Klasse – mit folgendem Code: import random cls = "random" # Name der Klasse als String all_attributes = [x for x in dir(eval(cls)) if not x.startswith("__")] print(all_attributes) 293 294 27 Metaklassen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir erhalten folgende Ausgabe: ['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'Random', 'SG_MAGICCONST ', 'SystemRandom', 'TWOPI', '_BuiltinMethodType', '_MethodType', ' _Sequence', '_Set', '_acos', '_ceil', '_cos', '_e', '_exp', '_inst ', '_log', '_pi', '_random', '_sha512', '_sin', '_sqrt', '_test', '_test_generator', '_urandom', '_warn', 'betavariate', 'choice', ' expovariate', 'gammavariate', 'gauss', 'getrandbits', 'getstate', 'lognormvariate', 'normalvariate', 'paretovariate', 'randint', ' random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', ' triangular', 'uniform', 'vonmisesvariate', 'weibullvariate'] Anschließend filtern wir noch die aufrufbaren Attribute heraus bzw. die public-Methoden der Klasse: methods = [x for x in dir(eval(cls)) if not x.startswith("__") and callable(eval(cls + "." + x))] print(methods) Nun erhalten wir folgende Ausgabe: ['Random', 'SystemRandom', '_BuiltinMethodType', '_MethodType', ' _Sequence', '_Set', '_acos', '_ceil', '_cos', '_exp', '_log', ' _sha512', '_sin', '_sqrt', '_test', '_test_generator', '_urandom', '_warn', 'betavariate', 'choice', 'expovariate', 'gammavariate', 'gauss', 'getrandbits', 'getstate', 'lognormvariate', ' normalvariate', 'paretovariate', 'randint', 'random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', 'triangular', 'uniform', 'vonmisesvariate', 'weibullvariate'] Um die nicht aufrufbaren Attribute zu filtern, können wir das Schlüsselwort „not” bei callable einfügen. non_callable_attributes = [x for x in dir(eval(cls)) if not x. startswith("__") and not callable(eval(cls + "." + x))] print(non_callable_attributes) Ausgabe: ['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'SG_MAGICCONST', 'TWOPI ', '_e', '_inst', '_pi', '_random'] Normalerweise ist es weder empfohlen noch notwendig, Methoden wie folgt zu verwenden, aber es ist möglich: lst = [3,4] list.__dict__["append"](lst, 42) lst [3, 4, 42] 27.5 Beispiel – Methodenaufrufe zählen 27.5.3 Ein Dekorateur, um Funktionsaufrufe zu zählen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Nun wollen wir beginnen, die Metaklasse zu erstellen, die wir bereits am Anfang dieses Kapitels angesprochen haben. Sie wird alle Methoden der Subklasse dekorieren mit dem Dekorateur, der die Aufrufe zählt. Wir haben diesen Dekorateur bereits im Kapitel „Memoisation und Dekorateure” definiert: def call_counter(func): def helper(*args, **kwargs): helper.calls += 1 return func(*args, **kwargs) helper.calls = 0 helper.__name__= func.__name__ return helper Wir können ihn wie gewohnt verwenden: @call_counter def f(): pass print(f.calls) for _ in range(10): f() print(f.calls) Ausgabe: 0 10 27.5.4 Die Metaklasse „Aufrufzähler” Jetzt haben wir alle „Zutaten” beisammen, um unsere Metaklasse zu schreiben. Wir fügen unseren call_counter-Dekorateur als static-Methode ein: class FuncCallCounter(type): """ A Metaclass which decorates all the methods of the subclass using call_counter as the decorator """ @staticmethod def call_counter(func): """ Decorator for counting the number of function or method calls to the function or method func """ def helper(*args, **kwargs): helper.calls += 1 295 296 27 Metaklassen return func(*args, **kwargs) helper.calls = 0 helper.__name__= func.__name__ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. return helper def __new__(cls, clsname, superclasses, attributedict): """ Every method gets decorated with the decorator call_counter, which will do the actual call counting """ for attr in attributedict: if callable(attributedict[attr]) and not attr.startswith(" __"): attributedict[attr] = cls.call_counter(attributedict[ attr]) return type.__new__(cls, clsname, superclasses, attributedict) class A(metaclass=FuncCallCounter): def foo(self): pass def bar(self): pass if __name__ == "__main__": x = A() print(x.foo.calls, x.bar.calls) for i in range(10): x.foo() for i in range(5): x.bar() print(x.foo.calls, x.bar.calls) Wir erhalten folgende Ausgabe: 0 0 10 5 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 28 Abstrakte Klassen Ein Klasse gilt in der objektorientierten Programmierung als abstrakt, wenn sie sich nicht instanziieren lässt. Es lassen sich also keine Objekte von dieser Klasse erzeugen. Abstrakte Klassen enthalten eine oder mehrere abstrakte Methoden. Unter einer abstrakten Methode versteht man eine Bild 28.1 Abstrakte Basis Klassen Methode, die deklariert, aber nicht implementiert wird. Leitet eine Klasse von einer abstrakten Klasse ab, müssen alle vererbten abstrakten Methoden überschrieben und implementiert werden, ansonsten würde die erbende Klasse selbst zu einer abstrakten Klasse. Da man in Python unter anderem keine Deklarationen kennt, kann man in Python „normalerweise” keine abstrakten Klassen definieren. Wir demonstrieren dies im folgenden Beispiel: class AbstractClass: def do_something(self): pass class B(AbstractClass): pass a = AbstractClass() b = B() Wenn wir dieses Programm starten, erkennen wir, dass es sich nicht um eine abstrakte Klasse handelt, da ■ ■ wir sie instanziieren können, d.h. a = AbstractClass() erhebt keine Ausnahme. wir nicht gezwungen werden, do_something in der Klassendefinition von B zu überlagern. Dennoch gibt es eine Möglichkeit, abstrakte Klassen in Python zu definieren. Zu diesem Zweck wurde in Python das Modul abc eingeführt. abc ist ein Akronym für „Abstract Base Classes” (ABCs). 298 28 Abstrakte Klassen Im folgenden Beispiel zeigen wir, wie wir mit dem Modul abc und der dort definierten Klasse ABC eine abstrakte Klasse erzeugen können: from abc import ABC, abstractmethod Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class AbstractClassExample(ABC): def __init__(self, value): self.value = value super().__init__() @abstractmethod def do_something(self): pass Die Methode do_something haben wir mithilfe des Dekorators abstractmethod zu einer abstrakten Methode gemacht. Dekoriert man eine Methode in einer abstrakten Klasse mit abstractmethod, wird diese Methode zu einer abstrakten Methode. Außerdem wird sichergestellt, dass eine erbende Klasse diese Methode implementieren muss, d.h. sie muss sie überlagern. Wir zeigen im folgenden Beispiel, dass es nicht möglich ist, von AbstractClassExample zu erben und in der erbenden Klasse die Methode do_something nicht zu überlagern: class DoAdd42(AbstractClassExample): pass x = DoAdd42(4) Man erhält folgende Ausgabe, wenn man obiges Programm startet: ---------------------------------------------------------------------TypeError Traceback (most recent call last) <ipython-input-9-83fb8cead43d> in <module>() 2 pass 3 ----> 4 x = DoAdd42(4) TypeError: Can't instantiate abstract class DoAdd42 with abstract methods do_something Im folgenden Code-Beispiel definieren wir nun zwei korrekte Unterklassen: class DoAdd42(AbstractClassExample): def do_something(self): return self.value + 42 class DoMul42(AbstractClassExample): def do_something(self): return self.value * 42 28 Abstrakte Klassen x = DoAdd42(10) y = DoMul42(10) print(x.do_something()) print(y.do_something()) Die Ausgabe sieht wie folgt aus: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 52 420 Falls der Eindruck entstanden sein sollte, dass wir die Methoden in der abstrakten Klasse nur mittels pass definieren könnten, so trügt dies. Eine Methode kann ganz „normal” Code im Body enthalten, aber dennoch muss diese Methode in der Unterklasse überlagert werden. Der Vorteil liegt jedoch darin, dass wir die Methode aus der abstrakten Klasse mittels super() aufrufen können. Dies ermöglicht es uns, eine grundlegende Funktionalität in der abstrakten Methode unterzubringen, welche dann durch die überlagernden Implementierungen der erbenden Klassen bereichert werden: from abc import ABC, abstractmethod class AbstractClassExample(ABC): @abstractmethod def do_something(self): print("Grundlegende Funktionalitäten!") class AnotherSubclass(AbstractClassExample): def do_something(self): super().do_something() print("Bereicherungen durch AnotherSubclass!") x = AnotherSubclass() x.do_something() Grundlegende Funktionalitäten! Bereicherungen durch AnotherSubclass! 299 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 29 Aufgaben 1. Aufgabe: Wir haben folgende einfache Roboterklasse geschrieben: class Robot(object): def __init__(self, name): self.name = name x = Robot("Marvin") y = Robot("Hugo") print(x.name, y.name) Diese Klasse erfreut sich nun weltweit großer Beliebtheit. Wir haben allerdings ein Problem: Die internationale Robotergewerkschaft konnte ein weltweites Verbot durchsetzen, dass Roboter nicht mehr „Hugo” genannt werden dürfen. Schreiben Sie nun die Klasse so um, dass Roboter automatisch „Marvin” genannt werden, wenn jemand versucht, sie „Hugo” zu taufen. Für die Benutzer der Klasse darf sich natürlich nichts ändern, d.h. die letzten drei Zeilen müssen unverändert bleiben können. Lösung: 1. Aufgabe, Seite 502 2. Aufgabe: In dieser Aufgabe geht wieder um eine Roboterklasse. Uns interessiert nicht das Aussehen und die Beschaffenheit eines Roboters, sondern nur seine Position in einer imaginären „Landschaft”, die zweidimensional sein soll und durch ein Koordinatensystem beschrieben werden kann. Bild 29.1 Roboter Ein Roboter hat also zwei Attribute für die x- und die yKoordinate. Es empfiehlt sich, diese Informationen in einer 2erListe zusammenzufassen, also beispielsweise position = [3,4], wobei dann 3 der x-Position und 4 der y-Position entspricht. Der Roboter ist in eine der vier Richtungen „west”, „south”, „east” oder „north” orientiert, was wir in einem Attribut speichern wollen. Außerdem sollten unsere Roboter auch Namen haben. Allerdings dürfen die Namen nicht länger als 10 Zeichen sein. Sollte 302 29 Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. jemand versuchen, dem Roboter einen längeren Namen zuzuweisen, soll der Name auf 10 Zeichen abgeschnitten werden. Um die Roboter im Koordinatensystem bewegen zu können, benötigen wir eine moveMethode. Die Methode erwartet einen Parameter „distance”, der angibt, um welchem Betrag sich der Roboter in Richtung der eingestellten Orientierung bewegen soll. Wird ein Roboter x beispielsweise mit x.move(10) aufgerufen und ist dieser Roboter östlich orientiert, also x.orientation == "east", und ist [3,7] die aktuelle Position des Roboters, dann bewegt er sich 10 Felder östlich und befindet sich anschließend in Position [3,17]. Lösung: 2. Aufgabe, Seite 502 3. Aufgabe: Erweitern Sie die folgende Roboterklasse class Roboter: def __init__(self, name, baujahr): self.name = name self.baujahr = baujahr um ein Attribut „energie” und eine Property „befinden”, die vier Stringwerte liefert: ■ „In Anbetracht meines Alters nicht so schlecht!” falls das Alter das Roboters größer als 20 und der Energiewert kleiner als 50 ist. ■ „Super. Vor allem in Anbetracht meines Alters!” falls das Alter das Roboters größer als 20 und der Energiewert nicht kleiner 50 ist. ■ „Ausgelaugt” falls das Alter das Roboters kleiner oder gleich 20 ist und der Energiewert kleiner als 50 ist. ■ „Super!” falls das Alter das Roboters größer 20 und der Energiewert nicht kleiner 50 ist. Lösung: 3. Aufgabe, Seite 505 4. Aufgabe: Neandertal- oder Bierdeckelarithmetik: Üblicherweise zählen und rechnen wir im Dezimalsystem. Informatiker und Programmierer haben manchmal auch Kontakt mit dem Binärsystem. Aber es gibt auch eine Berufsgruppe, die von Berufs wegen mit einem unären Zahlsystem rechnet. Ja richtig, in der Gastronomie. In der folgenden Aufgabe geht es um die Bierdeckelnotation oder auch Bierdeckelarithmetik. Es könnte auch die Art gewesen sein, wie unsere in Höhlen lebenden Vorfahren mit Zahlen umgegangen sind. Das Unärsystem ist ein Zahlensystem, in dem lediglich eine Ziffer vorhanden ist, die man üblicherweise mit einem senkrechten Strich bezeichnet. Also der Strich auf dem Bierdeckel. Man könnte aber ebenso gut eine Null, eine Eins oder irgendein anderes Zeichen verwenden. 29 Aufgaben Es gilt: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 7d ezi mal = |||||||unär Bild 29.2 Neandertal-Arithmetik Schreiben Sie eine Klasse CaveInt, die die vier Grundrechenarten für die Neandertal-Arithmetik definiert. Auch wenn die Höhlenmenschen keine Null kannten, definieren wir einen leeren String als die Null. Da es keine negativen Zahlen gibt, definieren wir die Subtraktion wie folgt: x - y ist gleich dem leeren String, wenn x kleiner als y ist. Außerdem soll eine Cast-Methode für die Wandlung von unär nach int bereitgestellt werden. Lösung: 4. Aufgabe, Seite 506 5. Aufgabe: Wir haben in diesem Kapitel die Standardklasse list um die Methode push erweitert. In Perl gibt es noch die Methode „splice”, mit der eine Folge von Elementen aus einer Liste gelöscht werden kann und durch eine Liste von anderen Elementen ersetzt wird. splice(offset, length, replacement) „offset” entspricht dem ersten Index, ab dem die Elemente gelöscht werden sollen, und der Parameter „length” gibt an, wie viele Elemente gelöscht werden sollen. An die Stelle der gelöschten Elemente sollen die Elemente der Liste replacement eingefügt werden. Erweitern Sie nun die Klasse plist um eine Methode splice. Lösung: 4. Aufgabe, Seite 506 6. Aufgabe: Erweitern Sie die Klasse Length, die wir im Kapitel vorgestellt haben, um eine Subtraktion, Multiplikation und Division. Zusätzlich können Sie sie noch um die erweiterten Zuweisungen erweitern, d.h. „+=”, „-=” usw. Lösung: 6. Aufgabe, Seite 508 303 304 29 Aufgaben 7. Aufgabe: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Schreiben Sie eine abstrakte Klasse BasisFigure, die zwei Attribute size1 und size2 hat. Außerdem soll diese Klasse zwei abstrakte Methoden area (Fläche) und circumference (Umfang) definieren. Schreiben Sie dann zwei Unterklassen Rectangle (Rechteck) und Circle (Kreis) als Konkretisierungen dieser abstrakten Klasse. x = Rectangle(2, 3) print(x.circumference()) # gibt den Umfang des Rechtecks zurück y = Circle(3) print(y.area()) # gibt die Fläche des Kreises mit dem Radius 3 zurück Lösung: 7. Aufgabe, Seite 511 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Teil III Fortgeschrittenes Python Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 30 lambda, map, filter und reduce Wenn es nach Guido van Rossum, dem Autor von Python, gegangen wäre, würde dieses Kapitel in unserem Buch wahrscheinlich fehlen. Guido van Rossum hatte lambda, reduce(), filter() und map() noch nie gemocht und sie bereits 1993 widerwillig in Python aufgenommen, nachdem er eine Codeerweiterung mit diesen Funktionalitäten von einem, wie er glaubte, Lisp-Hacker erhalten hatte. In Python 3 sollten sie nach seinem Willen verschwinden.1 Dazu kann man die folgenden Gründe angeben: ■ ■ ■ Es gibt eine gleich mächtige Alternative zu lambda, filter, map und reduce, nämlich die Listen-Abstraktion (englisch: list comprehension). Die Listen-Abstraktion ist klarer und leichter verständlich. Hat man sowohl die Listen-Abstraktion als auch „Lambda und Co.”, dann verletzt dies das Python-Motto „Es soll (genau) einen offensichtlichen Weg geben, um ein Problem zu lösen” („There should be one obvious way to solve a problem”). 30.1 lambda Eine anonyme Funktion oder Lambda-Funktion ist eine Funktion, die nicht über einen Namen verfügt. Eine solche Funktion kann deshalb nur über Verweise angesprochen werden. Der lambda-Operator bietet eine Möglichkeit, anonyme Funktionen, also Funktionen ohne Namen, zu schreiben und zu benutzen. Lambda-Funktionen kommen aus der funktionalen Programmierung und wurden insbesondere durch die Programmiersprache Lisp besonders bekannt. Sie können eine beliebige Anzahl von Parametern haben, führen einen Ausdruck aus und liefern den Wert dieses Ausdrucks als Rückgabewert zurück. 1 Bild 30.1 Dateien „About 12 years ago, Python aquired lambda, reduce(), filter() and map(), courtesy of (I believe) a Lisp hacker who missed them and submitted working patches. But, despite of the PR value, I think these features should be cut from Python 3000.”, Guido van Rossum in „The fate of reduce() in Python 3000”, 10. März 2005 308 30 lambda, map, filter und reduce Anonyme Funktionen sind insbesondere bei der Anwendung der map-, filter- und reduceFunktionen besonders vorteilhaft. Schauen wir uns ein einfaches Beispiel einer lambda-Funktion an. lambda x: x + 42 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beim obigen Beispiel handelt es sich um eine Funktion mit einem Argument „x”, die die Summe von x und 42 zurückgibt. Die allgemeine Syntax einer Lambda-Funktion sieht wie folgt aus: lambda Argumentenliste: Ausdruck Die Argumentenliste besteht aus einer durch Kommata getrennten Liste von Argumenten, und der nach dem Doppelpunkt stehende Ausdruck ist ein beliebiger Ausdruck, der diese Argumente benutzt. Doch kommen wir zurück zu unserem einführenden Beispiel. Wie können wir unsere Funktion lambda x: x + 42 benutzen? >>> 45 >>> >>> -55 >>> ... ... 42 43 44 45 46 47 48 49 50 51 (lambda x: x + 42)(3) y = (lambda x: x + 42)(3) - 100 print(y) for i in range(10): (lambda x: x + 42)(i) Natürlich kann man mit obigem Code viele beeindrucken, aber man hätte es natürlich auch viel einfacher hinschreiben können: >>> for i in range(10): ... print(42 + i) ... 42 43 44 30.1 lambda Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 45 46 47 48 49 50 51 Eine andere Möglichkeit besteht darin, den lambda-Ausdruck einer Variablen zuzuweisen. Mit diesem Namen können wir unsere Funktion jetzt wie eine „gewöhnliche” Funktion f benutzen: >>> f42 = lambda x: x + 42 >>> f42(4) 46 Aber dazu brauchten wir nicht die lambda-Notation. Wir hätten dies auch mit einer normalen Funktionsdefinition bewerkstelligen können: >>> def f42(x): ... return x + 42 ... >>> f42(4) 46 Nun kommen wir endlich zu einer sinnvollen Anwendung der lambda-Notation. Wir schreiben eine Funktion mit dem Namen „anwenden”, die eine Funktion als erstes Argument und eine Liste als zweites Argument erwartet. Die Funktion „anwenden” wendet auf jedes Element der übergebenen Liste die als erstes Argument übergebene Funktion an: >>> def anwenden(f,liste): ... ergebnis = [] ... for element in liste: ... ergebnis.append(f(element)) ... return ergebnis Wir können nun die Funktion „anwenden” mit unserer f42-Funktion und der Liste der Zahlen von 0 bis 9 aufrufen: >>> anwenden(f42,range(10)) [42, 43, 44, 45, 46, 47, 48, 49, 50, 51] Wir haben oben den Funktionsnamen als Referenz auf unsere Funktion übergeben. Um diese Funktion zu benutzen, hatten wir zuerst eine Funktion mit dem (hässlichen) Namen f42 einführen müssen. Die Funktion f42 ist eine „Wegwerffunktion”, die wir nur einmal bei dem Funktionsaufruf von „anwenden” benötigen. Sie können sich leicht vorstellen, dass wir gegebenenfalls auch ähnliche Funktionen wie f43, f44 usw. benötigen könnten. Aus diesem Grund wäre es natürlich bedeutend eleganter, wenn wir diese Funktionen direkt an unsere Funktion „anwenden” übergeben könnten, also ohne den Umweg mit der Namensgebung. Dies ist mit der lambda-Notation möglich: 309 310 30 lambda, map, filter und reduce >>> anwenden(lambda x: x + 42,range(10)) [42, 43, 44, 45, 46, 47, 48, 49, 50, 51] Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Auch solche Anwendungen sind jetzt möglich: >>> for i in [17, 22,42]: ... anwenden(lambda x: x + i, range(10)) ... [17, 18, 19, 20, 21, 22, 23, 24, 25, 26] [22, 23, 24, 25, 26, 27, 28, 29, 30, 31] [42, 43, 44, 45, 46, 47, 48, 49, 50, 51] In obigem Beispiel haben wir mithilfe des Schleifenparameters i drei verschiedene Funktionen kreiert. 30.2 map Nachdem wir uns im vorigen Kapitel intensiv mit der Funktion „anwenden” beschäftigt hatten, stellt die von Python zur Verfügung gestellte Funktion „map” kein Problem dar. Im Prinzip entspricht „map” unserer Funktion „anwenden”. map ist eine Funktion mit zwei Argumenten: r = map(func, seq) Das erste Argument func ist eine Funktion und das zweite eine Sequenz (z.B. eine Liste oder ein Tupel) seq. map wendet die Funktion func auf alle Elemente von seq an und schreibt die Ergebnisse in ein map object, also ein Iterator. Rufen wir map(lambda x: x + 42, range(10)) auf, entspricht dies fast unserem Aufruf anwenden(lambda x: x + 42, range(10)) Damit es ist völlig gleich ist, müssen wir lediglich das map-Objekt noch in eine Liste wandeln: >>> list(map(lambda x: x [42, 43, 44, 45, 46, 47, >>> anwenden(lambda x: x [42, 43, 44, 45, 46, 47, + 42, range(10))) 48, 49, 50, 51] + 42, range(10)) 48, 49, 50, 51] In einem weiteren Beispiel wollen wir nun zeigen, welchen großen Vorteil diese Kombination aus lambda und map-Funktion mit sich bringt. Nehmen wir an, dass wir Listen mit Temperaturwerten in Grad Celsius und Grad Fahrenheit haben. Wir möchten diese wechselseitig in die jeweils andere Temperaturskala wandeln. Eine Temperatur C in Grad Celsius lässt sich mittels der Formel 9 ∗ C + 32 5 30.2 map in Grad Fahrenheit wandeln, und eine Temperatur F in Grad Fahrenheit lässt sich mittels der Formel 5 ∗ (F − 32) 9 in Grad Celsius wandeln. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Für das folgende Beispiel „vergessen” wir nun „lambda” und „map” und beschränken uns auf eine „konventionelle” Programmierung: def fahrenheit(T): return ((9.0 / 5) * T + 32) def celsius(T): return (5.0 / 9) * ( T - 32 ) temp = (36.5, 37, 37.5,39) def Fahrenheit2Celsius(F_liste): erg = [] for F in F_liste: erg.append(celsius(F)) return erg def Celsius2Fahrenheit(C_liste): erg = [] for C in C_liste: erg.append(fahrenheit(C)) return erg F_liste = Celsius2Fahrenheit(temp) print(F_liste) C_liste = Fahrenheit2Celsius(F_liste) print(C_liste) Unter Benutzung von lambda und map schrumpft unser obiges Codebeispiel in beachtlicher Weise: temp = (36.5, 37, 37.5,39) F_liste = list(map(lambda C: (5.0 / 9) * ( C - 32 ), temp)) print(F_liste) C_liste = map(lambda F: (9.0 / 5) * F + 32, F_liste) print(list(C_liste)) map kann auch gleichzeitig auf mehrere Listen angewendet werden. Dann werden die Argumente entsprechend ihrer Position und der Reihenfolge der Listenargumente mit den Werten aus den Listen versorgt. 311 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 312 30 lambda, map, filter und reduce >>> a = [1,2,3,4] >>> b = [17,12,11,10] >>> c = [-1,-4,5,9] >>> list(map(lambda x,y:x+y, a,b)) [18, 14, 14, 14] >>> list(map(lambda x,y,z:x+y+z, a,b,c)) [17, 10, 19, 23] >>> list(map(lambda x,y,z : 2.5*x + 2*y - z, a,b,c)) [37.5, 33.0, 24.5, 21.0] >>> Wir sehen in dem obigen Beispiel, dass der Parameter x seine Werte aus der Liste a bezieht, der Parameter y seine Werte aus der Liste b und der Parameter z schließlich seine Werte aus der Liste c. 30.3 Filtern mit „filter” Die Funktion filter(funktion, liste) bietet eine elegante Möglichkeit, diejenigen Elemente aus der Liste liste herauszufiltern, für die die Funktion „funktion” True liefert. Die Funktion filter(f,iter) benötigt als erstes Argument eine Funktion f, die Wahrheitswerte liefert. Diese Funktion wird dann auf jedes Argument des Objekts „iter” angewendet. „iter” ist entweder ein sequentieller Datentyp wie beispielsweise eine Liste oder ein Tupel oder es ist ein iterierbares Objekt. Liefert f True für ein x, dann wird x in der Ergebnisliste übernommen, ansonsten wird x nicht übernommen. >>> >>> >>> [1, >>> >>> [0, >>> >>> >>> ... >>> >>> [0, >>> fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] odd_numbers = list(filter(lambda x: x % 2, fibonacci)) print(odd_numbers) 1, 3, 5, 13, 21, 55] even_numbers = list(filter(lambda x: x % 2 == 0, fibonacci)) print(even_numbers) 2, 8, 34] # or alternatively: even_numbers = list(filter(lambda x: x % 2 -1, fibonacci)) print(even_numbers) 2, 8, 34] 30.4 reduce 30.4 reduce Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Eingangs des Kapitels erwähnten wir bereits, dass Guido van Rossum lambda, map, filter und reduce in Python 3 nicht mehr wollte. reduce ist diejenige Funktion, die er am wenigsten mag. Er schrieb wörtlich: „Dies ist in der Tat diejenige, die ich immer am meisten gehasst habe ...”.2 Mit reduce war er erfolgreich. reduce wurde in das Modul „functools” verbannt und gehört damit nicht mehr in den Kern der Sprache. Die Funktion reduce(func, seq) wendet die Funktion func() fortlaufend auf eine Sequenz seq an und liefert einen einzelnen Wert zurück. Die Funktion „func” ist eine Funktion, die zwei Argumente erwartet. Falls seq = [ s1, s2, s3, ... , sn ] ist, funktioniert der Aufruf reduce(func, seq) wie folgt: ■ Zuerst wird func auf die beiden ersten Argumente s1 und s2 angewendet. Das Ergebnis ersetzt die beiden Elemente s1 und s2. ■ Die Liste sieht damit wie folgt aus: [ func(s1, s2), s3, ... , sn ]. ■ Im nächsten Schritt wird func auf func(s1, s2) und s3 angewendet. ■ Die Liste sieht damit wie folgt aus: [ func(func(s1, s2),s3), ... , sn ]. ■ Dies wird solange fortgesetzt, bis nur noch ein Element übrig bleibt, d.h. man hat die Liste auf ein Element reduziert. Für den Fall n = 4 können wir die vorige Erklärung auch wie folgt illustrieren: Das folgende Beispiel zeigt die Arbeitsweise von reduce() an einem einfachen Beispiel. Um mit reduce zu arbeiten, müssen wir in Python 3 das Modul functools importieren. Das ist der wesentliche Unterschied zu früheren Python-Versionen wie zum Beispiel Python 2.7: >>> import functools >>> functools.reduce(lambda x,y: x+y, [47,11,42,13]) 113 >>> 2 „So now reduce(). This is actually the one I’ve always hated most, because, apart from a few examples involving + or *, almost every time I see a reduce() call with a non-trivial function argument, I need to grab pen and paper to diagram what’s actually being fed into that function before I understand what the reduce() is supposed to do.” 313 314 30 lambda, map, filter und reduce Im folgenden Beispiel veranschaulichen wir die Arbeitsweise von reduce an einem konkreten Beispiel: >>> reduce(lambda x,y: x+y, [47,11,42,13]) 113 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im folgenden Diagramm sind die Ergebniswerte dargestellt: 30.5 Aufgaben 1. Aufgabe: In einer Buchhandlung findet sich in einem Abrechnungsprogramm in Python eine Liste mit Unterlisten mit folgendem Aufbau: Bestellnummer Buchtitel und Autor Anzahl Einzelpreis 34587 Learning Python, Mark Lutz 4 40.95 98762 Programming Python, Mark Lutz 5 56.80 77226 Head First Python, Paul Barry 3 32.95 Schreiben Sie ein Programm unter Benutzung von lambda und map, das als Ergebnis eine Liste mit Zweier-Tupeln liefert. Jedes Tupel besteht aus der Bestellnummer und dem Produkt aus der Anzahl und dem Einzelpreis. Das Produkt soll jedoch um 10,- € erhöht werden, wenn der Bestellwert unter 100,00 € liegt. Lösung: Lösungen zu Kapitel 30 (lambda, map, filter und reduce), Seite 518 2. Aufgabe: Situation wie in voriger Aufgabe, aber jetzt sehen die Unterlisten wie folgt aus: [Bestellnummer, (Artikel-Nr, Anzahl, Einzelpreis), ... (Artikel-Nr, Anzahl, Einzelpreis) ] Schreiben Sie wieder ein Programm, das eine Liste mit 2-Tupel (Bestellnummer, Gesamtpreis) liefert. Lösung: Lösungen zu Kapitel 30 (lambda, map, filter und reduce), Seite 518 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 31 31.1 Listen-Abstraktion/ List Comprehension Die Alternative zu Lambda und Co. Im vorigen Kapitel über lambda, filter, reduce und map haben wir erfahren, dass Guido van Rossum diese Funktionen nicht mag und sie am liebsten mit der Einführung von Python 3 entfernt hätte. Stattdessen bevorzugt er die Listen-Abstraktion, die meist auch im Deutschen als „List Comprehension” bezeichnet wird. Die ListenAbstraktion wurde mit der Version 2.0 in Python eingeführt. Die Listen-Abstraktion ist eine elegante Methode, Mengen in Python zu Bild 31.1 definieren oder zu erzeugen. Die List Comprehension kommt der Durchfahrt verboten mathematischen Notation von Mengen sehr nahe. In der Mathefür Lambda und Co. matik definiert man die Quadratzahlen der natürlichen Zahlen als 2 {x |x ∈ N} oder die Menge der komplexen ganzen Zahlen {(x, y)|x ∈ Z ∧ y ∈ Z}. Die Listen-Abstraktion ist eine einfache Methode, um Listen zu erzeugen. Man kann sie als vollständigen Ersatz für den Lambda-Operator sowie die Funktionen map, filter und reduce ansehen. Sie werden verwendet, um neue Listen zu erzeugen, bei denen jedes Element durch Anwendung verschiedener Operationen aus einem Element einer anderen Liste oder eines Iterators erzeugt wird. Ebenso kann man Unterlisten erzeugen, d.h. es werden nur Elemente aus einer anderen Liste, einem Tupel oder allgemein eines sequentiellen Datentyps übernommen, die bestimmte Bedingungen erfüllen. Im Folgenden erzeugen wir die Liste der Quadratzahlen für die Zahlen von 1 bis 9 ohne Benutzung der Listen-Abstraktion: >>> >>> ... ... >>> [1, squares = [] for i in range(1, 10): squares.append(i**2) squares 4, 9, 16, 25, 36, 49, 64, 81] Viel einfacher – das heißt in nur einer Codezeile – lässt sich dies unter Benutzung der Listen-Abstraktion bewerkstelligen: squares = [i**2 for i in range(1, 10)] 316 31 Listen-Abstraktion/List Comprehension Unter Benutzung von map und lambda sieht es wie folgt aus: >>> squares = list(map(lambda x: x**2, range(1, 10))) >>> squares [1, 4, 9, 16, 25, 36, 49, 64, 81] Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. map liefert einen Iterator auf ein list-Objekt zurück, während die Listen-Abstraktion eine Liste zurückgibt. 31.2 Syntax Die Listen-Abstraktion ist von eckigen Klammern umrahmt. Nach der öffnenden Klammer steht ein Ausdruck, der von einem oder mehreren for-Ausdrücken und gegebenenfalls von einer if-Bedingung gefolgt wird. Im folgenden Beispiel erzeugen wir alle 2-Tupel mit Zahlen von 1 bis 6, deren Summe gleich 7 ist. Dies könnte beispielsweise den Ergebnissen von zwei Würfeln entsprechen: >>> [ (x,y) for x in range(1, 7) for y in range(1, 7) if x + y == 7] [(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)] Dies ist äquivalent mit dem folgenden „konventionellen” Code, der keine Listen-Abstraktion nutzt: >>> t = [] >>> for x in range(1,7): ... for y in range(1,7): ... if x + y == 7: ... t.append( (x,y) ) ... >>> t [(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)] 31.3 Weitere Beispiele Im Abschnitt über die map-Funktion hatten wir die Wandlung einer Liste mit Werten in Grad Celsius in Fahrenheit-Werte mit der map-Funktion gelöst. Mit der List Comprehension lässt sich dies sehr einfach lösen: >>> Celsius = [39.2, 36.5, 37.3, 37.8] >>> Fahrenheit = [ ((float(9)/5)*x + 32) for x in Celsius ] >>> print(Fahrenheit) [102.56, 97.7, 99.14, 100.03999999999999] 31.4 Die zugrunde liegende Idee Ein pythagoreisches Tripel oder pythagoreisches Zahlentripel besteht aus drei positiven ganzen Zahlen mit der Eigenschaft a 2 + b 2 = c 2 . Ein solches Tripel wird üblicherweise mit (a, b, c) notiert. Ein Beispiel stellen die Zahlen 3, 4 und 5 dar, also das Tripel (3, 4, 5). Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die pythagoreischen Tripel lassen sich leicht mittels List Comprehension berechnen, wie das folgende Beispiel zeigt: >>> [(x,y,z) for x in range(1,30) for y in range(x,30) for z in range( y,30) if x**2 + y**2 == z**2] [(3, 4, 5), (5, 12, 13), (6, 8, 10), (7, 24, 25), (8, 15, 17), (9, 12, 15), (10, 24, 26), (12, 16, 20), (15, 20, 25), (20, 21, 29)] Im folgenden Beispiel berechnen wir das Kreuzprodukt aus zwei Mengen: >>> colours = [ "red", "green", "blue" ] >>> things = [ "house", "tree" ] >>> coloured_things = [ (x,y) for x in colours for y in things ] >>> print(coloured_things) [('red', 'house'), ('red', 'tree'), ('green', 'house'), ('green', ' tree'), ('blue', 'house'), ('blue', 'tree')] 31.4 Die zugrunde liegende Idee Die Idee, eine Liste quasi algorithmisch zu beschreiben, ist angelehnt an die mathematische Notation von Mengen. Beispiel: Die Menge aller ganzen Zahlen, die durch 4 teilbar sind: {x|x ∈ Z ∧ x teilbar durch 4} oder ein anderes Beispiel: {x|10 <= x <= 100 ∧ x teilbar durch 4} Das letzte Beispiel lässt sich wie folgt in Python schreiben: >>> [ x for x in range(10,101) if not x % 4] [12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100] Der senkrechte Strich, also „|” in der mathematischen Notation, wird übrigens im Englischen bei der Mengenschreibweise meist als „for” gelesen, also im obigen Beispiel „All x for which 10 is . . . ” 317 318 31 Listen-Abstraktion/List Comprehension 31.5 Anspruchsvolleres Beispiel Berechnung der Primzahlen bis 100 nach dem Sieb des Eratosthenes: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> >>> >>> [2, noprimes = [j for i in range(2, 8) for j in range(i*2, 100, i)] primes = [x for x in range(2, 100) if x not in noprimes] print(primes) 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] Das vorige Beispiel wollen wir nun in einer etwas allgemeineren Form schreiben, damit es die Primzahlen für eine beliebige Zahl n berechnet: >>> >>> >>> >>> from math import sqrt n = 100 sqrt_n = int(sqrt(n)) no_primes = [j for i in range(2,sqrt_n) for j in range(i*2, n, i)] Wenn wir uns no_primes ausgeben lassen, erkennen wir, dass es bei der Konstruktion dieser Liste ein Problem gibt. Diese Liste beinhaltet Doppeleinträge, was die Berechnung der Liste der Primzahlen ineffizient werden lässt, da auch alle Doppeleinträge jeweils betrachtet werden müssen: >>> no_primes [4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 18, 27, 36, 45, 54, 63, 72, 81, 90, 99] Am besten wäre es, diese Doppeleinträge bereits gar nicht zu erzeugen. 31.6 Mengen-Abstraktion Die Mengen-Abstraktion (Set Comprehension) ist analog zur Listen-Abstraktion (List Comprehension), aber sie liefert – wie der Name andeutet – eine Menge und nicht eine Liste zurück. Zur syntaktischen Unterscheidung zur Listen-Abstraktion benutzen wir geschweifte Klammern, also „richtige Mengenklammern”. Mit der Mengen-Abstraktion sind wir nun in der Lage, das vorige Problem zu lösen, also die Nicht-Primzahl ohne Doppelte zu kreieren: 31.7 Rekursive Primzahlberechnung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> >>> >>> >>> >>> {4, from math import sqrt n = 100 sqrt_n = int(sqrt(n)) no_primes = {j for i in range(2,sqrt_n) for j in range(i*2, n, i)} no_primes 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30, 32, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45, 46, 48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 60, 62, 63, 64, 65, 66, 68, 69, 70, 72, 74, 75, 76, 77, 78, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 98, 99} >>> primes = {i for i in range(n) if i not in no_primes} >>> print(primes) {0, 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97} 31.7 Rekursive Primzahlberechnung Eine effizientere Berechnung der Primzahlen bis zu einer natürlichen Zahl n stellen wir in der folgenden rekursiven Implementierung in Python vor. Wir berücksichtigen darin, dass man sich nur die Vielfachen der Primzahlen von 1 bis zur Quadratwurzel von n anschauen muss: from math import sqrt def primes(n): if n == 0: return [] elif n == 1: return [] else: p = primes(int(sqrt(n))) no_p = {j for i in p for j in range(i*2, n+1, i)} p = {x for x in range(2, n + 1) if x not in no_p} return p for i in range(1,50): print(i, primes(i)) 31.8 Generatoren-Abstraktion Die Generatoren-Abstraktion (Generator Comprehension) wurde mit Python 2.6 eingeführt. Die Syntax ist nahezu identisch mit der Listen-Abstraktionen, außer dass sie in runde Klammern statt in eckige Klammern eingebettet sind. Erzeugt wird ein Iterator statt einer 319 320 31 Listen-Abstraktion/List Comprehension Liste. Man hat sie eingeführt, weil häufig keine Liste benötigt wird, sondern nur die einzelnen Elemente, über die iteriert wird. Im folgenden Summationsausdruck wird zunächst mittels der Listen-Abstraktion die Liste der Quadratzahlen von 1 bis 10 gebildet. Danach wird die Funktion sum darauf angewendet, um diese Quadratzahlen zu summieren: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> sum([x**2 for x in range(1,11)]) 385 Für die Funktion sum hätte aber auch ein Generator-Objekt statt einer Liste genügt. Speicherplatzschonend kann man obige Summe also auch so berechnen: >>> sum(x**2 for x in range(1,11)) 385 Sicherlich haben Sie bemerkt, dass im vorigen Ausdruck eigentlich ein Klammernpaar fehlte. Im Prinzip hätten wir den Ausdruck so schreiben müssen: >>> sum((x**2 for x in range(1,11))) 385 Bei Reduktionsfunktionen, die ein sequentielles Datenobjekt auf einen Wert reduzieren, so wie sum, min und max, brauchen wir also kein separates Klammernpaar. Sonst ist es aber notwendig: >>> (x**2 for x in range(1,11)) <generator object <genexpr> at 0x960e2ac> >>> x**2 for x in range(1,11) File "<stdin>", line 1 x**2 for x in range(1,11) ^ SyntaxError: invalid syntax 31.9 Aufgaben 1. Aufgabe: Mittels einer Listen-Abstraktion soll die Menge aller Paare von natürlichen Zahlen kleiner als ein gegebenes n gebildet werden, sodass die erste der Zahlen ohne Rest durch die zweite teilbar ist und die beiden Zahlen nicht identisch sind. Lösung: Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension), Seite 519 31.9 Aufgaben 2. Aufgabe: Wie groß ist die Wahrscheinlichkeit, dass beim Wurf zweier Würfel die Summe der Augenzahlen 7 entspricht? Anmerkung: Wir erwarten natürlich, dass Sie die Aufgabe unter Benutzung von ListenAbstraktionen lösen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Lösung: Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension), Seite 519 3. Aufgabe: Schreiben Sie eine Funktion, die obige Wahrscheinlichkeit für eine beliebige Summe als Argument berechnet. Lösung: Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension), Seite 519 321 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 32 32.1 Generatoren und Iteratoren Einführung In der Informatik versteht man unter einem Generator Programmcode – meist eine Routine oder Funktion –, der dazu benutzt wird, das Iterationsverhalten einer Schleife zu kontrollieren. Man kann sich das wie eine Funktion mit Parametern vorstellen, die bei einem Aufruf eine Folge von Werten zurückliefert. Allerdings mit dem Unterschied, dass diese Folge von Werten nicht, wie zum Beispiel bei einer Liste, auf einmal zurückgeliefert wird, sondern ein Wert nach dem anderen. Dies hat zwei Vorteile: Zum einen Bild 32.1 Generatoren spart man Speicherplatz, weil eine solche Liste nie mit all ihren Werten erzeugt werden muss, und zum anderen, weil die Schleife schneller ihre ersten Werte erhält, denn man muss nicht warten, bis die gesamte Folge von Werten berechnet worden ist (falls Letzteres überhaupt möglich ist). Generatoren können auch potenziell unendliche Folgen von Werten liefern. Ein Generator ist also im Prinzip eine Funktion, die sich wie ein Iterator verhält. Deshalb werden Generatoren, wie wir schon eingangs erwähnt haben, meistens in Schleifen verwendet. Auch wenn man denken könnte, dass es sich bei Generatoren um neue und moderne Software-Technik handelt, wurden sie bereits 1975 in CLU und 1977 in Icon verwendet. 32.2 Iteration in for-Schleifen Iteratoren haben wir bereits in for-Schleifen kennengelernt und häufig benutzt, ohne im Besonderen darauf einzugehen. Im folgenden Beispiel durchlaufen wir die Elemente der Liste „cities”. >>> cities = ["Hamburg", "Berlin", "München", >>> for city in cities: ... print("Stadt: " + city) ... "Zürich"] 324 32 Generatoren und Iteratoren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Stadt: Stadt: Stadt: Stadt: Hamburg Berlin München Zürich Schauen wir uns einmal an, was Python genau macht, um die for-Schleife auszuführen: Bevor die Schleife gestartet wird, ruft Python die eingebaute1 Funktion iter mit der Liste „cities” als Argument auf. Die Funktion iter liefert nun ein Objekt zurück, mit dem es möglich ist, die Elemente der Liste zu durchlaufen. Der Rückgabewert von iter() ist ein Element der Klasse „list_iterator”, also ein Iterator. Die eigentliche „Arbeit” erledigt die Methode __iter__ der list-Klasse, die von der Funktion iter() aufgerufen wird. Um das weitere Vorgehen bei der for-Schleife zu verstehen, nehmen wir an, dass das Resultat von iter() an eine Variable iter_i gebunden wird. Also in unserem Fall iter_i = iter(cities). Nach jedem Schleifendurchlauf wird die next-Funktion mit iter_x als Argument aufgerufen und das Ergebnis der Variablen iter_x zugewiesen. Die Schleife wird solange wiederholt, bis next(iter_x) die Ausnahme StopIteration erzeugt. Man kann diese Iteration auch „manuell” nachvollziehen: >>> l = [23, 34, 56, 78] >>> iter_x = iter(l) >>> next(iter_x) 23 >>> next(iter_x) 34 >>> next(iter_x) 56 >>> next(iter_x) 78 >>> next(iter_x) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration Man kann die Iteration der for-Schleife durch folgenden Code simulieren: cities = ["Hamburg", "Berlin", "München", "Stuttgart", "Zürich"] iterator = iter(cities) while True: try: city = next(iterator) except StopIteration: break print("Stadt: " + city) 1 Häufig wird hierfür der englische Begriff „built-in” benutzt! 32.3 Generatoren Die sequentiellen Basistypen sowie ein Großteil der Klassen der Standardbibliothek von Python unterstützen Iterationen. Auch der Datentyp Dictionary (dict) unterstützt die Iteration. In diesem Fall läuft die Iteration über die Schlüssel des Dictionarys:2 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. hauptstaedte = { "Österreich":"Wien", "Deutschland":"Berlin", "Schweiz":"Bern" } for land in hauptstaedte: print(land + ": " + hauptstaedte[land]) Ausgegeben werden nur die Schlüsselwerte des Dictionarys: Schweiz: Bern Österreich: Wien Deutschland: Berlin 32.3 Generatoren Doch kommen wir zurück zu unserem ersten Beispiel über Städtenamen. Wir können daraus ein erstes Beispiel für einen Generator bilden: def city_generator(): cities = ["Paris", "Berlin", "London", "Wien"] for city in cities: yield city for city in city_generator(): print(city) Trifft der Programmfluss auf eine yield-Anweisung, wird der Generator wie bei einer return-Anweisung verlassen, aber Python „merkt” sich, wo der Generator verlassen wird, und gleichzeitig werden auch alle Zustände, also die lokalen Variablen, für einen weiteren Aufruf erhalten. Zur Erinnerung: Bei Funktionen werden alle lokalen Variablen beim Verlassen gelöscht und bei einem neuen Aufruf wieder neu angelegt. Ruft man dieses Programm auf, werden die Städtenamen ausgegeben: $ python3 city_generator.py Paris Berlin London Wien 2 Bern ist der Bundessitz der Schweiz, aber wird nicht Haupt-, sondern Bundesstadt genannt. Bern ist nur de facto, aber nicht de jure Hauptstadt der Schweiz. 325 326 32 Generatoren und Iteratoren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die for-Schleife endet also, sobald alle Städte im Generator durchlaufen sind. Wir können im Generator allerdings auch eine Endlosschleife verwenden. def city_generator(): cities = ["Hamburg", "Berlin", "München", while True: city = cities.pop(0) yield city cities.append(city) "Zürich"] Speichern wir diese Funktionsdefinition in einer Datei cities_forever.py, können wir sie interaktiv in der Python-Shell testen: >>> from cities_forever import city_generator >>> x = city_generator() >>> next(x) 'Hamburg' >>> next(x) 'Berlin' >>> next(x) 'München' >>> next(x) 'Zürich' >>> next(x) 'Hamburg' >>> next(x) 'Berlin' Wir sehen also, dass wir einen Generator, der eine Endlosschleife beinhaltet, durchaus sinnvoll benutzen können. Wir dürfen ihn lediglich nicht in einer Endlosschleife aufrufen, wie es im folgenden interaktiven Skript geschieht. Wir müssen dann die Schleife mit „Ctrl-C”’ abbrechen: >>> from cities_forever import city_generator >>> for city in city_generator(): ... print(city, end=", ") ... Hamburg, Berlin, München, Zürich, Hamburg, Berlin, Zürich, Hamburg, Berlin, München, Zürich, Hamburg, München, Zürich, Hamburg, Berlin, München, Zürich, Berlin, München, Zürich, Hamburg, Berlin, München, Hamburg, Berlin, ... München, Berlin, Hamburg, Zürich, Um eine solche Endlosschleife zu vermeiden, können wir unsere for-Schleife mit einem Zähler und einem break versehen: def city_generator(): cities = ["Paris","Berlin","London","Wien"] while True: city = cities.pop(0) 32.4 Generatoren zähmen mit firstn und islice Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. yield city cities.append(city) if __name__ == "__main__": count = 0 for city in city_generator(): if count == 8: print() break print(city, end=", ") count += 1 Ein Aufruf liefert dann, wie erwartet, folgende Ausgabe: Paris, Berlin, London, Wien, Paris, Berlin, London, Wien, 32.4 Generatoren zähmen Obiges Abbruchproblem können wir auch eleganter lösen, indem wir einen Generator firstn(g, n) schreiben, der die ersten n Elemente eines Generators g ausgibt: def firstn(generator, n): iterator = generator() for i in range(n): yield next(iterator) def city_generator(): cities = ["Paris", "Berlin", "London", "Wien"] while True: city = cities.pop(0) yield city cities.append(city) if __name__ == "__main__": for city in firstn(city_generator, 9): print(city, end=", ") print() Wir erhalten hier folgendes Ergebnis: Paris, Berlin, London, Wien, Paris, Berlin, London, Wien, Paris, Aber Python wäre nicht Python, wenn es nicht eine noch elegantere Methode gäbe. Man findet sie im Modul itertools. Mit der Methode islice kann man sich die Elemente von einer Start- bis zu einer Endposition ausgeben lassen. Im nächsten Beispiel sind dies die ersten fünf Elemente, die von city_generator generiert werden: 327 328 32 Generatoren und Iteratoren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> import itertools >>> from cities_forever import city_generator >>> itertools.islice(city_generator(),0,5) <itertools.islice object at 0xb713f4b4> >>> cities = itertools.islice(city_generator(),0,5) >>> list(cities) ['Paris', 'Berlin', 'London', 'Wien', 'Paris'] 32.5 32.5.1 Beispiele Permutationen Unter einer Permutation (von lateinisch permutare, vertauschen) versteht man in der Kombinatorik eine Anordnung von Objekten in einer bestimmten Reihenfolge. Wenn Objekte mehrfach auftreten dürfen, spricht man von einer Permutation mit Wiederholung, ansonsten spricht man von einer Permutation ohne Wiederholung. Im Folgenden wollen wir uns nur mit Permutationen ohne Wiederholung beschäftigen und einen Generator schreiben, der alle Permutationen eines sequentiellen Datenobjekts erzeugt, also z.B. von einem String, einer Liste oder einem Tupel. Bild 32.2 Permutationen von vier Farben auf vier Plätzen Die Anzahl der Permutationen von n Objekten berechnet sich durch die Fakultät: n! = n · (n − 1) . . . 2 · 1 Auch wenn das hier sehr abstrakt klingt, kennt man Permutationen aus dem täglichen Leben: ■ ■ Das Mischen der Karten eines Kartenspiels: Der Kartenstapel entspricht einer Permutation aller Karten. Ein Anagramm ist eine Permutation der Buchstaben eines Wortes: Lager, Regal, erlag def permutations(items): n = len(items) if n==0: yield [] else: for i, item in enumerate(items): for cc in permutations(items[:i] + items[i+1:]): yield [item] + cc 32.5 Beispiele print("""permutations for (['r','e','d']""") for p in permutations(['r','e','d']): print(''.join(p)) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. print("""permutations of the letters of the string "bin" """) for p in permutations(list("bin")): print(''.join(p)) Obiges Programm liefert folgende Ausgabe: permutations for (['r','e','d'] red rde erd edr dre der permutations of the letters of the string "bin" bin bni ibn inb nbi nib 32.5.2 Variationen und Kombinationen Wählt man aus einer n-elementigen Menge von Objekten k Objekte unter Berücksichtigung der Reihenfolge ohne Zurücklegen aus, so bezeichnet man dies als eine Variation ohne Wiederholung. Die Permutationen ergeben sich als ein Sonderfall für n = k. Die Anzahl der verschiedenen k-Variationen von n Elementen lässt sich wie folgt berechnen: n! = n · (n − 1) · (n − 2)...(n − k + 1) (n − k)! Der folgende Generator „variations” liefert alle k-Variationen ohne Wiederholungen: def variations(items, k): if k==0: yield [] else: for item in items: for v in variations(items, k-1): if item not in v: yield [item] + v 329 330 32 Generatoren und Iteratoren for variation in variations(["a", "b", "c"], 2): print(variation) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Das Ergebnis lautet: ['a', ['a', ['b', ['b', ['c', ['c', 'b'] 'c'] 'a'] 'c'] 'a'] 'b'] Spielt die Reihenfolge bei der Auswahl ohne Zurücklegen keine Rolle, so bezeichnet man dies als eine Kombination ohne Wiederholung. Die k-Kombinationen von n Elementen kann man auch als die k-elementigen Teilmengen der n Elemente ansehen. Bei der Lotterie 6 aus 49 handelt es sich beispielsweise um eine solche Kombination. Die Anzahl der verschiedenen k-Kombinationen von n Elementen berechnet sich wie folgt: Ã ! Ã ! n · (n − 1) · (n − 2)...(n − k + 1) n n n! = = = k! · (n − k)! k! k n −k Der folgende Generator „combinations” liefert alle k Kombinationen aus „elements” ohne Zurücklegen: def combinations(elements, k): s = set(elements) while s: el = s.pop() if k==1: yield {el} for combination in combinations(s, k-1): combination.add(el) yield combination for c in combinations("abcd", 3): print(c) Das Ergebnis lautet: {'c', {'c', {'c', {'b', 'b', 'b', 'a', 'a', 'a'} 'd'} 'd'} 'd'} Falls Sie allerdings nur an einem Lottogewinn interessiert sind, können Sie auch die Funktion sample aus dem Modul random benützen. random.sample(population, k) 32.6 Generator-Ausdrücke population ist eine Sequenz (z.B. Liste), aus der k Elemente ausgewählt und in einer neuen Liste zurückgegeben werden. Dabei wird „population” aber nicht verändert. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> import random >>> print(random.sample(range(1,50),6)) [19, 10, 30, 2, 14, 41] Obige Zahlen können Sie gerne als Tipp für Ihren Lottoschein verwenden. Vergessen Sie bitte nicht den Autor dieses Buches, sollten Sie einen Sechser haben ,. 32.6 Generator-Ausdrücke Generator-Ausdrücke sind Listen-Abstraktionen (List Comprehensions) sehr ähnlich. Statt eckiger Klammern werden runde Klammern verwendet. Anders als Listen-Abstraktionen werden keine vollständigen Listen erzeugt, sondern Generatoren, die ihre Elemente wie Generatoren eines nach dem anderen zurückliefern. Den Zusammenhang zwischen Listen-Abstraktion und Generator-Ausdrücken zeigt das nächste Beispiel: >>> squares1 = [i**2 for i in >>> squares1 [1, 4, 9, 16, 25, 36, 49, 64, >>> squares2 = (i**2 for i in >>> type(squares2) <class 'generator'> >>> list(squares2) [1, 4, 9, 16, 25, 36, 49, 64, range(1, 10)] 81] range(1, 10)) 81] Man sieht, dass list(squares2) und squares1 identisch sind: >>> squares = (i**2 for i in range(1, 4)) >>> next(squares) 1 >>> next(squares) 4 >>> next(squares) 9 >>> next(squares) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration Der obigen Listenausdruck ist identisch mit dem folgenden Generator: def squares(): for i in range(1, 4): yield i ** 2 331 332 32 Generatoren und Iteratoren 32.7 return-Anweisungen in Generatoren Seit Python 3.3 kann man innerhalb von Generatoren auch return-Anweisungen benutzen. Allerdings muss ein Generator dennoch mindestens eine yield-Anweisung haben, um ein Generator zu sein. Eine return-Anweisung innerhalb eines Generators entspricht einer raise StopIteration(). Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Schauen wir uns im Folgenden einen Generator an, in dem wir eine StopIteration erheben: >>> def gen(): ... yield 1 ... raise StopIteration(42) ... >>> >>> g = gen() >>> next(g) 1 >>> next(g) Traceback (most recent call last): File "", line 1, in File "", line 3, in gen StopIteration: 42 >>> Wenn wir raise StopIteration(42) durch return 42 austauschen, sehen wir, dass wir bis auf eine Zeile Unterschied im Traceback das gleiche Ergebnis wie vorher erhalten: >>> def gen(): ... yield 1 ... return 42 ... >>> >>> g = gen() >>> next(g) 1 >>> next(g) Traceback (most recent call last): File "", line 1, in StopIteration: 42 >>> 32.8 send-Methode / Koroutinen Generatoren können nicht nur mittels yield Objekte liefern, sondern man kann auch „Nachrichten” (Objekte) an Generatoren senden. Dies erreicht man dadurch, dass man statt next die send-Methode benutzt. Die send-Methode verhält sich im Prinzip wie die 32.8 send-Methode / Koroutinen next-Methode, aber sendet gleichzeitig noch einen Wert an den Generator. Im Prinzip sendet auch next einen Wert, aber dieser ist immer None. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir demonstrieren dieses Verhalten in folgendem Beispiel: >>> def simple_coroutine(): ... print("Koroutine wurde gestartet!") ... x = yield ... print("Koroutine empfing: ", x) ... >>> cr = simple_coroutine() >>> cr <generator object simple_coroutine at 0x7fc08820d990> >>> next(cr) Koroutine wurde gestartet! >>> cr.send("hi") Koroutine empfing: hi Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration Wir mussten den Generator zuerst mit next aufrufen, weil der Generator zuerst gestartet werden musste. Wendet man send auf einen Klassifikator an, der noch nicht gestartet worden ist, so führt dies zu einer Ausnahme: >>> cr = simple_coroutine() >>> cr.send("Hi") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't send non-None value to a just-started generator Damit wir die send-Methode benutzen können, muss der Generator in einer yieldAnweisung positioniert sein, d.h. er muss bereits vorher einen Wert mittels yield geliefert haben und wartet auf einen Aufruf von next oder send. Steht yield auf der rechten Seite einer Zuweisung, so wird der Wert des Arguments von send an die Variable auf der linken Seite übergeben. Wird der Generator mit next aufgerufen, so wird None übergeben. Den nachfolgenden Generator haben wir infinite_looper genannt, weil wir ihn mit einem sequentiellen Datentyp aufrufen können und der Generator dann in der Lage ist, „ewig” über dieses Objekt zu iterieren, d.h. er beginnt automatisch wieder mit dem ersten Element, wenn das letzte abgearbeitet worden ist. Mittels der send-Methode können wir diesen Generator auf eine beliebige Position des sequentiellen Datentyps umsetzen. def infinite_looper(objects): count = 0 while True: if count >= len(objects): count = 0 message = yield objects[count] 333 334 32 Generatoren und Iteratoren if message != None: count = 0 if message < 0 else message else: count += 1 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir zeigen in der folgenden interaktiven Sitzung, wie man diesen Generator nutzen kann. >>> >>> 'E' >>> 'r' >>> ' ' >>> 'S' x = infinite_looper("Ein kleiner String") next(x) x.send(10) next(x) next(x) 32.9 Die throw-Methode Mit Python 2.5 wurde die throw-Methode für Generator-Objekte hinzugefügt. Dadurch ist es möglich, innerhalb des Generator-Objekts eine Ausnahme zu erheben, so als wäre sie von der yield-Anweisung erhoben worden. Der Generator muss diese Ausnahme auffangen; ansonsten wird die Ausnahme wieder an den Aufrufer propagiert. Der infinite_looper-Generator aus unserem vorigen Beispiel liefert die Elemente des sequentiellen Datentyps zurück, aber wir erhalten keinerlei Information über die Variable count. Wir können die throw-Methode nutzen, um an diese Information zu gelangen. Wir fangen die exception auf und geben den Wert von count aus, wenn die throw-Methode aufgerufen wird: def infinite_looper(objects): count = 0 while True: if count >= len(objects): count = 0 try: message = yield objects[count] except Exception: print("index: " + str(count)) message = None if message != None: count = 0 if message < 0 else message else: count += 1 Dieser Generator lässt sich wie folgt benutzen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 32.9 Die throw-Methode >>> from generator_throw import infinite_looper >>> looper = infinite_looper("Python") >>> next(looper) 'P' >>> looper.throw(Exception) index: 0 'y' >>> next(looper) 't' >>> looper.throw(Exception) index: 2 'h' Das vorige Beispiel können wir noch verbessern, indem wir unsere eigene AusnahmeKlasse StateOfGenerator definieren: class StateOfGenerator(Exception): def __init__(self, message=None): self.message = message def infinite_looper(objects): count = 0 while True: if count >= len(objects): count = 0 try: message = yield objects[count] except StateOfGenerator: print("index: " + str(count)) if message != None: count = 0 if message < 0 else message else: count += 1 Beispiel für die Benutzung des gerade definierten Generators: >>> from generator_throw import infinite_looper, StateOfGenerator >>> looper = infinite_looper("Python") >>> next(looper) 'P' >>> next(looper) 'y' >>> >>> looper.throw(StateOfGenerator) index: 1 't' >>> next(looper) 'h' >>> 335 336 32 Generatoren und Iteratoren 32.10 Dekoration von Generatoren Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Unser Ansatz hat aber einen Schönheitsfehler: Wir können ihn nicht direkt starten und gleichzeitig einen Index übergeben, d.h. wir können nicht sofort ein send benutzen. Wir müssen zuerst die next-Methode aufrufen, um damit den Generator zu starten und ihn dadurch zur yield-Anweisung vorwärts zu bewegen. Wir werden nun einen Dekorateur schreiben, der in der Lage ist, einen Generator bereit zu machen, indem es diesen automatisch bei der Erzeugung zur yield-Anweisung fortbewegt. Dadurch wird es möglich, die send-Methode unmittelbar nach der Initialisierung zu benutzen. from functools import wraps def get_ready(gen): """ gets a generator gen ready by advancing it to first first yield statement """ @wraps(gen) def generator(*args,**kwargs): g = gen(*args,**kwargs) next(g) return g return generator @get_ready def infinite_looper(objects): count = -1 message = yield None while True: count += 1 if message != None: count = 0 if message < 0 else message if count >= len(objects): count = 0 message = yield objects[count] x = infinite_looper("abcdef") print(next(x)) print(x.send(4)) print(next(x)) print(next(x)) print(x.send(5)) print(next(x)) Wir erhalten die folgenden Ergebnisse: 32.11 yield from Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. a e f a f a 32.11 yield from Dieses Sprachkonstrukt ist seit 3.3 verfügbar! yield from <expr> kann im Body eines Generators benutzt werden. Der Ausdruck <expr> wird in einen Iterator evaluiert, aus dem ein Iterator extrahiert wird. Dieser Iterator läuft bis StopIteration, und er liefert und empfängt Werte vom aufrufenden Generator, also demjenigen, der die yield from-Anweisung enthält. Aus dem folgenden Beispiel können wir ersehen, dass der Generator gen2 das Gleiche macht wie gen1 . Durch die Verwendung von yield from benötigen wir jedoch keine forSchleife mehr: def gen1(): for char in "Python": yield char for i in range(5): yield i def gen2(): yield from "Python" yield from range(5) g1 = gen1() g2 = gen2() print("g1: ", end=", ") for x in g1: print(x, end=", ") print("\ng2: ", end=", ") for x in g2: print(x, end=", ") print() Die Ausgabe zeigt, dass gen1 und gen2 die gleiche Ausgabe liefern: g1: , P, y, t, h, o, n, 0, 1, 2, 3, 4, g2: , P, y, t, h, o, n, 0, 1, 2, 3, 4, Der Vorteil eines yield from-Ausdrucks besteht darin, dass man einen Generator in mehrere Generatoren aufsplitten kann. Im Prinzip haben wir das bereits im vorigen Beispiel gemacht, wollen es aber nochmals direkter im folgenden Code demonstrieren: 337 338 32 Generatoren und Iteratoren def cities(): for city in ["Berlin", "Hamburg", "Munich", "Freiburg"]: yield city Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def squares(): for number in range(10): yield number ** 2 def generator_all_in_one(): for city in cities(): yield city for number in squares(): yield number def generator_splitted(): yield from cities() yield from squares() lst1 = [el for el in generator_all_in_one()] lst2 = [el for el in cities()] + [el for el in squares()] print(lst1 == lst2) Der vorherige Code liefert True zurück, weil die Generatoren generator_all_in_one und generator_splitted die gleichen Elemente zurückliefern. Dies bedeutet, dass, falls <expr> von der yield from-Anweisung ein anderer Generator ist, der Effekt der gleiche ist, als wenn der Body des Subgenerators anstelle der yield from hereinkopiert würde. Einem solchen Subgenerator ist es erlaubt, eine return-Anweisung zu haben. Der Wert der return-Anweisung wird zum Rückgabewert der yield from-Anweisung. Wir veranschaulichen dies mit folgendem Programmcode: def subgenerator(): yield 1 yield 2 return 42 def delegating_generator(): x = yield from subgenerator() print(x) for x in delegating_generator(): print(x) Wir erhalten das folgende Ergebnis: 1 2 42 32.12 Aufgaben 32.12 Aufgaben 1. Aufgabe: Schreiben Sie einen Generator, der die Fibonacci-Zahlen generiert. Zur Erinnerung: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die n -te Fibonacci-Zahl errechnet sich F (n) = F (n − 1) + F (n − 2), mit F (0) = 0 und F (1) = 1 2. Aufgabe: Schreiben Sie einen Generator „round_robin” mit einem Generator g als Argument, der zunächst die Elemente von g auch generiert. Falls g endlich ist, beginnt „round_robin” wieder von vorne mit g, nachdem das letzte Element von g produziert worden ist. Beispiel: Falls g die Elemente 1, 2 und 3 generiert, dann generiert round_robin die Elemente 1,2,3,1,2,3,1,2,3 . . . 3. Aufgabe: Schreiben Sie einen Generator „pendulum” mit einem Generator g als Argument, der als Erstes die Elemente von g generiert. Falls g endlich ist, gibt „pendulum” die Elemente von g in umgekehrter Reihenfolge aus, nachdem das letzte Element von g produziert worden ist. Wird dann wieder das erste Element erreicht, beginnt „pendulum” wieder von vorne. Beispiel: Falls g die Elemente 1, 2 und 3 generiert, dann generiert „pendulum” die Folge 1,2,3,3,2,1,1,2,3,3,2,1 . . . 4. Aufgabe: Schreiben Sie einen Generator „pair_sum” mit einem Generator g als Argument. Nehmen wir an, dass g die Werte g 1 , g 2 , . . . g n generiert. „pair_sum” generiert dann die Werte g 1 + g 2 , g 2 + g 3 , . . . g n−1 + g n , g n + g 1 , g 1 + g 2 , . . . 5. Aufgabe: Schreiben Sie einen Generator mit Namen random_ones_and_zeroes, der in jeder Iteration eine 1 oder eine Null zurückliefert. Die Wahrscheinlichkeit p, mit der eine 1 zurück geliefert wird, wird durch die Variable p gesteuert. Beim Start des Generators wird der Wert von p auf 0.5 gesetzt. Das bedeutet, dass Nullen und Einsen mit gleicher Wahrscheinlichkeit erzeugt werden. 339 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 33 33.1 Dekorateure Einführung Dekorateure Dekorateure gehören vermutlich zu den leistungsstärksten Designmöglichkeiten von Python. Gleichzeitig wird es von vielen als schwierig betrachtet, einen Einstieg in die Thematik zu finden. Um genau zu sein: Die Nutzung von Dekorateuren ist sehr einfach, aber das Schreiben eines Dekorateurs kann sich vor allem für Ungeübte als kompliziert erweisen. In Python gibt es zwei verschiedene Arten von Dekorateuren: ■ Funktions-Dekorateure ■ Klassen-Dekorateure Ein Dekorateur in Python ist ein beliebiges aufrufbares Python-Objekt, welches zur Modifikation einer Funktion oder einer Klasse genutzt wird. Eine Referenz zu einer Funktion „func” oder einer Klasse „C” wird an den Bild 33.1 Dekorateure mit Klammeraffen Dekorateur übergeben, und der Dekorateur liefert eine modifizierte Funktion oder Klasse zurück. Die modifizierten Funktionen oder Klassen rufen üblicherweise intern die Originalfunktion „func” oder -Klasse „C” auf. Aus Erfahrung können wir sagen, dass es für Anfänger beim Thema Dekorateure einige Schwierigkeiten bei der Benutzung von Funktionen mit funktionalen Argumenten und Rückgabewerten gibt. Deshalb möchten wir nochmals auf die prinzipiellen Möglichkeiten von Funktionen bei Parameterübergabe und Rückgabe sowie bei lokalen Funktionsdefinitionen eingehen. Es ist wichtig zu verstehen, dass Funktionsnamen Referenzen auf Funktionen darstellen, oder genauer: Referenzen auf Funktionsobjekte. Wie bei allen Objekten ist es also möglich, verschiedene Namen für das gleiche Objekt zu vergeben: 342 33 Dekorateure Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> ... ... >>> >>> 42 >>> 42 def nachfolger(n): return n + 1 successor = nachfolger successor(41) nachfolger(41) Wir haben jetzt also zwei Namen, d.h. nachfolger und successor für dasselbe Funktionsobjekt. Der nächste wichtige Punkt ist, dass wir einen der beiden Namen, also nachfolger oder successor, löschen können. Dadurch wird das Funktionsobjekt nicht entfernt, da weiterhin noch eine Referenz auf das Objekt existiert. >>> del nachfolger >>> successor(41) 42 33.1.1 Verschachtelte Funktionen Das Konzept von verschachtelten Funktionen, also Funktionsdefinitionen innerhalb einer Funktion, ist in vielen anderen Programmiersprachen wie beispielsweise C, C++ und Java nicht bekannt: def f(): def g(): print("Hallo, ich bin es, 'g'") print("Dies ist die Funktion 'f'") print("'f' ruft nun 'g' auf!") g() f() Wenn wir den obigen Code ausführen, erhalten wir die folgende Ausgabe: Dies ist die Funktion 'f' 'f' ruft nun 'g' auf! Hallo, ich bin es, 'g' Noch ein Beispiel mit Funktionen, die return-Anweisungen verwenden: def temperatur(t): def celsius2fahrenheit(x): return 9 * x / 5 + 32 33.1 Einführung Dekorateure result = str(celsius2fahrenheit(t)) + " Grad Fahrenheit!" return result print(temperatur(20)) Wir erhalten 68.0 Grad Fahrenheit! als Ergebnis. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. celsius2fahrenheit ist nur innerhalb von temperatur bekannt, d.h. sie kann nur innerhalb des Funktionskörpers von temperatur aufgerufen werden. 33.1.2 Funktionen als Parameter Jeder Parameter einer Funktion ist eine Referenz auf ein Objekt. Funktionen sind ebenfalls Objekte. Somit können wir Funktionen, oder besser „Referenzen auf Funktionen”, ebenfalls als Argumente an Funktionen übergeben. Wir demonstrieren dies am folgenden Beispiel: def g(): print("Hallo, ich bin es, 'g'") def f(func): print("Dies ist die Funktion 'f'") print("'f' ruft nun 'func' auf!") func() f(g) Die Ausgabe sieht wie folgt aus: Dies ist die Funktion 'f' 'f' ruft nun 'func' auf! Hallo, ich bin es, 'g' Möglicherweise sind Sie mit der Ausgabe nicht ganz zufrieden. f sollte ausgeben, dass g aufgerufen wird, und nicht func. Dazu müssen wir den „wirklichen” Namen von func wissen. In diesem Fall können wir das Attribut __name__ des Funktionsobjekts benutzen, welches den Namen der Funktion beinhaltet: def g(): print("Hallo, ich bin es, 'g'") def f(func): print("Dies ist die Funktion 'f'") print("'f' ruft nun " + func.__name__ + " auf!") func() f(g) 343 344 33 Dekorateure Einmal mehr zeigt die Ausgabe, was hier passiert: Dies ist die Funktion 'f' 'f' ruft nun g auf! Hallo, ich bin es, 'g' Noch ein Beispiel: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. import math def foo(func): print(func.__name__ + " wurde an foo übergeben") res = 0 for x in [1, 2, 2.5]: res += func(x) return res print(foo(math.sin)) print(foo(math.cos)) Wir erhalten folgende Ausgabe: sin wurde an foo übergeben 2.3492405557375347 cos wurde an foo übergeben -0.6769881462259364 33.1.3 Funktionen als Rückgabewert Die Rückgabe einer Funktion ist eine Referenz auf ein Objekt. Somit können auch Referenzen auf Funktionen zurückgegeben werden. def f(x): def g(y): return y + x + 3 return g nf1 = f(1) nf2 = f(3) print(nf1(1)) print(nf2(1)) Das vorige Beispiel liefert folgende Ausgabe: 5 7 33.1 Einführung Dekorateure 33.1.4 Fabrikfunktionen Unter einer Fabrikfunktion versteht man eine Funktion, die bei einem Aufruf eine neue Funktion produziert oder generiert. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Das folgende Beispiel einer Fabrikfunktion demonstriert, wie wir maßgeschneidert eine Begrüßungsfunktion in Abhängigkeit der gewünschten Sprache mit der Fabrikfunktion create_greeting_function erzeugen können: def create_greeting_function(lang): def greeting(name): if lang == "de": greeting = "Guten Tag " elif lang == "fr": greeting = "Bonjour " elif lang == "it": greeting = "Buon Giorno " elif lang == "es": greeting = "Buenos dias " else: greeting = "Hi " return greeting + name return greeting say_hi = create_greeting_function("fr") print(say_hi("Pierre")) Mit dem Aufruf von create_greeting_function("fr") haben wir eine Funktion erzeugt, die auf Französisch grüßt, wie wir an der Ausgabe Bonjour Pierre erkennen können. Das folgende Beispiel demonstriert die Arbeitsweise von Fabrikfunktionen in einem mathematischen Umfeld, indem wir eine Fabrikfunktion für e definieren. Wir starten mit einer Version für Polynome zweiten Grades. p(x) = a · x 2 + b · x + c Die Python-Implementierung als eine Polynom-Fabrikfunktion sieht wie folgt aus: def polynomial_creator(a, b, c): def polynomial(x): return a * x**2 + b * x + c return polynomial p1 = polynomial_creator(2, 3, -1) p2 = polynomial_creator(-1, 2, 1) for x in range(-2, 2, 1): print(x, p1(x), p2(x)) 345 346 33 Dekorateure Wir können dieses Prinzip auf auf Polynome beliebigen Grades ausdehnen: n X a k · x k = a n · x n + a n−1 · x n−1 + . . . + a 2 · x 2 + a 1 · x + a 0 k=0 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Python-Implementierung sieht wie folgt aus: def polynomial_creator(*coefficients): """ coefficients are in the form a_0, a_1, ... a_n """ def polynomial(x): res = 0 for index, coeff in enumerate(coefficients): res += coeff * x** index return res return polynomial p1 = polynomial_creator(4) p2 = polynomial_creator(2, 4) p3 = polynomial_creator(2, 3, -1, 8, 1) p4 = polynomial_creator(-1, 2, 1) for x in range(-2, 2, 1): print(x, p1(x), p2(x), p3(x), p4(x)) p3 implementiert zum Beispiel das folgende Polynom: p 3 (x) = x 4 + 8 · x 3 − x 2 + 3 · x + 2 Starten wir obiges Programm, erhalten wir folgende Ausgabe: (-2, 4, -6, -56, -1) (-1, 4, -2, -9, -2) (0, 4, 2, 2, -1) (1, 4, 6, 13, 2) 33.2 Ein einfacher Dekorateur Jetzt haben wir alle zum Bau unseres ersten einfachen Dekorateurs wichtigen Funktionseigenschaften zusammen: def our_decorator(func): def function_wrapper(x): print("Vor dem Aufruf von " + func.__name__) func(x) 33.3 @-Syntax für Dekorateure print("Nach dem Aufruf von " + func.__name__) return function_wrapper def foo(x): print("foo wurde mit " + str(x) + " aufgerufen!") Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. foo("first") foo = our_decorator(foo) foo("second") Wenn wir uns die folgende Ausgabe anschauen, dann sehen wir, was passiert. Nach der Dekoration foo = our_decorator(foo) ist foo eine Referenz auf die aus function_wrapper generierte Funktion. foo wird innerhalb von dieser durch function_wrapper generierten Funktion aufgerufen, aber vorher und nachher wird noch etwas Code ausgeführt – in diesem Beispiel zwei Print-Funktionen. foo wurde mit first aufgerufen! Vor dem Aufruf von foo foo wurde mit second aufgerufen! Nach dem Aufruf von foo 33.3 @-Syntax für Dekorateure Die Dekoration in Python wird in der Regel nicht so gemacht, wie wir es im vorigen Beispiel gezeigt haben. Wir haben mit der Schreibweise foo = our_decorator(foo) begonnen, weil sie einfach zu verstehen und einprägsam ist. Allerdings gibt es bei diesem Ansatz ein Designproblem. „foo” gibt es in dem Programm in zwei Versionen, und zwar sowohl vor der Dekoration als auch nach der Dekoration. Um dies zu vermeiden, gibt es eine alternative Definitionsweise für Dekorateure. Die Dekoration passiert in der Zeile über dem Funktionskopf: das Zeichen @, gefolgt von dem Funktionsnamen des Dekorateurs. Wir ersetzen diese Anweisung foo = our_decorator(foo) durch @our_decorator Diese Zeile muss unmittelbar über der zu dekorierenden Funktion platziert werden. Das komplette Beispiel sieht nun folgendermaßen aus: def our_decorator(func): def function_wrapper(x): print("Vor dem Aufruf von " + func.__name__) 347 348 33 Dekorateure func(x) print("Nach dem Aufruf von " + func.__name__) return function_wrapper Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. @our_decorator def foo(x): print("foo wurde mit " + str(x) + " aufgerufen!") foo("first") foo("second") Alle Funktionen mit einem Parameter lassen sich mit dem Dekorateur our_decorator dekorieren. Im Folgenden werden wir dies demonstrieren. Wir haben den Wrapper etwas verändert, sodass wir die Ergebnisse der Funktionsaufrufe sehen können: def our_decorator(func): def function_wrapper(x): print("Vor dem Aufruf von " + func.__name__) result = func(x) print(result) print("Nach dem Aufruf von " + func.__name__) return function_wrapper @our_decorator def succ(n): return n + 1 succ(10) Die Ausgabe des Beispiels: Vor dem Aufruf von succ 11 Nach dem Aufruf von succ Es ist ebenfalls möglich, weitere Funktionen, die von Dritten geschrieben worden sind, zu dekorieren. Also Funktionen, die wir beispielsweise aus einem Modul importieren. In solchen Fällen und in unserem nächsten Beispiel können wir jedoch die Dekorationsweise mit dem „@”-Zeichen nicht verwenden. from math import sin, cos def our_decorator(func): def function_wrapper(x): print("Vor dem Aufruf von " + func.__name__) res = func(x) print(res) print("Nach dem Aufruf von " + func.__name__) return function_wrapper 33.3 @-Syntax für Dekorateure sin = our_decorator(sin) cos = our_decorator(cos) sin(3.1415) cos(3.1415) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Ausgabe: Vor dem Aufruf von sin 9.265358966049026e-05 Nach dem Aufruf von sin Vor dem Aufruf von cos -0.9999999957076562 Nach dem Aufruf von cos Zusammenfassend können wir Folgendes festhalten: Ein Dekorateur ist ein aufrufbares Python-Objekt, welches zur Modifikation von Funktions-, Methoden- oder Klassendefinitionen genutzt werden kann. Das Objekt, welches modifiziert werden soll, wird dem Dekorateur als Argument übergeben. Der Dekorateur liefert dann das modifizierte Objekt zurück. Die vorige Wrapper-Funktion function_wrapper funktioniert nur für Funktionen mit genau einem Parameter. Im nachfolgenden Beispiel haben wir sie so umgeschrieben, dass sie Funktionen mit beliebigen Parametern verarbeiten kann: from random import random, randint, choice def our_decorator(func): def function_wrapper(*args, **kwargs): print("Vor dem Aufruf von " + func.__name__) res = func(*args, **kwargs) print(res) print("Nach dem Aufruf von " + func.__name__) return function_wrapper random = our_decorator(random) randint = our_decorator(randint) choice = our_decorator(choice) random() randint(3, 8) choice([4, 5, 6]) Wir erhalten wie erwartet die folgenden Ergebnisse: Vor dem Aufruf von random 0.16420183945821654 Nach dem Aufruf von random Vor dem Aufruf von randint 8 349 350 33 Dekorateure Nach dem Aufruf von randint Vor dem Aufruf von choice 5 Nach dem Aufruf von choice Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 33.4 33.4.1 Anwendungsfälle für Dekorateure Überprüfung von Argumenten durch Dekorateure Die im folgenden definierte Funktion is_prime berechnet, ob eine positive ganze Zahl eine Primzahl ist: def is_prime(n): return all(n % i for i in xrange(2, n)) Die Funktion all und damit auch die Funktion is_prime liefert True zurück, wenn alle Zahlen zwischen 2 und (n-1) einen von 0 verschiedenen Rest liefern, wenn man n durch sie teilt. Wir können nun einen Dekorateur argument_test_natural_number schreiben, der prüft, ob ein Argument eine positive ganze Zahl ist. Damit können wir dann is_prime dekorieren, da diese Funktion nur für positive ganze Zahlen definiert ist: def argument_test_natural_number(f): def helper(x): if type(x) == int and x > 0: return f(x) else: raise Exception("Argument is not an integer") return helper @argument_test_natural_number def is_prime(n): return all(n % i for i in range(2, n)) for i in range(1,10): print(i, is_prime(i)) print(is_prime(-1)) Das obige Programm liefert folgende Ausgaben: 1 2 3 4 5 6 True True True False True False Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 33.4 Anwendungsfälle für Dekorateure 7 True 8 False 9 False Traceback (most recent call last): File "prime_test.py", line 16, in <module> print(is_prime(-1)) File "prime_test.py", line 6, in helper raise Exception("Argument is not an integer") Exception: Argument is not an integer 33.4.2 Funktionsaufrufe mit einem Dekorateur zählen Im folgenden Beispiel zeigen wir, wie wir unter Benutzung eines Dekorateurs elegant die Aufrufe einer Funktion zählen können. Wir können diesen Dekorateur aber nur für Funktionen mit einem Parameter benutzen: def call_counter(func): def helper(x): helper.calls += 1 return func(x) helper.calls = 0 return helper @call_counter def succ(x): return x + 1 print(succ.calls) for i in range(10): succ(i) print(succ.calls) Die Ausgabe sieht wie folgt aus: 0 10 Unsere bisherigen Beispiele für Dekorateure waren nur für Funktionen mit einem Parameter geeignet. Wir zeigen nun, wie wir mittels *args und **kwargs beliebige Dekorateure schreiben können, d.h. solche, die mit einer beliebigen Anzahl an Positions- und KeywordParametern umgehen können. def call_counter(func): def helper(*args, **kwargs): helper.calls += 1 return func(*args, **kwargs) 351 352 33 Dekorateure helper.calls = 0 return helper Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. @call_counter def succ(x): return x + 1 @call_counter def mul1(x, y=1): return x*y + 1 print(succ.calls) for i in range(10): succ(i) mul1(3, 4) mul1(4) mul1(y=3, x=2) print(succ.calls) print(mul1.calls) Die Ausgabe sieht dann so aus: 0 10 3 33.5 Dekorateure mit Parametern Bisher hatten wir nur Dekorateure ohne Parameter geschrieben, auch wenn die Dekoratorfunktion einen Parameter hat, nämlich die Referenz auf die zu dekorierende Funktion. Unter „Dekorateur mit Parameter” verstehen wir Dekorationen der Art @greeting("Moin"), wie wir in diesem Kapitel sehen werden. Betrachten wir die beiden Dekorateure: def evening_greeting(func): def function_wrapper(x): print("Good evening, " + func.__name__ + " returns:") func(x) return function_wrapper def morning_greeting(func): def function_wrapper(x): print("Good morning, " + func.__name__ + " returns:") func(x) 33.5 Dekorateure mit Parametern return function_wrapper @evening_greeting def foo(x): print(42) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. foo("Hi") Diese beiden Dekorateure sind beinahe identisch außer dem Begrüßungstext. Wollten wir nun noch weitere Begrüßungsfloskeln als Dekorateure definieren, müssten wir für jede einen eigenen Dekorateur definieren. Stattdessen werden wir nun einen Dekorateur mit einem Parameter schreiben, damit der Gruß bei der Dekoration selbst verändert werden kann. Damit das klappt, müssen wir unsere Dekoratorfunktion in eine Wrapper-Funktion greeting einpacken. Dies versetzt uns nun in die Lage auf verschiedenste Arten zu grüßen, so auch hanseatisch: def greeting(expr): def greeting_decorator(func): def function_wrapper(x): print(expr + ", " + func.__name__ + " returns:") func(x) return function_wrapper return greeting_decorator @greeting("Moin") def foo(x): print(42) foo("Hi") Die Ausgabe: Moin, foo returns: 42 Wenn wir die @-Schreibweise nicht verwenden wollen oder können, so lässt sich dies auch mittels Funktionsaufrufen realisieren: def foo(x): print(42) special_greeting = greeting("Moin") foo = special_greeting(foo) foo("Hi") Das Ergebnis ist identisch: Moin, foo returns: 42 353 354 33 Dekorateure Natürlich ist die zusätzliche Definition von special_greeting nicht nötig. Wir können die Rückgabe aus greeting("Moin") direkt an foo übergeben: foo = greeting("Moin")(foo) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 33.6 Benutzung von Wraps aus functools Die Art und Weise, wie wir bisher Dekorateure definiert haben, hat nicht berücksichtigt, dass die Attribute ■ __name__ (Name der Funktion), ■ __doc__ (der Docstring) und ■ __module__ (das Modul, in dem die Funktion definiert ist) der originalen Funktionen bei der Dekoration verloren gehen. Wir zeigen dies in dem folgenden Beispiel: def greeting(func): def function_wrapper(x): """ Der Funktions-Wrapper von greeting """ print("Hi, " + func.__name__ + " liefert zurück:") return func(x) return function_wrapper @greeting def f(x): """ nur eine Funktion """ return x + 4 f(10) print("function name: " + f.__name__) print("docstring: " + f.__doc__) print("module name: " + f.__module__) Wir erhalten ungewollte und gegebenenfalls unerwartete Ergebnisse: Hi, f liefert zurück: function name: function_wrapper docstring: Der Funktions-Wrapper von greeting module name: __main__ Wir können die originalen Attribute der Funktion f speichern, indem wir sie innerhalb des Dekorateurs zuweisen. Wir passen den vorigen Dekorateur entsprechend an und lassen den Rest des Programms unverändert: def greeting(func): def function_wrapper(x): 33.7 Klassen statt Funktionen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. """ Der Funktions-Wrapper von greeting """ print("Hi, " + func.__name__ + " liefert zurück:") return func(x) function_wrapper.__name__ = func.__name__ function_wrapper.__doc__ = func.__doc__ function_wrapper.__module__ = func.__module__ return function_wrapper Jetzt erhalten wir die richtigen Ergebnisse: Hi, f liefert zurück: function name: f docstring: nur eine Funktion module name: __main__ Wir müssen zum Glück nicht diesen ganzen Code bei jedem Dekorateur hinzufügen, um diese Ergebnisse zu erhalten. Dies kann der Dekorateur wraps aus dem Modul functools für uns erledigen: from functools import wraps def greeting(func): @wraps(func) def function_wrapper(x): """ Der Funktions-Wrapper von greeting """ print("Hi, " + func.__name__ + " liefert zurück:") return func(x) return function_wrapper 33.7 33.7.1 Klassen statt Funktionen Die __call__-Methode Bis hierher haben wir Funktionen als Dekorateure verwendet. Bevor wir einen Dekorateur als Klasse definieren können, führen wir die __call__-Methode der Klassen ein. Wir hatten bereits erwähnt, dass ein Dekorateur einfach ein aufrufbares Objekt ist, welches eine Funktion als Parameter entgegennimmt. Vielen Programmierern ist aber nicht bekannt, dass wir Instanzen von Klassen ebenfalls aufrufbar machen können. Die __call__-Methode wird aufgerufen, wenn eine Instanz „wie eine Funktion” aufgerufen wird. Sprich, man benutzt Klammern. Das hedeutet, dass eine Instanz einer Klasse mit __call__-Methode aufrufbar oder englisch „callable” ist. class A: def __init__(self): print("Eine Instanz von A wurde initialisiert") 355 356 33 Dekorateure def __call__(self, *args, **kwargs): print("Die Argumente lauten:", args, kwargs) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. x = A() print("Aufruf der gerade erzeugten Instanz:") x(3, 4, x=11, y=10) print("Rufen wir sie nochmals auf:") x(3, 4, x=11, y=10) Wir erhalten folgende Ausgabe: Eine Instanz von A wurde initialisiert Aufruf der gerade erzeugten Instanz: Die Argumente lauten: (3, 4) {'x': 11, 'y': 10} Rufen wir sie nochmals auf: Die Argumente lauten: (3, 4) {'x': 11, 'y': 10} Wir können eine Klasse für die Fibonacci-Funktion schreiben, indem wir __call__ benutzen: class Fibonacci: def __init__(self): self.cache = {} def __call__(self, n): if n not in self.cache: if n < 2: self.cache[n] = n else: self.cache[n] = self.__call__(n-1) + self.__call__(n -2) return self.cache[n] fib = Fibonacci() for i in range(15): print(fib(i), end=", ") Die Ausgabe: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 33.8 Eine Klasse als Dekorateur benutzen 33.8 Eine Klasse als Dekorateur benutzen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir werden folgenden Dekorateur als Klasse umschreiben: def decorator1(f): def helper(): print("Decorating", f.__name__) f() return helper @decorator1 def foo(): print("inside foo()") foo() Der folgende Dekorateur, der als Klasse implementiert ist, erledigt die gleiche Aufgabe: class decorator2: def __init__(self, f): self.f = f def __call__(self): print("Decorating", self.f.__name__) self.f() @decorator2 def foo(): print("inside foo()") foo() Beide Versionen liefern das gleiche Ergebnis: Decorating foo inside foo() 357 358 33 Dekorateure 33.9 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 33.9.1 Memoisation Bedeutung und Herkunft des Begriffs Der Ausdruck „Memoisation”1 wurde im Jahre 1968 von Donald Michie geprägt. Der Ausdruck basiert auf dem lateinischen Wort „memorandum”, was „das zu Erinnernde” bedeutet. Memoisation ist eine Technik, die in der Programmierung benutzt wird, um den Programmablauf zu beschleunigen. Dies wird erreicht, indem Berechnungsergebnisse gespeichert werden wie zum Beispiel die Ergebnisse von Funktionsaufrufen. Wird eine Funktion mit den gleichen Parametern aufgerufen wie bei einem vorigen Aufruf, wird auf die gespeicherten Ergebnisse zugegriffen, statt die Werte neu zu berechnen. In vielen Fällen wird ein einfaches Array für die Ergebnisspeicherung benutzt, aber es sind auch kompliziertere Strukturen möglich. So können beispielsweise assoziative Arrays verwendet werden, die in Perl Hashes und in Python Dictionaries genannt werden. Die Memoisation kann explizit vom Program- Bild 33.2 Gehirn mierer programmiert werden, aber einige Programmiersprachen wie auch Python stellen Mechanismen zur Verfügung, welche die Implementierung der Memoisation erleichtern. 33.9.2 Memoisation mit Dekorateur-Funktionen In unserem Kapitel über rekursive Funktionen hatten wir eine iterative und eine rekursive Funktionsversion zur Berechnung der Fibonacci-Zahlen programmiert. Wir hatten gezeigt, dass die direkte Umsetzung der mathematischen Definition der Fibonacci-Zahlen ein exponentielles Laufzeitverhalten aufzeigt, also eine Funktion wie die folgende: def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) In unserem Kapitel über Rekursion hatten wir auch einen Weg dargestellt, wie man das Laufzeitverhalten der rekursiven Version verbessern kann. Wir hatten ein Dictionary dazu verwendet, um bereits durch die Funktion berechnete Werte für spätere Aufrufe zu speichern. Dadurch wird eine Neuberechnung verhindert. Dabei handelte es sich bereits, ohne 1 Wir haben den englischen Fachbegriff eingedeutscht, da es keinen deutschen Begriff gibt. Im Englischen kommt der Begriff in den Schreibweisen Memoisation und Memoization vor. 33.9 Memoisation Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. dass wir es so genannt hatten, um ein Beispiel, wie man explizit die Technik der Memoisation nutzen kann. Die Methode hatte jedoch einen entscheidenden Nachteil: Die Klarheit und die Schönheit der ursprünglichen rekursiven Methode ging dabei verloren. Das „Problem” besteht darin, dass wir den Code der rekursiven fib-Funktion ändern bzw. erweitern mussten. Im folgenden Code präsentieren wir eine Lösung mit einem Dekorateur (die Funktion memoize), die ohne Änderungen der fib-Funktion auskommt. Dadurch wird ihre Klarheit, Schönheit und Lesbarkeit nicht tangiert. Zu diesem Zweck definieren wir eine Funktion, die wir memoize nennen. memoize benötigt zwei Argumente. Auch die Funktion memoize benutzt ein Dictionary, um die Funktionswerte zu speichern. Obwohl sowohl die Variable „memo” (unser Dictionary zum Speichern der Werte) als auch die Funktion „f” lokal innerhalb der Funktion memoize sind, werden ihre Werte durch die helper-Funktion bewahrt. Im Englischen benutzt man statt „bewahrt” das Wort „captured”, was im Deutschen „eingefangen” oder „gefangen genommen” bedeutet. Diese Technik, d.h. das Bewahren von „lokalen” Werten oder Funktionen über ihren lokalen Geltungsbereich hinaus, wird auch als Closure oder Funktionsabschluss bezeichnet. memoize liefert als Funktionswert eine Referenz auf die helper-Funktion zurück. Ein Aufruf der Funktion memoize(fib) liefert eine Funktion zurück, die im Ein- und Ausgabeverhalten identisch mit fib ist, d.h. sie tut, was fib tut. Zusätzlich speichert sie jedoch die Ergebnisse im memo-Dictionary. def memoize(f): memo = {} def helper(x): if x not in memo: memo[x] = f(x) return memo[x] return helper @memoize def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) print(fib(40)) 33.9.3 Memoisation mit einer Klasse Wir können das Zwischenspeichern der Ergebnisse auch in einer Klasse statt einer Funktion einkapseln. Wir demonstrieren dies im folgenden kleinen Beispiel: 359 360 33 Dekorateure class Memoize: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __init__(self, fn): self.fn = fn self.memo = {} def __call__(self, *args): if args not in self.memo: self.memo[args] = self.fn(*args) return self.memo[args] @Memoize def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) Da wir ein Dictionary verwenden, können wir keine veränderlichen2 Argumente verwenden. Anders ausgedrückt: Die Argumente dürfen nur unveränderlich3 , also z.B. Tupel, sein. 33.9.4 Memoisation mit functools.lru_cache Das Modul functools enthält einen komfortablen Dekorateur zur Memoisation, und zwar den lru_cache-Dekorateur. Die Buchstaben „lru” stehen für „least recently used” (am längsten nicht verwendet), das bedeutet, dass sich der Dekorator nur die zuletzt berechneten Werte in einem limitierten Cache merkt. import functools @functools.lru_cache() def fib(n): if n < 2: return n else: return fib(n-1) + fib(n-2) Die Arbeitsweise dieses Dekorateurs können wir am besten aus folgendem Beispiel ersehen: >>> import functools >>> @functools.lru_cache() 2 3 englisch: mutable englisch: immutable Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 33.9 Memoisation ... def f(x): ... return x ... >>> f.cache_info() CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) >>> f(1) 1 >>> f(3) 3 >>> f(1) 1 >>> f.cache_info() CacheInfo(hits=1, misses=2, maxsize=128, currsize=2) f.cache_info() liefert die Cache-Statistik (hits, misses, maxsize, currsize) zurück. f.cache_clear() löscht den Cache und die Statistik. Bei dem lru_cache-Dekorateur handelt es sich um einen Dekorateur, der mit Parametern aufgerufen wird. Er kann mit den beiden Schlüsselwortparametern maxsize und typed aufgerufen werden. In unserem Beispielaufruf wurden für die Parameter die Default-Werte maxsize = 128 und typed = False verwendet. Falls maxsize auf None gesetzt wird, werden die LRU-Eigenschaften disabled, und der Cache kann ungebunden wachsen. Wir demonstrieren dies im folgenden Beispiel: import functools @functools.lru_cache(maxsize=None) def f(x): return x for i in range(1000): f(i) print(f.cache_info()) An der Ausgabe können wir erkennen, dass der Cache auf 1000 Elemente angestiegen ist: CacheInfo(hits=0, misses=1000, maxsize=None, currsize=1000) Ruft man den Dekorateur entweder ohne Argumente oder mit der Zuweisung maxsize=128 auf, so können wir an der Ausgabe sehen, dass der Cache auf 128 begrenzt ist: CacheInfo(hits=0, misses=1000, maxsize=128, currsize=128) Falls typed auf True gesetzt ist, werden Argumente von verschiedenem Type separat gecashed. So werden dann z.B. f(3.0) und f(3) als verschiedene Aufrufe betrachtet. Die Argumente der dekorierten Funktion müssen hash-bar sein. Die zugrunde liegende Funktion kann man mit f.__wrapped__ ansprechen. 361 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 362 33 Dekorateure >>> @functools.lru_cache(typed=True) ... def g(x): ... return x ... >>> g(1) 1 >>> g(1.0) 1.0 >>> g.cache_info() CacheInfo(hits=0, misses=2, maxsize=128, currsize=2) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Teil IV Weiterführende Themen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 34 34.1 Tests und Fehler Einführung Es ist kein Zufall, dass wir unseren weiterführenden Teil mit einem Kapitel über Fehler, Fehlersuche und Fehlervermeidung beginnen. Tests und Fehlersuche gehören zum Alltag eines jeden Programmierers. Die Zeit, die zur Fehlersuche und zum Testen eines Programms verwendet wird, hat üblicherweise einen hohen Anteil an der Programmentwicklung, Studien sprechen von bis zu 50 %.1 Dass Fehler menschlich seien, wurde schon vor mehr als 2000 Jahren von Cicero festgestellt. Sein „errare humanum est”2 gilt häufig auch als Ausrede, um unzureichende Arbeitsergebnisse zu entschuldigen. Auch Bild 34.1 Fehler sind menschlich. wenn wir Fehler nie vollständig vermeiden können, sollten wir danach trachten, die Anzahl der Fehler, die wir machen (und vor allen diejenigen, die im Produkt bleiben könnten), minimal zu halten. Es gibt unterschiedliche Fehlerarten. Während der Programmentwicklung treten immer wieder „kleine Fehler” auf, bei denen es sich meistens um Flüchtigkeitsfehler bzw. Tippfehler handelt. Irgendwo fehlt immer mal ein Doppelpunkt, so zum Beispiel hinter einem „else”, oder man hat vielleicht „true” statt True geschrieben. All dies sind sogenannte syntaktische Fehler oder Syntaxfehler.3 Dabei handelt es sich um die Verletzung von Schreibweisen einzelner Wörter, also beispielsweise Schlüsselwörtern4 oder von Strukturregeln. In 1 2 3 4 Boehm und Barry sprechen von 40 - 50 %, die zum Beheben von Design- und Code-Fehlern benötigt werden: Boehm, Barry W. 1987. „Improving Software Productivity.” IEEE Computer, September: 43-57.; Jones, Capers, ed. 1986. Der Spruch stammt von Cicero und lautet in voller Länge: „Errare (Errasse) humanum est, sed in errare (errore) perseverare diabolicum.” Auf Deutsch lautet dies: „Irren ist menschlich, aber auf Irrtümern zu beharren ist teuflisch.” Unter der Syntax einer Programmiersprache versteht man die Beschreibung der erlaubten Zeichenketten für Programme dieser Sprache. Fast alle Programmiersprachen werden mittels kontextfreier Grammatiken syntaktisch beschrieben. Man spricht auch in natürlichen Sprachen von syntaktischen Fehlern. „I is learning Python” ist beispielsweise ein Syntaxfehler in der englischen Sprache. englisch: key words 366 34 Tests und Fehler der Linguistik würde man das als Satzbauregeln bezeichnen, also zum Beispiel der vergessene Doppelpunkt hinter dem „else” oder eckiger statt runder Klammer bei einem Funktionsaufruf. Neben den syntaktischen Fehlern, die sich häufig relativ leicht finden und beheben lassen, gibt es auch die semantischen Fehler.5 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Betrachten wir folgenden Programmausschnitt: x = int(input("x? ")) y = int(input("y? ")) if x > 10: if y == x: print("Fine") else: print("So what?") Es gibt zwei if-Anweisungen, aber nur eine else-Anweisung. Das Codefragment ist zwar syntaktisch korrekt, aber es könnte durchaus sein, dass der Schreiber des Programms den Text „So what?” nur ausgeben wollte, wenn x größer als 10 ist und der Wert von y dem von x entspricht. Das bedeutet, dass er folgenden Code hätte schreiben müssen: x = int(input("x? ")) y = int(input("y? ")) if x > 10: if y == x: print("Fine") else: print("So what?") Beide Programme bzw. Programmfragmente sind syntaktisch korrekt. Aber einer von beiden verstößt gegen die intendierte Semantik, dass der Text „So what?” nur ausgegeben werden sollte, wenn x größer als 10 ist und der Wert von y dem von x entspricht. Der Programmierer hatte wahrscheinlich die Semantik der if-Anweisung korrekt verstanden, aber sein Problem nicht richtig umgesetzt. Es ist aber auch denkbar, dass ein Programmierer die Semantik eines Python-Konstrukts nicht richtig verstanden und deshalb einen Fehler produziert hat. Betrachten wir hierzu das folgende Beispiel: >>> for i in range(7): ... print(i) ... 0 1 5 Die Bedeutung der einzelnen Sprachkonstrukte einer Sprache wird als die Semantik einer Programmiersprache bezeichnet. Normalerweise beißen Männer keine Hunde, sodass man im folgenden Satz von einem Semantikfehler ausgehen kann: „The man bites the dog!” 34.2 Modultests Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 2 3 4 5 6 >>> Aus der Tatsache, dass es keinen Fehler bei der Ausführung gab, ersehen wir, dass diese Anweisungen syntaktisch korrekt sind. Ob sie jedoch semantisch korrekt sind, können wir nicht entscheiden, da wir ja die Problemstellung nicht kennen. Nehmen wir an, dass der Programmierer die Zahlen von 1 bis 7 hatte ausgeben wollen. In diesem Fall hat er einen semantischen Fehler begangen, der wohl daraus resultiert, dass er die range-Funktion nicht richtig verstanden hat. Er ging irrtümlich davon aus, dass range(7) die Zahlen von 1 bis 7 produziere, d.h. 1, 2, 3, 4, 5, 6 und 7. Wir können also die Semantikfehler in zwei Gruppen unterteilen: ■ Fehler, die aus dem mangelnden Verständnis eines Sprachkonstrukts herrühren. ■ Fehler, die aus der fehlerhaften Umsetzung des Problems resultieren. 34.2 Modultests Für den Begriff Modultest oder Komponententest wird häufig im Deutschen der englische Begriff „Unittest” („unit test”) verwendet. Modultests verwendet man in der Software-Entwicklung, um Module (also funktionale Einzelteile eines Programms) zu testen, d.h. man prüft, ob sie die geforderte Funktionalität erfüllen. Es empfiehlt sich, Tests auf Modulebene durchzuführen, da die dortigen Funktionalitäten noch eine begrenzte bzw. überschaubare Komplexität zeigen und die Bild 34.2 Testen und Messen Schnittstellen noch klar definiert sind. Häufig kann man so ein Modul vollständig auf Korrektheit prüfen. Dies ist auf einem umfassenden Software-Paket in der Regel nicht mehr möglich. Niemand kann beispielsweise ein Betriebssystem vollständig auf Korrektheit prüfen. 367 368 34 Tests und Fehler Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 34.3 Modultests unter Benutzung von __name__ Jedes Python-Modul hat einen im built-in-Attribut __name__ definierten Namen. Nehmen wir an, wir haben ein Modul mit dem Namen „abc” unter dem Dateinamen „abc.py” gespeichert. Wird dieses Modul mit „import abc” importiert, dann hat das built-in-Attribut __name__ den Wert „abc”. Wird die Datei „abc.py” jedoch als eigenständiges Programm aufgerufen, also mittels $python3 abc.py dann hat diese Variable den Wert ’__main__’. Anhand des folgenden Moduls (das sowohl eine einzelne Fibonacci-Zahl für eine bestimmte Generation n als auch die Liste der Fibonacci-Zahlen bis zu einer Generation n berechnen kann) wollen wir veranschaulichen, wie man mit dem built-in-Attribut __name__ einen einfachen, aber sehr wirkungsvollen Modultest durchführen kann. Das fibonacci-Modul sieht wie folgt aus und ist unter dem Namen fibonacci.py in unserem Beispielverzeichnis zu finden: """ Modul mit wichtigen Funktionen zur Fibonacci-Folge """ def fib(n): """ Die Fibonacci-Zahl für die n-te Generation wird iterativ berechnet """ a, b = 0, 1 for i in range(n): a, b = b, a + b return a def fiblist(n): """ produziert die Liste der Fibbo-Zahlen bis zur n-ten Generation """ fib = [0,1] for i in range(1,n): fib += [fib[-1]+fib[-2]] return fib Natürlich kann man dieses Modul auch „manuell” in der interaktiven Python-Shell testen: >>> >>> 0 >>> 1 >>> 55 >>> [0, >>> from fibonacci import fib, fiblist fib(0) fib(1) fib(10) fiblist(10) 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] fiblist(-8) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 34.3 Modultests unter Benutzung von __name__ [0, 1] >>> fib(-1) 0 >>> fib(0.5) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "fibonacci.py", line 6, in fib for i in range(n): TypeError: 'float' object cannot be interpreted as an integer >>> Wir sehen, dass die Funktionen auf Integer-Zahlen definiert sind. Für negative ganze Zahlen liefert die Funktion fib nur Nullen zurück, während fiblist nur die Liste [0,1] zurückliefert. Ruft man die Funktionen mit Float-Zahlen auf, gibt es einen TypeError, weil range nicht für Float-Werte definiert ist. Unser Modul könnten wir prüfen, indem wir testen, ob die Aufrufe für fib() und fiblist() für bestimmte Werte definierte Ergebniswerte zurückliefern. Man könnte also unser Modul direkt um eine oder mehrere if-Anweisungen erweitern: if fib(0) == 0 and fib(10) == 55 and fib(50) == 12586269025: print("Test für fib-Funktion erfolgreich") else: print("fib-Funktion liefert fehlerhafte Werte") Ruft man das Programm dann direkt auf, erhält man folgende Ausgabe: $ python3 fibonacci.py Test für fib-Funktion erfolgreich Nun wollen wir bewusst einen Fehler in unsere fib-Funktion einbauen. Dazu ändern wir die Zeile a, b = 0, 1 in a, b = 1, 1 um.6 Ein Aufruf des veränderten Moduls liefert nun eine Fehlermeldung: $ python3 fibonacci.py fib-Funktion liefert fehlerhafte Werte Dieses Vorgehen hat jedoch einen entscheidenden Nachteil. Wenn man das Modul importiert, wird auch das Ergebnis dieses oder ähnlicher Tests angezeigt: >>> import fibonacci Test für fib-Funktion erfolgreich 6 Im Prinzip liefert fib zwar noch die Fibonacci-Werte, aber um eins versetzt. Wollen wir den n-ten Wert (für n größer als 0 berechnen), so müssen wir fib(n-1) aufrufen. 369 370 34 Tests und Fehler Es ist aber störend und auch nicht üblich, wenn Module solche Meldungen beim import ausgeben. Module sollen sich „schweigend” laden lassen. Die Lösung für dieses Problem stellt das eingangs erwähnte built-in-Attribut __name__ dar. Wird unser Modul direkt gestartet, also nicht importiert, hat __name__ den Wert „__main__”. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir können unser Modul nun so umschreiben, dass die Tests nur gestartet werden, wenn das Modul direkt gestartet wird: """ Modul mit wichtigen Funktionen zur Fibonacci-Folge """ def fib(n): """ Iterative Fibonacci-Funktion """ a, b = 0, 1 for i in range(n): a, b = b, a + b return a def fiblist(n): """ produziert Liste der Fibbo-Zahlen """ fib = [0,1] for i in range(1,n): fib += [fib[-1]+fib[-2]] return fib if __name__ == "__main__": if fib(0) == 0 and fib(10) == 55 and fib(50) == 12586269025: print("Test für fib-Funktion erfolgreich") else: print("fib-Funktion liefert fehlerhafte Werte") Nun gibt es keine Ausgaben, wenn das Modul importiert wird, und zwar weder im Fehlerfall noch im Erfolgsfall. Diese Methode ist die einfachste und am weitesten verbreitetste Methode für Modultests. 34.4 doctest-Modul Das doctest-Modul stellt eine weitere einfache Methode dar, Modultests durchzuführen. Der eigentliche Test befindet sich bei dieser Methode, wie der Name vermuten lässt, im Docstring. Vorgehensweise: Man muss das Modul „doctest” importieren. Dann kopiert man einen Auszug aus einer interaktiven Sitzung in den Docstring einer Funktion. 34.4 doctest-Modul Im Folgenden zeigen wir das Vorgehen an einem simplen Beispiel. Dazu haben wir das vorige fibonacci-Modul bis auf die Funktion fib abgespeckt: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. import doctest def fib(n): """ Die Fibonacci-Zahl für die n-te Generation wird iterativ berechnet """ a, b = 0, 1 for i in range(n): a, b = b, a + b return a Dieses Modul rufen wir nun in einer interaktiven Python-Shell auf und lassen ein paar Werte berechnen: >>> >>> 0 >>> 1 >>> 55 >>> 610 >>> from fibonacci import fib fib(0) fib(1) fib(10) fib(15) Diese Aufrufe mit den Ergebnissen kopieren wir aus der interaktiven Shell in den Docstring unserer Funktion. Damit das Modul doctest aktiv wird, müssen wir die Methode testmod() starten, falls das Modul direkt aufgerufen wird. Dies können wir wie üblich mit einem Test des Attributs __name__ auf den Wert „__main__” machen. Das vollständige Modul sieht nun wie folgt aus: import doctest def fib(n): """ Die Fibonacci-Zahl für die n-te Generation wird iterativ berechnet >>> 0 >>> 1 >>> 55 >>> fib(0) fib(1) fib(10) fib(15) 371 372 34 Tests und Fehler Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 610 >>> """ a, b = 0, 1 for i in range(n): a, b = b, a + b return a if __name__ == "__main__": doctest.testmod() Starten wir obiges Modul direkt mit dem Aufruf $ python3 fibonacci_doctest.py erhalten wir keine Ausgabe, weil alles okay ist. Um das Verhalten im Fehlerfall zu sehen, bauen wir wieder einen kleinen Fehler ein. Dazu ändern wir erneut die Zeile a, b = 0, 1 in a, b = 1, 1 um. Nun erhalten wir folgende Ausgabe beim direkten Start des Moduls: $ python3 fibonacci_doctest.py ********************************************************************** File "fibonacci_doctest.py", line 8, in __main__.fib Failed example: fib(0) Expected: 0 Got: 1 ********************************************************************** File "fibonacci_doctest.py", line 12, in __main__.fib Failed example: fib(10) Expected: 55 Got: 89 ********************************************************************** File "fibonacci_doctest.py", line 14, in __main__.fib Failed example: fib(15) Expected: 610 34.5 Testgetriebene Entwicklung oder „Im Anfang war der Test” Got: 987 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ********************************************************************** 1 items had failures: 3 of 4 in __main__.fib Test Failed *** *** 3 failures. Es werden alle Aufrufe angezeigt, die ein fehlerhaftes Ergebnis geliefert haben. Wir sehen jeweils den Beispielaufruf hinter der Zeile „Failed example:”. Hinter „Expected:” folgt der erwartete Wert, also der korrekte Wert, und hinter „Got:” folgt der von der Funktion produzierte Ausdruck, also der Wert, den doctest beim Aufruf von fib erhalten hat. 34.5 Testgetriebene Entwicklung Im vorigen Kapitel hatten wir bereits eine fertig geschriebene Fibonacci-Funktion, die man zu testen hatte. Man kann aber auch so vorgehen, dass man bereits am Anfang – also bevor man die zu testende Funktion geschrieben hat – Ergebnisse in den Docstring schreibt und dann erst mit der Entwicklung der Funktion beginnt. Im Folgenden haben wir den Rückgabewert der Funktion fib fest auf 0 gesetzt: import doctest def fib(n): """ Die Fibonacci-Zahl für die n-te Generation wird iterativ berechnet >>> 0 >>> 1 >>> 55 >>> 610 >>> fib(0) fib(1) fib(10) fib(15) """ return 0 if __name__ == "__main__": doctest.testmod() 373 374 34 Tests und Fehler Es versteht sich von selbst, dass ein Test dieses Moduls außer für fib(0) nur Fehler liefert: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. $ python3 fibonacci_TDD.py ********************************************************************** File "fibonacci_TDD.py", line 10, in __main__.fib Failed example: fib(1) Expected: 1 Got: 0 ********************************************************************** File "fibonacci_TDD.py", line 12, in __main__.fib Failed example: fib(10) Expected: 55 Got: 0 ********************************************************************** File "fibonacci_TDD.py", line 14, in __main__.fib Failed example: fib(15) Expected: 610 Got: 0 ********************************************************************** 1 items had failures: 3 of 4 in __main__.fib ***Test Failed*** 3 failures. Man ändert bzw. schreibt nun den eigentlichen Code der Funktion fib solange, bis die Tests im Doctest „bestanden” werden. Dieses Vorgehen ist eine Methode der Software-Entwicklung, die man als „Testgetriebene Entwicklung” oder auch „Testgesteuerte Entwicklung” bezeichnet. Aber wie so häufig in der SW-Branche werden auch hier die englischen Begriffe benutzt, d.h. „test first development” oder noch geläufiger „test-driven development” (TDD). 34.6 unittest 34.6 unittest Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Für das Modul „unittest” standen JUnit7 von Erich Gamma und SUnit8 von Kent Beck Pate. Ein deutlicher Unterschied zum Modul doctest besteht darin, dass die Testfälle bei dem Modul „unittest” außerhalb des eigentlichen Programmcodes definiert werden, d.h. in einer eigenen Datei. Der Vorteil besteht unter anderem darin, dass die Programmdokumentation und die Testbeschreibungen voneinander getrennt sind. Der Preis dafür besteht besteht jedoch in einem erhöhten Aufwand für die Erstellung der Tests. Wir wollen nun für unser Modul fibonacci.py einen Test mit unittest erstellen. In einer Datei fibonacci_unittest.py9 müssen wir das Modul unittest und das zu testende Modul, also in unserem Fall fibonacci, importieren. Außerdem müssen wir eine Klasse mit beliebigem Namen – wir wählen in unserem Beispiel „FibonacciTest” – erstellen, die von unittest.TestCase erbt. In dieser Klasse werden die nötigen Testfälle in Methoden definiert. Der Name dieser Methoden ist beliebig, er muss jedoch mit test beginnen. In unserer Methode „testCalculation” benutzen wir die Methode assertEqual der Klasse TestCase. assertEqual(first, second, msg = None) prüft, ob der Ausdruck „first” gleich dem Ausdruck „second” ist. Falls die beiden Ausdrücke ungleich sind, wird msg ausgegeben, wenn msg ungleich None ist. import unittest from fibonacci import fib class FibonacciTest(unittest.TestCase): def testCalculation(self): self.assertEqual(fib(0), 0) self.assertEqual(fib(1), 1) self.assertEqual(fib(5), 5) self.assertEqual(fib(10), 55) self.assertEqual(fib(20), 6765) if __name__ == "__main__": unittest.main() Rufen wir obigen Testfall auf, erhalten wir folgende Ausgabe: $ python3 fibonacci_unittest.py . ---------------------------------------------------------------------Ran 1 test in 0.000s OK 7 8 9 Dabei handelt es sich um ein Framework zum Testen von Java-Programmen, das für automatisierte UnitTests von einzelnen Klassen und Methoden besonders geeignet ist. SUnit ist ein Smalltalk-Testframework, das von Kent Beck erfunden wurde. SUnit wurde auf Java unter dem Namen JUnit portiert. Der Name kann beliebig gewählt sein. Wichtig ist lediglich, dass der Testfall in einer separaten Datei steht. 375 376 34 Tests und Fehler Bei der normalen Programmentwicklung ist dies das von uns gewünschte Ergebnis. Hier sind wir allerdings interessiert, was im Fehlerfall passiert. Wir produzieren deshalb wieder unseren Fehler. Dazu ändern wir von Neuem die Zeile a, b = 0, 1 in Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. a, b = 1, 1 um. Jetzt sieht der Test wie folgt aus: $ python3 fibonacci_unittest.py F ====================================================================== FAIL: testCalculation (__main__.FibonacciTest) ---------------------------------------------------------------------Traceback (most recent call last): File "fibonacci_unittest.py", line 7, in testCalculation self.assertEqual(fib(0), 0) AssertionError: 1 != 0 ---------------------------------------------------------------------Ran 1 test in 0.000s FAILED (failures=1) Bereits die erste Anweisung in testCalculation hat eine Ausnahme erzeugt. In diesem Fall wurden die weiteren assertEqual-Aufrufe nicht mehr ausgeführt. Wir verändern fib nun dahingehend, dass wir nur einen falschen Wert erhalten, wenn n auf 20 gesetzt ist: def fib(n): """ Iterative Fibonacci-Funktion """ a, b = 0, 1 for i in range(n): a, b = b, a + b if n == 20: a = 42 return a Die Ausgabe eines Testlaufs sieht nun wie folgt aus: $ python3 fibonacci_unittest.py F ====================================================================== FAIL: testCalculation (__main__.FibonacciTest) ---------------------------------------------------------------------Traceback (most recent call last): File "fibonacci_unittest.py", line 12, in testCalculation self.assertEqual(fib(20), 6765) 34.7 Methoden der Klasse TestCase AssertionError: 42 != 6765 ---------------------------------------------------------------------Ran 1 test in 0.000s Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. FAILED (failures=1) Jetzt wurden aber auch die folgenden Anweisungen durchgeführt, allerdings generierten sie keine Ausnahme, da ihre Ergebnisse ja korrekt sind: self.assertEqual(fib(0), 0) self.assertEqual(fib(1), 1) self.assertEqual(fib(5), 5) 34.7 Methoden der Klasse TestCase Wir wollen nun näher auf die Klasse TestCase eingehen. Wir stellen dazu einige wichtige Methoden dieser Klasse vor. Zunächst stellen wir die beiden Hook-Methoden10 setUp() und tearDown() vor. Wir erweitern unser voriges Beispiel um eine setUp- und eine tearDown-Methode: import unittest from fibonacci import fib class FibonacciTest(unittest.TestCase): def setUp(self): self.fib_elems = ( (0,0), (1,1), (2,1), (3,2), (4,3), (5,5) ) print ("setUp executed!") def testCalculation(self): for (i,val) in self.fib_elems: self.assertEqual(fib(i), val) def tearDown(self): # Objekte können # in diesem Fall self.fib_elems = print ("tearDown gelöscht oder geändert werden macht es jedoch wenig Sinn: None executed!") if __name__ == "__main__": unittest.main() 10 Ein Hook (auch Einschubmethode) bezeichnet eine Schnittstelle, mit der fremder Programmcode in eine bestehende Anwendung integriert werden kann. Dadurch kann die Anwendung erweitert oder ihr Ablauf verändert werden. In der objektorientierten Programmierung kann dies durch Vererbung geschehen. Eine Klasse stellt leere Methoden – also Methoden, die nichts tun, wenn man sie aufruft – zur Verfügung, die dann in Unterklassen von Anwendern der Klasse nach eigenem Bedarf implementiert werden können. 377 378 34 Tests und Fehler Ein Aufruf führt zu folgendem Ergebnis: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. $ python3 fibonacci_unittest2.py setUp executed! tearDown executed! . ---------------------------------------------------------------------Ran 1 test in 0.000s OK Die meisten der TestCase-Methoden verfügen über einen optionalen Parameter „msg”. Mit „msg” kann man eine zusätzliche Beschreibung für einen Fehler ausgeben. Tabelle 34.1 Methoden der Klasse TestCase Methode Bedeutung setUp() Bei der Methode setUp handelt es sich um eine sogenannte Hook-Methode. Sie wird vor jedem Aufruf der implementierten Testmethoden aufgerufen. Wird in der Methode setUp eine Ausnahme generiert, so wird diese auch als Error in der Testausgabe ausgegeben. Selbstverständlich wird auch bei einer Ausnahme im setUp-Code der Test abgebrochen. tearDown() Die Methode tearDown wird nach dem Aufruf einer Testmethode gestartet. Ebenso wie bei setUp gilt, dass im Code von tearDown generierte Ausnahmen auch in der Testausgabe ausgegeben werden. assertEqual(self, first, second, msg=None) Der Test schlägt fehl, wenn die Parameter „first” und „second” nicht gleich sind. Dabei ist Gleichheit im Sinne von „==” gemeint, also Wertegleichheit und nicht nur reine Objektgleichheit. assertAlmostEqual(self, first, second, places=None, msg=None, delta=None) Diese Methode schlägt fehl, wenn die Differenz der beiden Parameter „first” und „second” gleich 0 ist, nachdem man sie vor dem Vergleich auf „places” Nachkommastellen gerundet hatte. Der Default-Wert für „places” ist 7. assertCountEqual(self, first, second, msg=None) Die Parameter „first” und „second” müssen hierbei sequentielle Datentypen sein. Es muss Folgendes gelten: Alle Elemente müssen genauso oft in „first” wie in „second” vorkommen. Beispiel: [0, 1, 1] und [1, 0, 1] gelten in obigem Sinne als gleich, weil die 0 und die 1 jeweils gleich oft vorkommen. [0, 0, 1] und [0, 1] sind verschieden, weil die 0 in der ersten Liste zweimal vorkommt und in der zweiten Liste nur einmal. assertDictEqual(self, d1, d2, msg=None) Betrachtet die beiden Argumente als Dictionaries und prüft auf Gleichheit. (Fortsetzung nächste Seite) 34.7 Methoden der Klasse TestCase Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Tabelle 34.1 Methoden der Klasse TestCase (Fortsetzung) Methode Bedeutung assertTrue(self, expr, msg=None) Prüft, ob der Ausdruck „expr” True ist. assertGreater(self, a, b, msg=None) Prüft, ob a > b gilt. assertGreaterEqual(self, a, b, msg=None) Prüft, ob a >= b gilt. assertFalse(self, expr, msg=None) Prüft, ob der Ausdruck „expr” True ist. assertLess(self, a, b, msg=None) Prüft, ob a < b gilt. assertLessEqual(self, a, b, msg=None) Prüft, ob a <= b gilt. assertIn(self, member, container, msg=None) Prüft, ob a in b gilt. assertIs(self, expr1, expr2, msg=None) Prüft, ob a is b gilt. assertIsInstance(self, obj, cls, msg=None) Prüft, ob isinstance(obj, cls) gilt. assertIsNone(self, obj, msg=None) Prüft, ob obj is None gilt. assertIsNot(self, expr1, expr2, msg=None) Prüft, ob a is not b gilt. assertIsNotNone(self, obj, msg=None) Prüft, ob obj nicht None ist. assertItemsEqual(self, expected_seq, actual_seq, msg=None) Auf zwei sequentiellen Datenobjekten wird eine Gleichheit über die Anzahl der Elemente geprüft, d.h. es wird geprüft, ob jedes Element in jeder Liste gleich häufig vorkommt. Beispiel: [0, 1, 1] und [1, 0, 1] gelten als gleich. [0, 0, 1] and [0, 1] gelten nicht als gleich, da die Anzahl der Nullen in beiden Listen variiert. assertListEqual(self, list1, list2, msg=None) Listen werden auf Gleichheit geprüft. assertMultiLineEqual(self, first, second, msg=None) Mehrzeilige Strings werden auf Gleichheit geprüft. assertNotRegexpMatches(self, text, unexpected_regexp, msg=None) Schlägt fehl, wenn der Text „text” den regulären Ausdruck unexpected_regexp matcht. assertTupleEqual(self, tuple1, tuple2, msg=None) Analog zu assertListEqual 379 380 34 Tests und Fehler 34.8 Aufgaben 1. Aufgabe: Betrachten Sie folgendes Beispiel für einen doctest. Erkennen Sie ein Problem? Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. import doctest def fib(n): """ Die Fibonacci-Zahl für die n-te Generation wird iterativ berechnet >>> fib(0) 0 >>> fib(1) 1 >>> fib(10) 55 >>> fib(40) 102334155 >>> """ if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) if __name__ == "__main__": doctest.testmod() Lösung: Lösungen zu Kapitel 34 (Tests und Fehler), Seite 512 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 35 35.1 Daten konservieren Persistente Speicherung In diesem Kapitel geht es darum, Wege zu zeigen, wie man Programmdaten über das Programmende oder den Programmabbruch hinaus speichern kann. Man bezeichnet dies in der Informatik als persistente Daten. Darunter versteht man, dass die Daten eines Programms auch nach dem kontrollierten oder auch unvorhergesehenen Ende bei einem erneuten Programmstart wieder zur Verfügung stehen. Daten, die nur im Hauptspeicher des Computers existieren, werden als flüchtige oder transiente Daten bezeichnet, da sie bei einem erneuten Programmstart nicht mehr zur Verfügung stehen. Zur Generierung von persistenten Daten gibt es verschiedene Möglichkeiten: ■ ■ ■ ■ Bild 35.1 Daten „eingurken” Manuelle Speicherung in einer Datei Nicht zu empfehlen, da man dann die komplette Serialisierung der Daten selbst vornehmen muss. marshal-Modul Ähnlich wie das pickle-Modul. Wir werden dieses Modul jedoch nicht behandeln, da allgemein aus verschiedenen Gründen empfohlen wird, pickle zu verwenden. pickle-Modul Wir werden im Folgenden näher auf dieses Modul eingehen. shelve-Modul Shelve baut auf pickle auf und bietet eine Dictionary-ähnliche Struktur! 382 35 Daten konservieren 35.2 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 35.2.1 Pickle-Modul Daten sichern mit pickle.dump Beim Programmieren kommt es natürlich immer wieder mal vor, dass man in die Klemme gerät, aber bei Python ist das wahrscheinlich – hoffen wir – seltener als in anderen Sprachen der Fall. In die Klemme geraten heißt im Englischen „to get oneself into a pickle”. Aber „to pickle” bedeutet eigentlich einlegen oder pökeln, z.B. saure Gurken. Aber was hat nun das Pökeln von sauren Gurken mit Python zu tun? Na ja, ganz einfach: Python hat ein Modul, das „pickle” heißt, und mit dem kann man Python-Objekte gewissermaßen einlegen, um sie später, also in anderen Sitzungen oder anderen Programmläufen, wieder zu benutzen. Aber jetzt wollen wir wieder seriös werden: Mit dem Modul pickle (dt. einlegen) lassen sich Objekte serialisiert abspeichern, und zwar so, dass sie später wieder deserialisiert werden können. Dazu dient die Methode dump, die in ihrer allgemeinen Syntax wie folgt aussieht: dump(obj, file, protocol=None, *, fix_imports=True) -> None dump() schreibt eine „gepickelte” Darstellung des Objekts „obj” in das Dateiobjekt file. Das optionale Argument für das Protokollargument „protocol” steuert die Art der Ausgabe: Protokollversion Beschreibung 0 ist die ursprüngliche Ablageart des Pickle-Moduls, d.h. vor Python 3. Dabei handelt es sich um ein für Menschen lesbares Format. Dieser Modus ist abwärts kompatibel mit früheren Python-Versionen. 1 benutzt das alte Binärformat. Dieser Modus ist ebenfalls abwärts kompatibel mit früheren Python-Versionen. 2 wurde mit 2.3. eingeführt. Es stellt ein effizienteres „Pickling” zur Verfügung. 3 wurde speziell für Python 3.0 entwickelt. Dateien, die in diesem Modus erzeugt werden, können nicht mehr in Python 2.x mit dem zugehörigen PickleModul bearbeitet werden. Es handelt sich hierbei um den von Python 3.x empfohlenen Modus. Er stellt auch den Default-Wert dar. Wird das Argument „fix_imports” auf True gesetzt und hat „protocol” einen Wert kleiner als 3, wird pickle versuchen, die neuen Python 3.x-Namen auf die alten Modulnamen zu setzen, sodass der Pickle-Datenstrom mit Python 2.x lesbar ist. Im folgenden Beispiel schreiben wir eine Liste in eine Pickle-Datei: >>> >>> >>> >>> >>> import pickle cities = ["Bern", "Basel","St. Gallen", "Zürich"] fh = open("data.pkl","wb") pickle.dump(cities,fh) fh.close() Es stellt sich die Frage, welche Art von Daten man eigentlich pickeln kann? Im Prinzip alles, was man sich vorstellen kann: ■ Alle Python-Basistypen wie Booleans, Integers, Floats, komplexe Zahlen, Strings, ByteArrays und Byte-Objekte und natürlich None. 35.3 Ein persistentes Dictionary mit shelve ■ Listen und Tupels, Mengen und Dictionaries ■ Beliebige Kombinationen der beiden vorigen Gruppen ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ Außerdem können auch Funktionen, Klassen und Instanzen unter gewissen Bedingungen gepickelt werden. file-Objekte lassen sich nicht pickeln. 35.2.2 pickle.load Objekte, die mit pickle.dump() in eine Datei geschrieben worden sind, können mit der Pickle-Methode pickle.load(file) wieder eingelesen werden. pickle.load erkennt automatisch, in welchem Format eine Datei erstellt worden ist. Im Folgenden zeigen wir, wie wir die eben geschriebene Liste in einer anderen Sitzung wieder einlesen können: $ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import pickle >>> f = open("data.pkl","rb") >>> staedte = pickle.load(f) >>> print(staedte) ['Bern', 'Basel', 'St. Gallen', 'Zürich'] 35.3 shelve-Modul Hat man das shelve-Modul importiert und beginnt die englische Beschreibung unter help(shelve) zu lesen, wundert man sich vielleicht über die Rechtschreibung. Dort steht: „A ’shelf’ is a persistent, dictionary-like object.” Wir haben also ein Modul, was sich „shelve” nennt und ein dictionaryähnliches Objekt, was „shelf” geschrieben wird. Es handelt sich nicht um einen Verschreiber. Das Wort „shelf” ist ein Substantiv und bedeutet „Regal” oder „Ablage”, während „shelve” als Verb benutzt wird und meistens bedeutet, dass man etwas in ein Regal stellt. Nach dieser kurzen englischen Grammatikauffrischung sind wir auch schon der Aufgabe des shelve-Moduls näher gekommen. shelve funktioniert wie ein Bücherregal, nur dass wir statt „Büchern” Daten haben und unser Regal eine Datei ist. Prinzipiell ver- Bild 35.2 Bücherregal hält sich ein shelf-Objekt wie ein Python-Dictionary. Allerdings gibt es zwei wesentliche Unterschiede. Bevor man ein shelf benutzen kann, muss man es öffnen, d.h. man öffnet die Datei, und nach der Benutzung muss man die Datei wieder schließen. Außerdem gibt es eine Einschränkung bzgl. der Schlüssel, die man 383 384 35 Daten konservieren verwenden kann. Während ein Python-Dictionary beliebige unveränderliche Datentypen als Keys verwenden kann, also beispielsweise Integer, Floats, Strings und Tupel, kann ein shelf nur Strings als Keys verwenden. Dies ist allerdings nur eine geringe Beschränkung der Nutzbarkeit. Für die Werte gibt es hingegen keine Einschränkungen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Das Shelve-Modul lässt sich sehr einfach benutzen. Man importiert zuerst das Modul selbst, und dann öffnet man eine Datei mit dem zu verwendenden Shelf-Objekt unter Verwendung der shelve-Methode „open”: >>> import shelve >>> s = shelve.open("MyShelve") Falls bereits eine Datei „MyShelve” existiert, versucht die open-Methode, diese zu öffnen. Falls es sich um eine Datei handelt, die nicht unter Verwendung des shelve-Moduls angelegt worden ist, erfolgt eine Fehlermeldung, da es sich dann nicht um Daten im korrekten Format handelt. Falls die Datei noch nicht existiert, wird eine Datei im korrekten Format angelegt. Jetzt kann man mit s arbeiten wie mit einem Dictionary: >>> s["street"] = "Fleet Str" >>> s["city"] = "London" >>> for key in s: ... print(key) ... city street Bevor man das Programm verlässt, in dem man mit einem shelf-Objekt arbeitet, sollte man das shelf-Objekt wieder schließen. >>> s.close() Wir können nun die Shelf-Datei in einem beliebigen Python-Programm oder in der interaktiven Shell wieder benutzen, und die alten Daten stehen sofort zur Verfügung: $ python3 Python 3.2.3 (default, Feb 28 2014, 00:22:33) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import shelve >>> s = shelve.open("MyShelve") >>> s["street"] 'Fleet Str' >>> s["city"] 'London' >>> 35.3 Ein persistentes Dictionary mit shelve Man kann ein shelve-Objekt auch einfach mit dem dict-Casting-Operator in ein Dictionary wandeln: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> s <shelve.DbfilenameShelf object at 0xb7133dcc> >>> dict(s) {'city': 'London', 'street': 'Fleet Str'} >>> Im folgenden Beispiel demonstrieren wir, dass wir in einem Shelf-Eintrag auch komplexe Objekte wie beispielsweise Dictionaries abspeichern können: >>> >>> >>> >>> import shelve tele = shelve.open("MyPhoneBook") tele["Mike"] = {"first":"Mike", "last":"Miller", "phone":"4689"} tele["Steve"] = {"first":"Stephan", "last":"Burns", "phone ":"8745"} >>> tele["Eve"] = {"first":"Eve", "last":"Naomi", "phone":"9069"} >>> tele["Eve"]["phone"] '9069' Die Daten sind auch in diesem Fall persistent gespeichert, d.h. man kann sie auch wieder in einem späteren Programmlauf benutzen. Dafür braucht man nur die Datei MyPhoneBook zu öffnen: $ python3 Python 3.2.3 (default, Feb 28 2014, 00:22:33) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import shelve >>> tele = shelve.open("MyPhoneBook") >>> tele["Steve"]["phone"] '8745' >>> 385 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 36 36.1 Reguläre Ausdrücke Ursprünge und Verbreitung In diesem Abschnitt präsentieren wir eine detaillierte und anschauliche Einführung in die regulären Ausdrücke ganz allgemein, aber vor allen Dingen auch unter Python 3. Der Begriff „regulärer Ausdruck” kommt aus der Automatentheorie und der Theorie der formalen Sprachen, zwei Gebieten der theoretischen Informatik. Als Erfinder der regulären Ausdrücke gilt der amerika- Bild 36.1 Reguläre Ausdrücke nische Mathematiker Stephen Cole Kleene, der in den 1950er Jahren reguläre Mengen einführte. In der theoretischen Informatik dienen reguläre Ausdrücke zur formalen Definition einer Sprachfamilie mit bestimmten Eigenschaften, die sogenannten regulären Sprachen. Zu jedem regulären Ausdruck existiert ein endlicher Automat, der die von dem Ausdruck spezifizierte Sprache akzeptiert. In Programmiersprachen werden reguläre Ausdrücke meist zur Filterung von Texten oder Textstrings genutzt, d.h. sie erlauben einem zu prüfen, ob ein Text oder ein String zu einem RA „matcht”, d.h. zutrifft, übereinstimmt oder passt. RA finden auch Verwendung, um Textersetzungen durchzuführen, die recht komplex sein können. Einen äußerst interessanten Aspekt von regulären Ausdrücken möchten wir nicht unerwähnt lassen: Die Syntax der regulären Ausdrücke ist in allen Programmiersprachen und Skriptsprachen gleich, also z.B. in Python, Perl, Java, SED oder AWK. Außerdem werden sie von vielen Texteditoren wie zum Beispiel dem vi benutzt. 36.2 Stringvergleiche Ein einfacher Sonderfall von regulären Ausdrücken stellt die Suche nach einem String in einem anderen String dar. Dazu kann man statt regulärer Ausdrücke natürlich auch den 388 36 Reguläre Ausdrücke „in”-Operator benutzen, den wir im Abschnitt über sequentielle Datentypen bereits kennengelernt haben. >>> s = "Reguläre Ausdrücke einfach erklärt!" >>> "einfach" in s True >>> Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im obigen Beispiel wurde geprüft, ob das Wort „einfach” im String s vorkommt. Im Folgenden zeigen wir Schritt für Schritt, wie die String-Vergleiche durchgeführt werden. In dem String s = "xaababcbcd" soll geprüft werden, ob der Substring sub = "abc" vorkommt. Übrigens handelt es sich bei einem Substring bereits um einen regulären Ausdruck, wenn auch einen besonders einfachen. Zuerst wird geprüft, ob die ersten Positionen übereinstimmen, also s[0] == sub[0]. Dies ist in unserem Beispiel nicht erfüllt, was wir durch die Farbe Rot kenntlich machen: Nun wird geprüft, ob gilt s[1:4] == sub. Dazu wird erst geprüft, ob sub[0] == s[1] erfüllt ist. Dies gilt, was wir mit der Farbe Grün kenntlich machen. Dann wird weiter verglichen, aber s[2] ist ungleich sub[1]: Nun vergleichen wir s[2:5] mit sub. In diesem Fall stimmen sogar die ersten beiden Positionen des Substrings überein. Für den nächsten Schritte benötigen wir wohl keine Erklärung mehr: 36.3 Überlappungen und Teilstrings Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im nächsten Schritt kann eine vollständige Übereinstimmung gefunden werden, denn es gilt s[4:7] == sub 36.3 Überlappungen und Teilstrings Mit regulären Ausdrücken können prinzipiell zwei Aufgaben bewerkstelligt werden: 1. Überlappungen: Feststellen, ob sich ein String vollständig durch einen regulären Ausdruck überlappen lässt. Zum Beispiel werden in Kontaktformularen im Internet reguläre Ausdrücke dazu verwendet, die Gültigkeit von E-Mail-Adressen oder Postleitzahlen zu überprüfen. 2. Teilstringsuche: In einem String wird geprüft, ob es einen Teilstring gibt, der zu einem gegebenen regulären Ausdruck passt. 36.4 Das re-Modul Reguläre Ausdrücke gehören nicht zum Grundumfang von Python, d.h. sie sind nicht built in. Um in Python mit regulären Ausdrücken arbeiten zu können, muss man erst das Modul re importieren. re ist eine Standardbibliothek, die zahlreiche Funktionen und Methoden zum Arbeiten mit regulären Ausdrücken zur Verfügung stellt. Beginnen wir mit der wohl wichtigsten Funktion: search Syntax: re.search(pattern, string, flags=0) Mit dieser Funktion wird ein String „string” durchsucht, ob der reguläre Ausdruck „pattern” darin vorkommt. Gibt es eine Übereinstimmung, d.h. also einen Teilstring in „string”, auf den der reguläre Ausdruck „pattern” passt, dann gibt die Funktion search ein MatchObject zurück. Falls „pattern” nicht passt, wird None zurückgegeben. Ein regulärer Ausdruck entspricht einem String in Python. Es empfiehlt sich aber, am besten immer einen raw-String zu verwenden, da es sonst häufig zu Problemen kommt, wenn man einen Rückwärtsschrägstrich (Backslash) verwendet. Das Problem liegt darin, dass Python Rückwärtsschrägstriche bereits auswertet, bevor es einen String an das re-Modul übergibt. „\b” wird in regulären Ausdrücken dazu verwendet, Wortgrenzen zu bezeichnen. Benutzt man „\b” aber in einem String, wird er von Python als Backspace (also Rückwärtsschritt) interpretiert. Man sieht dies am besten im folgenden Beispiel: 389 390 36 Reguläre Ausdrücke Der reguläre Ausdruck „\b(\w+).*\b\1\b” wird Ihnen zum jetzigen Zeitpunkt noch wie Hieroglyphen vorkommen, aber er dient dazu, in einem String festzustellen, ob ein Wort mehr als einmal vorkommt. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Benutzt man in search einen normalen String statt eines Raw-Strings, funktioniert es nicht: >>> import re >>> t = "Manchmal ist ein Wort doch kein Wort." >>> x = re.search("\b(\w+).*\b\1\b", t) >>> print(x) None >>> x = re.search(r"\b(\w+).*\b\1\b", t) >>> print(x) <_sre.SRE_Match object at 0x8e89220> Das liegt daran, dass Python, bevor es einen String an search übergibt, diesen interpretiert, d.h. Escape-Sequenzen werden aufgelöst. „\b” steht zum Beispiel für einen Backspace (Rückwärtsschritt): >>> s = "abc\bde\bf" >>> print(s) abdf Die Buchstaben „c” und „e” wurden gewissermaßen aus dem String gelöscht. So werden auch alle Zeichen vor einem „\b” aus einem regulären Ausdruck gelöscht, bevor dieser an re.search übergeben wird. Dies geschieht aber nicht, wenn wir einen Raw-String verwenden. Ohne Raw-Strings müsste man jeden Backslash mit einem Backslash versehen, um die Sequenz, also z.B. „\b”, vor einer Interpretation durch den Python-Interpreter zu schützen1 : >>> s = "abc\\bde\\bf" >>> print(s) abc\bde\bf 36.5 Matching-Problem Bevor wir mit der Besprechung der Syntax der regulären Ausdrücke beginnen, wollen wir noch auf ein allgemeines Problem bei regulären Ausdrücken zu sprechen kommen. Wenn Sie wollen, können Sie diesen Abschnitt momentan überspringen, um ihn später durchzuarbeiten. Jeder String – also Strings ohne spezielle Zeichen mit Sonderbedeutungen – ist bereits ein regulärer Ausdruck. So ist beispielsweise r"cat" ein regulärer Ausdruck. Er gehört zu den wohl am einfachsten zu verstehenden Ausdrücken, denn er enthält keinerlei Metazeichen (Funktionszeichen) mit Sonderbedeutungen. Unser Beispielausdruck r"cat" matcht beispielsweise die folgende Zeichenkette: „A cat and a rat can’t be friends.” 1 englisch: escape Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 36.5 Matching-Problem Interessanterweise zeigt sich schon in diesem ersten Beispiel ein „beliebter” Fehler. Eigentlich will man Strings matchen, in denen das Wort cat vorkommt. Dies gelingt auch, aber man erhält auch beispielsweise „cats”, was möglicherweise noch erwünscht ist. Schlimmer sind jedoch eine ganze Menge zusätzlicher Wörter, in denen die Buchstabenfolge „cat” als Teilstring vorkommt, also auch Wörter wie „education”, „communicate”, „falsification”, „ramifications”, „cattle” und viele andere. Dies ist ein Fall von „over matching”, d.h. wir erhalten positive Ergebnisse, die nicht gewünscht sind. Wir haben dies im nebenstehenden Diagramm mengenmäßig veranschaulicht. Der dunkelgrüne Kreis C entspricht der Menge, die wir gerne erkennen wollen, aber wir erkennen die Menge O (blauer Kreis). C ist eine Teilmenge von O. In die- Bild 36.2 „under” und „over” sem Diagramm erkennt man auch eine Menge U (hellgrüner matching Kreis), die eine Teilmenge unserer gewünschten Menge C ist. Dies ist ein Fall von „under matching”. Ein Beispiel dafür erhalten wir, wenn wir versuchen, unseren regulären Ausdruck zu verbessern. Wir könnten auf die Idee kommen, vor und hinter dem Wort cat im regulären Ausdruck ein Leerzeichen einzufügen, also r" cat ". Durch diese Änderung würden wir nicht mehr auf Wörter wie „education”, „falsification” und „ramification” hereinfallen. Wie sieht es aber mit einem String "The cat, called Oscar, climbed on the roof." aus? Er wird nun als nicht mehr passend eingestuft. Diese Problematik wird im Folgenden noch klarer. Zunächst wollen wir uns jedoch mal anschauen, wie man reguläre Ausdrücke in Python überprüfen kann. Dann sind wir auch in der Lage, die folgenden Beispiele direkt in Python nachzuvollziehen. >>> import re >>> x = re.search("cat","A cat and a rat can't be friends.") >>> print(x) <_sre.SRE_Match object at 0x7fd4bf238238> >>> x = re.search("cow","A cat and a rat can't be friends.") >>> print(x) None Im vorigen Beispiel haben wir die Methode search aus dem re-Modul verwendet. Es ist wohl die am wichtigsten und am häufigsten benutzte Methode. Mittels search(expr,s) wird ein String s nach dem Vorkommen eines Teilstrings untersucht, der auf den regulären Ausdruck expr passt. Der erste gefundene Teilstring wird zurückgeliefert. Wenn wir das Ergebnis ausdrucken, sehen wir, dass im positiven Fall ein sogenanntes Match-Objekt zurückgegeben wird, während im negativen Fall ein „None” zurückgegeben wird. Mit diesem Wissen kann man reguläre Ausdrücke bereits in einem Python-Skript nutzen, ohne Näheres über die Match-Objekte zu wissen: >>> if re.search("cat","A cat and a rat can't be friends."): ... print("Der Ausdruck hat gepasst") ... else: ... print("Der Ausdruck hat nicht gepasst") ... Der Ausdruck hat gepasst 391 392 36 Reguläre Ausdrücke Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> if re.search("cow","A cat and a rat can't be friends."): ... print("Der Ausdruck hat gepasst") ... else: ... print("Der Ausdruck hat nicht gepasst") ... Der Ausdruck hat nicht gepasst 36.6 36.6.1 Syntax der regulären Ausdrücke Beliebiges Zeichen Nehmen wir an, dass wir im vorigen Beispiel nicht daran interessiert waren, das Wort cat zu finden, sondern dreibuchstabige Wörter, die mit „at” enden: Die regulären Ausdrücke bieten einen Metacharacter „.”, der als Platzhalter für ein beliebiges Zeichen steht. Den regulären Ausdruck könnte man so formulieren: r" .at " Der reguläre Ausdruck matcht nun durch Leerzeichen isolierte dreibuchstabige Wörter, die mit „at” enden. Dieser Ausdruck passt nun auch auf Wörter wie „rat”, „cat”, „bat”, „eat”, „sat” und andere. Aber was ist, wenn im Text ein „@at” oder „3at” stünde? Die matchen dann auch, und wir haben wieder ein „over matching” . Eine Lösung, um dies zu umgehen, lernen wir im nun folgenden Unterabschnitt unserer Einführung kennen. Im Folgenden sehen wir obigen regulären Ausdruck in einer Anwendung: >>> import re >>> for i in cat_and_more: ... if re.search(r".at", i): ... print("matched") ... else: ... print("no match") ... matched matched matched matched no match no match >>> 36.7 Zeichenauswahl 36.7 Zeichenauswahl Durch eckige Klammern „[” und „]” können wir eine Zeichenauswahl definieren. Der Ausdruck in eckigen Klammern steht dann für genau ein Zeichen aus dieser Auswahl. Betrachten wir den folgenden regulären Ausdruck: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. r"M[ae][iy]er" Dieser Ausdruck passt auf vier verschiedene Schreibweisen des häufigen deutschen Familiennamens. Dem großen M kann ein kleines „a” oder ein kleines „e” folgen, dann muss ein „i” oder „y” folgen, zum Abschluss dann „er”. Statt einzelner Buchstaben – wie im vorigen Beispiel die Auswahl zwischen „e” oder „a” (in RE-Notation [ea]) – benötigt man sehr häufig die Auswahl zwischen ganzen Zeichenklassen, also zum Beispiel eine Ziffer zwischen „0” und „5” oder ein Buchstabe zwischen „a” und „e”. Dafür gibt es bei der Notation für reguläre Ausdrücke ein reserviertes Sonderzeichen innerhalb der Zeichenauswahl, nämlich den Bindestrich „-”. [a-e] ist eine abgekürzte Schreibweise für [abcde] oder [0-5] steht für [012345]. Der Vorteil beim Schreiben wird sofort ersichtlich, wenn man die Zeichenauswahl „ein beliebiger Großbuchstabe” notieren will. Man kann [ABCDEFGHIJKLMNOPQRSTUVWXYZ] oder [A-Z] schreiben. Wen das noch nicht überzeugt: Wie sieht es mit der Zeichenauswahl „ein beliebiger Klein- oder Großbuchstabe” aus? [A-Za-z] Die umständliche Alternative überlassen wir der geneigten Leserin oder dem geneigten Leser :-) Aber es gibt noch eine Besonderheit mit dem Bindestrich, den wir benutzt hatten, um den Anfang und das Ende einer Zeichenklasse zu markieren. Der Bindestrich hat nur eine Sonderbedeutung, wenn er innerhalb von eckigen Klammern steht, und auch dann nur, wenn er nicht unmittelbar nach der öffnenden eckigen Klammer oder vor der schließenden eckigen Klammer positioniert ist. So bezeichnet der Ausdruck [-az nur die Auswahl zwischen den drei Zeichen „-”, „a” und „z”, aber keine anderen Zeichen. Das Gleiche gilt für [az-]. Frage: Welche Zeichenklasse wird durch [-a-z] beschrieben? Antwort: Das Zeichen „-”, weil es am Anfang direkt nach der öffnenden Klammer steht, und alle Zeichen zwischen „a” bis „z”, also das ganze Alphabet der kleinen Buchstaben und der Bindestrich. Das einzige andere Metazeichen innerhalb von eckigen Klammern ist das Caret-Zeichen (auch Textcursor oder Einschaltungszeichen genannt). Wenn es direkt hinter der öffnenden eckigen Klammer positioniert ist, dann negiert es die Auswahl. [^0-9] bezeichnet die Auswahl „irgendein Zeichen, aber keine Ziffer”. Die Position des Caret-Zeichen innerhalb der eckigen Klammern ist entscheidend. Wenn es nicht als erstes Zeichen steht, dann hat es keine spezielle Bedeutung und bezeichnet nur sich selbst. [^abc] bedeutet alles außer „a”, „b” oder „c”. [a^bc] bedeutet entweder ein „a”, „b”, „c” oder ein „^”. 393 394 36 Reguläre Ausdrücke 36.8 Endliche Automaten Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Dieser Abschnitt ist nicht zum weiteren Verständnis für die praktische Anwendung von regulären Ausdrücken notwendig. Sie können ihn also getrost überspringen. Ein endlicher Automat wird manchmal auch als Zustandsmaschine oder Zustandsautomat bezeichnet.2 Bei einem endlichen Automaten handelt es sich um ein Modell, bestehend aus Zuständen, Zustandsübergängen und Aktionen. Man bezeichnet ihn als endlich, weil die Zahl der Zustände, die er annehmen kann, endlich ist. Wir wollen hier nur ein Beispiel liefern: einen endlichen Automaten zum Akzeptieren der Meyer/Meier/Mayer/MaierVarianten. Vereinfachung im Diagramm: Eigentlich müsste es im Startknoten einen Zeiger geben, der wieder auf den Startknoten zurückzeigt. Das heißt, man bleibt solange Bild 36.3 Endlicher Automat auf dem Startknoten, wie man Zeichen liest, die von „M” verschieden sind. Von allen anderen Knoten müsste es auch einen Pfeil zum Startknoten zurück geben, wenn man kein Zeichen liest, was auf den ausgehenden Pfeilen vorhanden ist. Wie bereits eingangs gesagt, kann man das eben über endliche Automaten Gesagte getrost ignorieren, wenn man es nicht versteht. Es ist nicht wesentlich für die Anwendung von regulären Ausdrücken. 36.9 Anfang und Ende eines Strings Wie wir bereits ausgeführt hatten, ist der Ausdruck r"M[ae][iy]er" in der Lage, verschiedene Schreibweisen des Namen Meyer zu matchen. Dabei spielt es keine Rolle, ob sich der Name am Anfang, im Inneren oder am Ende des Strings befindet. >>> import re >>> line = "He is a German called Mayer." >>> if re.search(r"M[ae][iy]er",line): ... print("I found one!") ... I found one! >>> Aber wie sieht es aus, wenn wir nur Vorkommen des Namens direkt am Anfang eines Strings suchen, d.h. dass der String unmittelbar mit dem „M” des Namens beginnt? Das re-Modul von Python stellt zwei Funktionen zum Matchen von regulären Ausdrücken zur 2 englisch: finite state machine (FSM) 36.9 Anfang und Ende eines Strings Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Verfügung. Eine der beiden haben wir bereits kennengelernt, d.h. die Funktion search(). Die andere Funktion hat unserer Meinung nach einen irreführenden Namen, denn sie heißt match(). Irreführend deshalb, weil match(re_str, s) nur prüft, ob eine Übereinstimmung am Anfang des Strings vorliegt. Eigentlich würde man erwarten, dass match eine komplette Überlappung des regulären Ausdrucks mit dem String s überprüft. Aber egal wie, match() ist eine Lösung auf unsere Fragestellung, wie wir im folgenden Beispiel sehen: >>> import re >>> s1 = "Mayer is a very common name." >>> s2 = "He is called Meyer but he isn't German." >>> print(re.search(r"M[ae][iy]er", s1)) <_sre.SRE_Match object at 0xb724a0c8> >>> print(re.search(r"M[ae][iy]er", s2)) <_sre.SRE_Match object at 0xb724a0c8> >>> print(re.match(r"M[ae][iy]er", s1)) <_sre.SRE_Match object at 0xb724a0c8> >>> print(re.match(r"M[ae][iy]er", s2)) None >>> Auf diese Art können wir zwar den Anfang eines Strings matchen, aber diese Methode funktioniert nur in Python. Glücklicherweise stellt die Syntax der regulären Ausdrücke eine elegantere und allgemeingültige Möglichkeit zur Verfügung. Das Zeichen „^” (Textcursor, Einfügezeichen) stellt sicher, dass der nachfolgende reguläre Ausdruck nur direkt auf den Anfang des Strings angewendet wird, d.h. der reguläre Ausdruck mit einem führenden „^” muss also auf den Anfang des Strings passen. Außer im MULTILINE-Modus, dann kann der Ausdruck immer auf ein Newline-Zeichen folgen. >>> import re >>> s1 = "Mayer is a very common name." >>> s2 = "He is called Meyer but he isn't German." >>> print(re.search(r"^M[ae][iy]er", s1)) <_sre.SRE_Match object at 0x7fc59c5f26b0> >>> print(re.search(r"^M[ae][iy]er", s2)) None Aber was passiert, wenn wir die beiden Strings s1 und s2 auf nachfolgende Art zusammenfügen? s = s2 + "\n" + s1 Der String beginnt nicht mit einem Maier, egal in welcher Schreibweise: >>> s = s2 + "\n" + s1 >>> print(re.search(r"^M[ae][iy]er", s)) None >>> Der Ausdruck konnte nicht matchen. Aber der Name kommt nach einem Newline-Zeichen vor. Deshalb ändert sich das Ergebnis, wenn wir den MULTILINE-Modus zuschalten: 395 396 36 Reguläre Ausdrücke Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> s = s2 + "\n" + s1 >>> print(re.search(r"^M[ae][iy]er", s, re.MULTILINE)) <_sre.SRE_Match object at 0xb724a100> >>> print(re.search(r"^M[ae][iy]er", s, re.M)) <_sre.SRE_Match object at 0xb724a100> >>> print(re.match(r"^M[ae][iy]er", s, re.M)) None >>> Das vorige Beispiel zeigt auch, dass der Multiline-Modus keinen Einfluss auf die matchMethode hat. match() prüft nie etwas anderes als den Anfang des Strings unabhängig davon, ob man sich im Multiline-Modus befindet oder nicht. Damit haben wir die Prüfung für den Anfang eines Strings erledigt. Die Prüfung, ob ein regulärer Ausdruck auf das Ende eines Strings passt, sieht ähnlich aus. Dazu erhält das „$”-Zeichen eine Sonderbedeutung. Wird ein regulärer Ausdruck von einem „$”-Zeichen gefolgt, dann muss der Ausdruck auf das Ende des Strings passen, d.h. es darf kein weiteres Zeichen zwischen dem regulären Ausdruck und dem Newline des Strings stehen. Wir demonstrieren dies im folgenden Beispiel: >>> print(re.search(r"Python\.$","I like <_sre.SRE_Match object at 0xb724a138> >>> print(re.search(r"Python\.$","I like None >>> print(re.search(r"Python\.$","I like Perl.")) None >>> print(re.search(r"Python\.$","I like Perl.", re.M)) <_sre.SRE_Match object at 0xb724a138> >>> Python.")) Python and Perl.")) Python.\nSome prefer Java or Python.\nSome prefer Java or Die Metazeichen „^” und „$” verhalten sich anders als die bisher von uns verwendeten Zeichenliterale und Zeichenmengen. Wenn wir beispielsweise nach dem regulären Ausdruck r"c.t" in dem String "A cat and a rat can’t be friends." suchen, dann wird für jedes Zeichen des regulären Ausdrucks nach einem passenden Zeichen in dem String gesucht. >>> import re >>> s = "A cat and a rat can't be friends." >>> x = re.search(r"c.t", s) >>> x.group() 'cat' >>> Bei den eben besprochenen Metazeichen zur Erkennung der Stringbegrenzungen, also Anfang und Ende, wird aber nicht nach einem Zeichen, sondern nach einer Position in einem String gesucht. Man bezeichnet die Zeichen „^” und „$” deshalb auch als Ankerzeichen. Ankerzeichen können vor, zwischen oder nach Zeichen stehen. Sie dienen dazu, den Match 36.10 Vordefinierte Zeichenklassen an bestimmten Positionen zu verankern. So „verankert” das „^” einen regulären Ausdruck an den Anfang des Strings und das „$” entsprechend an das Ende. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 36.10 Vordefinierte Zeichenklassen Im Prinzip kann es sehr mühsam werden, bestimmte Zeichenklassen auf die bisherige Art und Weise zu konstruieren. Ein gutes Beispiel hierfür ist sicherlich die Zeichenklasse, die einen gültigen Wortbuchstaben definiert. Dies sind alle Klein- und Großbuchstaben, alle Ziffern und der Unterstrich „_”. Das entspricht der folgenden Zeichenklasse r"[a-zA-Z0-9_]" Deshalb gibt es für häufig vorkommende Zeichenklassen vordefinierte Kürzel: Die vordefinierten Zeichenklassen \b und \B der vorigen Übersicht werden häufig nicht richtig oder gar falsch verstanden. Während die anderen Klassen einzelne Zeichen matchen (so matcht beispielsweise \w unter anderem „a”, „b”, „m”, „3” usw.), matchen sie aber keine Zeichen. Bei diesen Metazeichen handelt es sich ebenBild 36.4 Wortbegrenzer so wie bei dem „^”- und dem „$”-Zeichen um Ankerzeichen. Sie dienen dazu, Wortgrenzen zu markieren. Sie matchen Positionen in Abhängigkeit von deren Nachbarschaft, d.h. es hängt davon ab, welches Zeichen vor und nach der Position steht. \b passt zum Beispiel auf die Position zwischen einem \W und eiTabelle 36.1 Vordefinierte Zeichenklassen Kürzel Bedeutung \d Eine Ziffer, entspricht [0-9]. \D das Komplement von \d. Also alle Zeichen außer den Ziffern, entspricht der Klassennotation [^0-9]. \s Ein Whitespace, also Leerzeichen, Tabs, Newlines usw., entspricht der Klasse [ \t \n \r \f \v ]. \S Das Komplement von \s. Also alles außer Whitespace, entspricht [^ \t \n \r \f \v ]. \w Alphanumerisches Zeichen plus Unterstrich, also [a-zA-Z0-9_]. Wenn die LOCALE gesetzt ist, matcht es auch noch die speziellen Zeichen der LOCALE, also z.B. die Umlaute. \W Das Komplement von \w. \b Passt auf den leeren String, aber nur, wenn dieser am Anfang oder Ende eines Strings ist. \B Passt wie \b auf den leeren String, aber nur, wenn dieser nicht am Anfang oder Ende eines Strings ist. \\ Ein Backslash. 397 398 36 Reguläre Ausdrücke Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. nem \w oder umgekehrt zwischen \w und \W. \B bezeichnet wie üblich das Komplement, das bedeutet, es werden Positionen zwischen \W und \W und Positionen zwischen \w und \w gematcht. Diesen Sachverhalt haben wir im nebenstehenden Diagramm veranschaulicht. Der Anker \b wird oft benutzt, um Wörter aus einem String herauszufiltern. Im nächsten Beispiel benutzen wir die Funktion findall, die wir erst im Detail im Abschnitt 36.15 besprechen werden. Prinzipiell ist findall aber einfach zu verstehen. findall liefert eine Liste aller mit dem regulären Ausdruck gematchten Teilstrings zurück. Im folgenden Beispiel3 matchen wir alle Wörter in einem String mittels \b: >>> import re >>> txt = "Weit draußen im Meere ist das Wasser so blau wie die Blütenblätter der schönsten Kornblume, und so klar wie das reinste Glas, aber es ist dort sehr tief, tiefer als irgendein Ankertau reicht, viele Kirchtürme müßten aufeinandergestellt werden, um vom Grunde bis über das Wasser zu reichen." >>> >>> re.findall(r"\b\w+\b",txt) ['Weit', 'draußen', 'im', 'Meere', 'ist', 'das', 'Wasser', 'so', 'blau ', 'wie', 'die', 'Blütenblätter', 'der', 'schönsten', 'Kornblume', 'und', 'so', 'klar', 'wie', 'das', 'reinste', 'Glas', 'aber', 'es', 'ist', 'dort', 'sehr', 'tief', 'tiefer', 'als', 'irgendein', 'Ankertau', 'reicht', 'viele', 'Kirchtürme', 'müßten', 'aufeinandergestellt', 'werden', 'um', 'vom', 'Grunde', 'bis', 'über', 'das', 'Wasser', 'zu', 'reichen'] >>> 36.11 Optionale Teile Falls Sie denken, dass wir bereits alle Schreibweisen der Namen Mayer und Co. erfasst hätten, dann irren Sie sich. Es gibt noch weitere Varianten in vielen anderen Schreibweisen. So gibt es insbesondere in Bayern die Variante, in der das „e” vor dem „r” „verloren” gegangen ist. Dadurch erhalten also noch vier weitere Schreibweisen: [’Mayr’, ’Meyr’, ’Meir’, ’Mair’] zusätzlich zu unserer alten Liste [’Mayer’, ’Meyer’, ’Meier’, ’Maier’]. Wenn wir nun versuchen, einen passenden regulären Ausdruck zu konstruieren, fällt uns auf, dass uns noch etwas fehlt: Wie können wir sagen: „e kann, aber muss nicht vorkommen”? Dafür hat man in der Syntax der regulären Ausdrücke dem Fragezeichen eine Sonderbedeutung verpasst. Der Ausdruck „e?” bedeutet gerade, was wir wollen, also „der Buchstabe e kann, aber muss nicht vorkommen”. Unser finaler Mayer-Erkenner sieht nun wie folgt aus: 3 Der Text entspricht dem ersten Satz des Märchens „Die kleine Meerjungfer” von Hans Christian Andersen 36.12 Quantoren r"M[ae][iy]e?r" Ein Fragezeichen kann auch hinter einer runden Klammer stehen. Dann bedeutet das, dass der komplette Unterausdruck innerhalb der Klammern vorkommen kann, aber nicht vorkommen muss. Mit dem folgenden Ausdruck können wir Teilstrings mit „Feb 2011” oder „February 2011” erkennen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. r"Feb(ruary)? 2011" 36.12 Quantoren Mit dem, was wir bisher an syntaktischen Mitteln kennengelernt haben, lassen sich bestimmte Eigenschaften nicht in regulären Ausdrücken abbilden. Beispielsweise benötigt man immer wieder Möglichkeiten darzustellen, dass man bestimmte Teilausdrücke wiederholen will. Eine Form von Wiederholung hatten wir gerade eben kennengelernt: das Fragezeichen. Ein Zeichen oder ein in runden Klammern eingeschlossener Teilausdruck wird entweder einmal oder keinmal „wiederholt”. Außerdem hatten wir zu Beginn dieser Einführung einen anderen Quantor kennengelernt, ohne dass wir in besonderer Weise auf ihn eingegangen sind. Es handelte sich um den Stern-Operator. Folgt ein Stern „*” einem Zeichen oder einem Teilausdruck, dann heißt dies, dass dieses Zeichen oder der Teilausdruck keinmal oder beliebig oft vorkommen oder wiederholt werden darf. r"[0-9]*" Der obige Ausdruck passt auf eine beliebige Folge von Ziffern, aber auch auf den leeren String. r".*" passt auf eine beliebige Folge von Zeichen und auf den leeren String. Übung: Schreiben Sie einen regulären Ausdruck, der Strings matcht, die mit einer Folge von Ziffern – wenigstens einer – beginnen und von einem Leerzeichen gefolgt werden. Lösung: r"[0-9][0-9]* .*" So, Sie haben also das Plus-Zeichen verwendet? Das ist super, aber in diesem Fall haben Sie wohl gemogelt, indem Sie weitergelesen haben, oder Sie wissen bereits mehr über reguläre Ausdrücke als das, was wir bisher in unserem Kurs behandelt haben ,. Also dann, wenn wir bereits beim Plus-Operator sind: Mit dem Plus-Operator kann man auf angenehme Art und Weise die vorige Übung lösen. Im Prinzip funktioniert der PlusOperator wie der Sternchen-Operator, nur dass der Plus-Operator wenigstens ein Vorkommen des Zeichens oder Teilausdrucks verlangt. Lösung unserer Aufgabe mit dem „+”-Operator: r"[0-9]+ .*" 399 400 36 Reguläre Ausdrücke Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Aber auch mit Plus- und Sternchen-Operator fehlt noch etwas Wichtiges: Wir wollen in bestimmten Situationen die exakte Anzahl der Wiederholungen oder eine minimale oder maximale Anzahl von Wiederholungen angeben können. Nehmen wir an, dass wir Adresszeilen von Briefumschlägen in der Schweiz lesen wollen. Also die Zeile, in der die Postleitzahl und der Ortsname steht, d.h. eine vierstellige Postleitzahl, gefolgt von einem Leerzeichen und dem Ortsnamen. + bzw. * sind zu unspezifisch für diesen Fall, und die folgende Lösung ist sicherlich zu umständlich: r"^[0-9][0-9][0-9][0-9] [A-Za-z]+" Glücklicherweise bietet die Syntax der regulären Ausdrücke eine optimale Lösung: r"^[0-9]{4} [A-Za-z]*" Nun wollen wir unseren regulären Ausdruck noch weiter verbessern. Nehmen wir an, dass es keine Stadt oder keinen Ort in der Schweiz gibt, deren Name aus weniger als drei Buchstaben besteht. Diesen Umstand können wir mit [A-Za-z][3,} beschreiben. Nun wollen wir auch noch Briefe mit erfassen, die nach Deutschland gehen. Postleitzahlen haben bekanntlich eine Stelle mehr in Deutschland. [0-9]{4,5} bedeutet, dass wir mindestens 4 Ziffern, aber höchstens 5 erwarten: r"^[0-9]{4,5} [A-Z][a-z]{2,}" Allgemein gilt: {min, max}: mindestens min-Mal und höchsten max-Mal. {, max} ist eine abgekürzte Schreibweise für {0,to}, und {min,} ist eine Abkürzung für „höchstens minMal, aber keine Beschränkung nach oben”. Ein praktisches Beispiel in Python Bevor wir fortfahren, möchten wir eine kleine praktische Übung mit regulären Ausdrücken in Python einschieben. Dazu haben wir ein Telefonbuch4 der Simpsons. Genau, DIE SIMPSONS, die aus der berühmten amerikanischen Serie. Allison Neu 555-8396 Bob Newhall 555-4344 C. Montgomery Burns 555-0001 C. Montgomery Burns 555-0113 Canine College 555-7201 Canine Therapy Institute 555-2849 Cathy Neu 555-2362 City of New York Parking Violation Bureau 555-BOOT Dr. Julius Hibbert 555-3642 Dr. Nick Riviera 555-NICK Earn Cash For Your Teeth 555-6312 Family Therapy Center 555-HUGS Homer Jay Simpson (Plow King episode) 555-3223 Homer Jay Simpson (work) 555-7334 Jack Neu 555-7666 4 Das gesamte Telefonbuch finden Sie in unserem Programm- und Beispielverzeichnis. 36.13 Gruppierungen und Rückwärtsreferenzen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Jeb Neu 555-5543 Jennifer Neu 555-3652 Ken Neu 555-8752 Lionel Putz 555-5299 In dieser Liste befinden sich Leute, die mit Nachnamen „Neu” heißen. Die selbst auferlegte Aufgabe besteht nun darin, diejenigen Leute zu finden, die den Namen Neu führen und deren Vorname mit einem „J” beginnt. Dazu schreiben wir ein Python-Skript, das diese Zeile einliest und Zeile für Zeile bearbeitet: import re fh = open("simpsons_phone_book.txt") for line in fh: if re.search(r"J.*Neu",line): print(line.rstrip()) fh.close() Startet man das Programm, erhält man folgende Ausgabe: $ python3 phone_numbers.py Jack Neu 555-7666 Jeb Neu 555-5543 Jennifer Neu 555-3652 36.13 Gruppierungen und Rückwärtsreferenzen Ausdrücke lassen sich, wie bereits erklärt, mit runden Klammern „(” und „)” zusammenfassen. Die gefundenen Übereinstimmungen der Gruppierungen werden von Python abgespeichert. Dadurch wird deren Wiederverwendung im gleichen regulären Ausdruck an späterer Stelle ermöglicht. Dies bezeichnet man als Rückwärtsreferenzen (engl. back references). \n (n = 1, 2, 3, ... ) bezeichnet die n-te Gruppierung. Bevor wir jedoch mit Rückwärtsreferenzen weitermachen, wollen wir noch einen Paragraphen über Match-Objekte einfügen, die wir im Folgenden benötigen. 36.13.1 Match-Objekte Bisher waren wir immer nur daran interessiert, ob ein Ausdruck gepasst hatte oder nicht. Wir nutzten die Tatsache, dass Python oder genauer die Methode re.search() ein MatchObjekt zurückliefert, wenn der reguläre Ausdruck gepasst hat, und ansonsten nur ein None. Uns interessierte bisher nicht, was gepasst hatte, also welcher Teilstring. Eine andere Information wäre, wo der Match im String stattfand, also die Start- und die Endposition. 401 402 36 Reguläre Ausdrücke Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Ein match-Objekt enthält unter allem die Methoden group(), span(), start() und end(), die man im folgenden Beispiel im selbsterklärenden Einsatz sieht: >>> import re >>> mo = re.search("[0-9]+", "Customer number: 232454, Date: February 12, 2013") >>> mo.group() '232454' >>> mo.span() (17, 23) >>> mo.start() 17 >>> mo.end() 23 >>> mo.span()[0] 17 >>> mo.span()[1] 23 Diese Methoden sind nicht schwierig zu verstehen: span() liefert ein 2er-Tupel zurück, das den Anfangs- und Endwert des Substrings enthält, auf den der reguläre Ausdruck passt. Für den Anfangs- und Endwert gibt es noch zwei Funktionen start() und end(), wobei gilt, dass span()[0] dem Wert von start() und span()[1] dem Wert von end() entspricht. Wird group() ohne Argumente aufgerufen, liefert es den Substring zurück, der auf den RE gepasst hat. Ruft man group mit einem Integer-Argument n auf, liefert es den Substring zurück, auf den die n-te Gruppe gepasst hatte. Man kann group() auch mit mehr als einem Integer-Wert aufrufen, z.B. group(n,m). Dann wird kein String zurückgeliefert, sondern ein Tupel mit den Werten von group(n) und group(m). Also es gilt (group(n),group(m)) ist gleich group(n,m): >>> import re >>> mo = re.search("([0-9]+).*: (.*)", "Customer number: 232454, Date: February 12, 2013") >>> mo.group() '232454, Date: February 12, 2013' >>> mo.group(1) '232454' >>> mo.group(2) 'February 12, 2013' >>> mo.group(1,2) ('232454', 'February 12, 2013') Ein sehr intuitives Beispiel stellt das Lesen von korrespondierenden schließenden Tags von XML oder HTML dar. In einer Datei (z.B. „tags.txt”) steht folgender Inhalt: <composer>Wolfgang Amadeus Mozart</composer> <author>Samuel Beckett</author> <city>London</city> 36.13 Gruppierungen und Rückwärtsreferenzen Wir möchten diesen Text automatisch in folgendes Format umschreiben: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. composer: Wolfgang Amadeus Mozart author: Samuel Beckett city: London Dies lässt sich mittels Python und regulären Ausdrücken mit folgendem Skript realisieren. Der reguläre Ausdruck funktioniert wie folgt: Er versucht erst, das Symbol „<” zu finden. Danach liest er eine Gruppe von Kleinbuchstaben, bis er auf das Größerzeichen „>” stößt. Alles, was zwischen „<” und „>” steht, wird in einer Rückwärtsreferenz (back reference) gespeichert, und zwar unter \1. Zuerst enthält \1 den Wert „composer”: Nachdem der Ausdruck das erste „>” erreicht hat, wird der reguläre Ausdruck so weiter ausgeführt, als hätte er von Anfang an „<composer>(.*)</composer>” gelautet. Das zugehörige Python-Skript: import re fh = open("tags.txt") for i in fh: res = re.search(r"<([a-z]+)>(.*)</\1>",i) print(res.group(1) + ": " + res.group(2)) Wenn es mehr als ein Klammerpaar (runde Klammern) innerhalb eines regulären Ausdrucks gibt, dann sind die Rückwärtsreferenzen in der Reihenfolge der Klammern durchnummeriert: \1, \2, \3, ... Übung: Im nächsten Beispiel werden drei Rückwärtsreferenzen benutzt. Gegeben ist eine Telefonliste der Simpsons. Nicht jeder Eintrag enthält eine Telefonnummer, aber wenn eine Telefonnummer existiert, dann steht sie am Anfang des Strings. Dann folgt, getrennt durch ein Leerzeichen, der Nachname. Durch ein Komma getrennt folgen dann Vornamen. Die Liste soll in der folgenden Form ausgegeben werden, d.h. zuerst der vollständige Name und dann die Telefonnummer: $ python3 simpsons_phone_book.py Allison Neu 555-8396 C. Montgomery Burns Lionel Putz 555-5299 Homer Jay Simpson 555-7334 Das folgende Python-Programm löst die Aufgabe: import re l = ["555-8396 Neu, Allison", "Burns, C. Montgomery", "555-5299 Putz, Lionel", "555-7334 Simpson, Homer Jay"] 403 404 36 Reguläre Ausdrücke for entry in l: res = re.search(r"([0-9-]*)\s*([A-Za-z]+),\s+(.*)", entry) print(res.group(3) + " " + res.group(2) + " " + res.group(1)) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 36.14 Umfangreiche Übung In dieser Übung geht es darum, die Informationen aus zwei Listen, d.h. Dateien, unter Benutzung von regulären Ausdrücken zusammenbringen. Die erste Datei beinhaltet über 13.000 Zeilen mit Postleitzahlen den zugehörigen Städtenamen sowie weiteren Informationen. Es folgen ein paar willkürlich ausgewählte Zeilen zur Verdeutlichung und zur Findung von regulären Ausdrücken: osm_id ort plz bundesland 1104550 Aach 78267 Baden-Württemberg ... 446465 Freiburg (Elbe) 21729 Niedersachsen 62768 Freiburg im Breisgau 79098 Baden-Württemberg 62768 Freiburg im Breisgau 79100 Baden-Württemberg 62768 Freiburg im Breisgau 79102 Baden-Württemberg ... 454863 Fulda 36037 Hessen 454863 Fulda 36039 Hessen 454863 Fulda 36041 Hessen ... 1451600 Gallin 19258 Mecklenburg-Vorpommern 449887 Gallin-Kuppentin 19386 Mecklenburg-Vorpommern ... 57082 Gärtringen 71116 Baden-Württemberg 1334113 Gartz (Oder) 16307 Brandenburg ... 2791802 Giengen an der Brenz 89522 Baden-Württemberg 2791802 Giengen an der Brenz 89537 Baden-Württemberg ... Die andere Datei enthält eine Liste der 19 größten deutschen Städte. Jede Zeile enthält die Position der Stadt, den Namen der Stadt, die Einwohnerzahl und das Bundesland. Was jedoch fehlt, ist die zugehörige Postleitzahl: 1. 2. 3. 4. 5. 6. 7. 8. Berlin 3.382.169 Berlin Hamburg 1.715.392 Hamburg München 1.210.223 Bayern Köln 962.884 Nordrhein-Westfalen Frankfurt am Main 646.550 Hessen Essen 595.243 Nordrhein-Westfalen Dortmund 588.994 Nordrhein-Westfalen Stuttgart 583.874 Baden-Württemberg Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 36.14 Umfangreiche Übung 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. Düsseldorf Bremen Hannover Duisburg Leipzig Nürnberg Dresden Bochum Wuppertal Bielefeld Mannheim 569.364 539.403 515.001 514.915 493.208 488.400 477.807 391.147 366.434 321.758 306.729 Nordrhein-Westfalen Bremen Niedersachsen Nordrhein-Westfalen Sachsen Bayern Sachsen Nordrhein-Westfalen Nordrhein-Westfalen Nordrhein-Westfalen Baden-Württemberg Die Aufgabe besteht nun darin, die 19 größten Städte zusammen mit ihren Postleitzahlen auszugeben. Die Dateien befinden sich in unserem Beispielverzeichnis unter den Namen largest_cities_germany.txt und zuordnung_plz_ort.txt. import re with open("zuordnung_plz_ort.txt", encoding="utf-8") as fh_post_codes: codes4city = {} for line in fh_post_codes: res = re.search(r"[\d ]+([^\d]+[a-z])\s(\d+)", line) if res: city, post_code = res.groups() if city in codes4city: codes4city[city].add(post_code) else: codes4city[city] = {post_code} with open("largest_cities_germany.txt", encoding="utf-8") as fh_largest_cities: for line in fh_largest_cities: re_obj = re.search(r"^[0-9]{1,2}\.\s+([\w\s-]+\w)\s+[0-9]", line) city = re_obj.group(1) print(city, codes4city[city]) Die Ausgabe für obiges Programm sieht wie folgt aus. Allerdings haben wir die Ausgabe gekürzt, d.h. wir haben alle außer den jeweils ersten drei Postleitzahlen pro Stadt rausgelöscht, um die Ausgabe klein zu halten: $ python3 largest_cities_postcode.py Berlin {'10715', '13158', '13187', ...} München {'80802', '80331', '80807', ...} Köln {'51065', '50997', '51067', ...} Frankfurt am Main {'65934', '60529', '60308', ...} Essen {'45144', '45134', '45309', ... } Dortmund {'44328', '44263', '44369',...} 405 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 406 36 Reguläre Ausdrücke Stuttgart {'70174', '70565', '70173', ...} Düsseldorf {'40217', '40589', '40472', ...} Bremen {'28207', '28717', '28777', ...} Hannover {'30169', '30419', '30451', ...} Duisburg {'47137', '47059', '47228', ...} Leipzig {'4158', '4329', '4349', ...'} Nürnberg {'90419', '90451', '90482', ...} Dresden {'1217', '1169', '1324', ...} Bochum {'44801', '44892', '44805', ...} Wuppertal {'42109', '42119', '42287', ...} Bielefeld {'33613', '33607', '33699', ...} Mannheim {'68161', '68169', '68167', ...} 36.15 findall Python oder besser das Modul re stellt noch eine weitere großartige Methode zur Verfügung, die sich Perl- und Java-Programmierer vergeblich wünschen: re.findall(pattern, str[, flags]) findall liefert alle Übereinstimmungen des regulären Ausdrucks „pattern” im String „str” als Liste zurück (zur Erinnerung: search() und match() liefern nur die erste Übereinstimmung zurück). Der String „str” wird von links nach rechts gescannt, und die Einträge der Liste entsprechen diesem Arbeitsablauf. >>> import re >>> t="A fat cat doesn't eat oat but a rat eats bats." >>> mo = re.findall("[force]at", t) >>> print(mo) ['fat', 'cat', 'eat', 'oat', 'rat', 'eat'] Falls eine oder mehrere Gruppen in dem regulären Ausdruck vorkommen, wird eine Liste mit Tupels der einzelnen Gruppenergebnisse zurückgeliefert: >>> import re >>> items = re.findall("[0-9]+.*: .*", "Customer number: 232454, Date: January 22, 2013") >>> print(items) ['232454, Date: January 22, 2013'] >>> items = re.findall("([0-9]+).*: (.*)", "Customer number: 232454, Date: January 22, 2013") >>> print(items) [('232454', 'January 22, 2013')] 36.16 Alternativen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 36.16 Alternativen Zu Beginn unseres Kapitels hatten wir Zeichenklassen eingeführt. Zeichenklassen bieten eine Auswahl aus einer Menge von Zeichen. Manchmal benötigt man etwas Analoges für verschiedene Teilausdrücke. Also die Wahl zwischen verschiedenen Ausdrücken. Dazu verwendet man das Symbol „|”, da es sich um ein logisches „Oder” handelt. Im folgenden Beispiel prüfen wir, ob in einem String die Städte London, Paris, Zürich oder Strasbourg vorkommen, und zwar nach einem vorausgegangenen Wort „destination”: >>> import re >>> str = "The destination is London!" >>> mo = re.search(r"destination.*(London|Paris|Zurich|Strasbourg)", str) >>> if mo: print(mo.group()) ... destination is London Wer das letzte Beispiel für zu künstlich und nicht praxisnah genug hält, für den oder die haben wir hier ein weiteres Beispiel. Nehmen wir an, wir wollen unsere E-Mails filtern. Wir möchten die gesamte Korrespondenz mit Guido van Rossum, dem Schöpfer und Designer von Python, finden. Der folgende reguläre Ausdruck dürfte dann recht hilfreich sein: r"(^To:|^From:) (Guido|van Rossum)" Dieser Ausdruck passt auf alle Zeilen, die entweder mit „To:” oder mit „From:” beginnen und von einem Leerzeichen gefolgt werden, denen dann entweder der Vorname „Guido” oder der Nachname „van Rossum” folgt. 36.17 Compilierung von regulären Ausdrücken Falls man denselben regulären Ausdruck mehrmals in seinem Skript verwenden will, ist es eine gute Idee, den Ausdruck zu compilieren. Die allgemeine Syntax der compile()Funktion: re.compile(patter[, flags]) compile lieferte ein regex-Objekt zurück, das man später zum Suchen und Ersetzen verwenden kann. Das Verhalten des Ausdrucks kann man mit Flag-Werten modifizieren. Schauen wir uns dies in einem Beispiel an: >>> import re >>> ce = re.compile(r"01+") >>> ce <_sre.SRE_Pattern object at 0xb72ac440> 407 408 36 Reguläre Ausdrücke Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Tabelle 36.2 Flags für compile Flag Beschreibung re.I, re.IGNORECASE Groß- und Kleinschreibung wird nicht mehr unterschieden. re.L, re.LOCALE Das Verhalten von bestimmten Zeichenklassen wie z.B. \w , \W , \b ,\s , \S wird von der eingestellten Sprachumgebung (locale) abhängig gemacht. re.M, re.MULTILINE ^ und $ passen defaultmäßig nur auf den Anfang und das Ende eines Strings. Mit diesem Flag passen sie auch im Innern eines Strings vor und nach einem Newline „\n”. re.S, re.DOTALL „.” passt dann auf alle Zeichen plus dem Newline „\n”. re.U, re.UNICODE \w , \W , \b , \B , \d , \D , \s , \S werden von re.X, re.VERBOSE Ermöglicht „wortreiche” (verbose) reguläre Ausdrücke, d.h. Leerzeichen werden ignoriert. Das bedeutet, dass Leerzeichen, Tabs, Wagenrücklauf „\c” usw. nicht als solche gematcht werden. Wenn man ein Leerzeichen im Verbose-Modus matchen will, muss man es mittels Backslash schützen (escape) oder es in eine Zeichenklasse packen. # wird auch ignoriert, außer wenn dieses Zeichen in einer Zeichenklasse oder hinter einem Backslash steht. Alles hinter einem „#” wird bis zum Ende einer Zeile als Kommentar ignoriert. Unicode-Einstellungen abhängig. Auf dem SRE_Pattern-Objekt ce kann man nun die Methode search anwenden, die nur einen String braucht, auf dem versucht wird, das zuvor compilierte Pattern zu matchen: >>> x = ce.search("Eine Binärzahl: 00111") >>> x <_sre.SRE_Match object at 0xb71af0c8> >>> x.span() (17, 21) >>> x.group() '0111' Das SRE_Pattern-Objekt kennt auch eine Methode findall5 , wie wir im folgenden Beispiel sehen: >>> import re >>> ce = re.compile(r"01+") >>> x = ce.search("Eine Binärzahl: 00111 und noch eine 01011") >>> x.findall() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: '_sre.SRE_Match' object has no attribute 'findall' >>> x = ce.findall("Eine Binärzahl: 00111 und noch eine 01011") >>> x ['0111', '01', '011'] 5 ebenso wie match, split sub und andere 36.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Compilierte reguläre Ausdrücke sparen in der Regel nicht viel Rechenzeit, weil Python sowieso automatisch reguläre Ausdrücke compiliert und zwischenspeichert, auch wenn man sie mit re.search() oder re.match() aufruft. Ein guter Grund für ihre Verwendung besteht darin, die Definition und die Benutzung von regulären Ausdrücken zu trennen. 36.18 36.18.1 Aufspalten eines Strings mit oder ohne regulären Ausdruck split-Methode der String-Klasse Es gibt eine String-Methode split(), mit deren Hilfe man einen String in Teilstrings aufspalten kann. str.split([sep[, maxsplit]]) Wenn das optionale Argument von split fehlt oder None ist, werden alle Teilstrings, die aus Leerräumen (Whitespaces) bestehen, als Separatoren benutzt. Den zweiten Parameter werden wir später besprechen. Bild 36.5 Splitting Strings Wir demonstrieren dieses Verhalten mit einem Zitat von Abraham Lincoln: >>> law_courses = "Let reverence for the laws be breathed by every American mother to the lisping babe that prattles on her lap. Let it be taught in schools, in seminaries, and in colleges. Let it be written in primers, spelling books, and in almanacs. Let it be preached from the pulpit, proclaimed in legislative halls, and enforced in the courts of justice. And, in short, let it become the political religion of the nation." >>> law_courses.split() ['Let', 'reverence', 'for', 'the', 'laws', 'be', 'breathed', 'by', ' every', 'American', 'mother', 'to', 'the', 'lisping', 'babe', ' that', 'prattles', 'on', 'her', 'lap.', 'Let', 'it', 'be', 'taught ', 'in', 'schools,', 'in', 'seminaries,', 'and', 'in', 'colleges .', 'Let', 'it', 'be', 'written', 'in', 'primers,', 'spelling', ' books,', 'and', 'in', 'almanacs.', 'Let', 'it', 'be', 'preached', 'from', 'the', 'pulpit,', 'proclaimed', 'in', 'legislative', ' halls,', 'and', 'enforced', 'in', 'the', 'courts', 'of', 'justice .', 'And,', 'in', 'short,', 'let', 'it', 'become', 'the', ' political', 'religion', 'of', 'the', 'nation.'] Wir schauen uns nun einen String an, der von seinem Aufbau von Excel oder von Calc (OpenOffice) kommen könnte. In unserem vorigen Beispiel hatten wir gesehen, dass standardmäßig Leerräume (Whitespaces) als Separatoren genommen werden. Im folgenden kleinen Beispiel wollen wir das Semikolon als Separator nehmen. Dazu müssen wir lediglich „;” als Argument beim Aufruf übergeben: 409 410 36 Reguläre Ausdrücke >>> line = "James;Miller;teacher;Perl" >>> line.split(";") ['James', 'Miller', 'teacher', 'Perl'] Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wie bereits erwähnt, hat die Methode split() noch einen weiteren optionalen Parameter: maxsplit. Falls maxsplit angegeben wird, dann wird der String an maximal „maxsplit” Separatoren zerlegt, d.h. die Ergebnisliste besteht aus höchstens „maxsplit + 1” Elementen. Wir demonstrieren die Wirkungsweise von maxsplit mit einer Definition des Begriffs Mammon aus dem „Devil’s Dictionary” von Ambrose Bierce: >>> mammon = "The god of the world's leading religion. The chief temple is in the holy city of New York." >>> mammon.split(" ",3) ['The', 'god', 'of', "the world's leading religion. The chief temple is in the holy city of New York."] Wir benutzten im vorigen Beispiel ein Leerzeichen als Separator, was ein Problem darstellen kann, wenn mehrere Leerzeichen (irgendwelche Leerzeichen, also auch Tabs) hintereinander stehen. In diesem Fall wird split() nach jedem Leerzeichen den String aufsplitten, und dadurch erhalten wir in unserer Ergebnisliste leere Strings und Strings, die nur ein „\t” enthalten: >>> mammon = "The god \t of the world's leading religion. The chief temple is in the holy city of New York." >>> mammon.split(" ",5) ['The', 'god', '', '\t', 'of', "the world's leading religion. The chief temple is in the holy city of New York."] >>> Die leeren Strings können wir verhindern, indem wir None als erstes Argument, also für den Separator, benutzen. Dann arbeitet split() mit der Standardeinstellung, also so, als wenn man keinen Trennstring angegeben hat: >>> mammon.split(None,5) ['The', 'god', 'of', 'the', "world's", 'leading religion. The chief temple is in the holy city of New York.'] 36.18.2 split-Methode des re-Moduls In den meisten Fällen genügt die split-Methode aus dem str-Modul voll und ganz. Aber wie sieht es beispielsweise aus, wenn man nur die reinen Wörter eines Texts herausfiltern will? Wenn man also alles herausfiltern will, was nicht Buchstaben entspricht. Dann benötigen wir eine Split-Funktion, die die Angabe von regulären Ausdrücken zur Definition von Separatoren zulässt. Das Modul re bietet eine solche Methode, die ebenfalls split() heißt. Wir erläutern die Arbeitsweise mit re.split() in einem kurzen Text, dem Beginn der Metamorphosen von Ovid: 36.18 Aufspalten eines Strings mit oder ohne regulären Ausdruck Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> import re >>> metamorphoses = "Of bodies chang'd to various forms, I sing: Ye Gods, from whom these miracles did spring, Inspire my numbers with coelestial heat;" >>> re.split("\W+",metamorphoses) ['Of', 'bodies', 'chang', 'd', 'to', 'various', 'forms', 'I', 'sing', 'Ye', 'Gods', 'from', 'whom', 'these', 'miracles', 'did', 'spring ', 'Inspire', 'my', 'numbers', 'with', 'coelestial', 'heat', ''] Seine volle Wirkungskraft entfaltet split im nachfolgenden Beispiel. Als Beispieltext verwenden wir eine ironische Definition von Logik, wie sie Ambrose Bierce in seinem berühmten „Devil’s Dictionary” vorschlägt: import re logik = """Logik: Die Kunst des Denkens und Schlussfolgerns in strenger Übereinstimmung mit den Beschränkungen und Unfähigkeiten des menschlichen Missverständnisses. Die Grundlage der Logik ist der Syllogismus (logischer Schluss), der aus einem Obersatz, einem Untersatz und einer Konklusion (Schlussfolgerung) besteht - somit : Obersatz: Sechzig Männer können eine Arbeit sechzig mal so schnell machen wie einer. Untersatz: Ein Mann kann ein Pfostenloch in sechzig Sekunden graben; deswegen: Konklusion: Sechzig Männer können ein Pfostenloch in einer Sekunde graben.""" definitions = re.split("\w+: ", logik)[1:] print("Definition von Logik: \n" + definitions[0]) print("Beispiel für einen Obersatz: \n" + definitions[1]) print("Beispiel für einen Untersatz: \n" + definitions[2]) print("Konklusion: \n" + definitions[3]) Wenn man sich den String logik anschaut, sieht man, dass jede Definition bzw. die Beispiele für den Obersatz und den Untersatz sowie die Konklusion durch den jeweiligen Begriff, gefolgt von einem Doppelpunkt, eingeleitet werden. Wir können also als Separator den regulären Ausdruck "\w+:"verwenden.6 Wir haben auf re.split("\w+: ", logik) den Slice-Operator [1:] angewendet, weil der erste Eintrag der Ergebnisliste von re.split("\w+: ", logik) ein leerer String ist, denn vor dem ersten Separator „Logik:” steht kein Zeichen. 6 Die Definitionen stammen von Ambrose Bierce, dem Autor von „The Devil’s Dictionary”. Übersetzung stammt von der Webseite „Logik wider alle Vernunft” (http://www.klein-singen.de/logik/) von Bernd Klein. 411 412 36 Reguläre Ausdrücke Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Ausgabe des obigen Skripts sieht wie folgt aus: $ python3 split_logics.py Definition von Logik: Die Kunst des Denkens und Schlussfolgerns in strenger Übereinstimmung mit den Beschränkungen und Unfähigkeiten des menschlichen Missverständnisses. Die Grundlage der Logik ist der Syllogismus ( logischer Schluss), der aus einem Obersatz, einem Untersatz und einer Konklusion (Schlussfolgerung) besteht - somit: Beispiel für einen Obersatz: Sechzig Männer können eine Arbeit sechzig mal so schnell machen wie einer. Beispiel für einen Untersatz: Ein Mann kann ein Pfostenloch in sechzig Sekunden graben; deswegen: Konklusion: Sechzig Männer können ein Pfostenloch in einer Sekunde graben. 36.18.3 Wörter filtern Wir wollen nun die Wörter eines Texts herausfiltern, also ohne Satzzeichen und Sonderzeichen. Betrachten wir folgenden String aus Shakespeares Hamlet: >>> >>> ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... import re t = """HORATIO Where, my lord? HAMLET HORATIO In my mind's eye, Horatio. I saw him once; he was a goodly king. HAMLET He was a man, take him for all in all, I shall not look upon his like again. HORATIO My lord, I think I saw him yesternight. HAMLET Saw? who? HORATIO My lord, the king your father. HAMLET The king my father!""" 36.19 Suchen und Ersetzen mit sub Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Mithilfe von re.split() können wir eine Liste aller Wörter erzeugen: >>> re.split(r"\W+",t) ['HORATIO', 'Where', 'my', 'lord', 'HAMLET', 'In', 'my', 'mind', 's', 'eye', 'Horatio', 'HORATIO', 'I', 'saw', 'him', 'once', 'he', 'was ', 'a', 'goodly', 'king', 'HAMLET', 'He', 'was', 'a', 'man', 'take ', 'him', 'for', 'all', 'in', 'all', 'I', 'shall', 'not', 'look', 'upon', 'his', 'like', 'again', 'HORATIO', 'My', 'lord', 'I', ' think', 'I', 'saw', 'him', 'yesternight', 'HAMLET', 'Saw', 'who', 'HORATIO', 'My', 'lord', 'the', 'king', 'your', 'father', 'HAMLET ', 'The', 'king', 'my', 'father', ''] Wir können uns auch sehr leicht die Menge der Wörter, also ohne mehrfache Vorkommen erzeugen: >>> words = { x.lower() for x in re.split(r"\W+",t)} >>> len(words) 36 >>> words {'', 'all', 'mind', 'hamlet', 'in', 'saw', 'your', 'again', 'eye', ' for', 'horatio', 'father', 'who', 'take', 'lord', 'he', 'was', ' his', 'shall', 'yesternight', 'upon', 'like', 'goodly', 'not', ' him', 'man', 'a', 'king', 'look', 'i', 'my', 's', 'the', 'where', 'think', 'once'} 36.19 Suchen und Ersetzen mit sub re.sub(regex, replacement, subject) Jede Übereinstimmung (match) des regulären Ausdrucks „regex” wird durch den String „replacement” ersetzt. Beispiel:7 >>> import re >>> str = "yes I said yes I will Yes." >>> res = re.sub("[yY]es","no", str) >>> print(res) no I said no I will no. 7 Der im Beispiel verwendete String entspricht den letzten Worten des Romans „Finnegans Wake” von James Joyce. 413 414 36 Reguläre Ausdrücke 36.20 Aufgaben 1. Aufgabe: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Welche der folgenden regulären Ausdrücke (a), . . . (g) passen auf welche Strings von (1), . . . (6): (1) (2) (3) (4) (5) (6) abc ac abbb bbc aabcd b (a) (b) (c) (d) (e) (f) (g) ab+c? a?b*c b+c* ^b+c*$ a.+b?c b{2,}c? ^a{1,2}b+.?d* Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 512 2. Aufgabe: Was matcht x(xy)*x? (1) (2) (3) (4) (5) xx xyxyxyx xxyxy xyxx xxyxyx Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 512 3. Aufgabe: Schreiben Sie einen Ausdruck, der US-Zip-Codes prüft. Ein Zip-Code besteht entweder aus 9 Zeichen, z.B. 205000001, oder 5 Ziffern, gefolgt von einem Strichpunkt, gefolgt von 4 Ziffern, z.B. 20500-0001 (president), 20500-0002 (first lady). Außerdem ist es auch zulässig, dass ein Zip-Code nur aus 5 Ziffern besteht, also z.B. 20500 (Washington DC) Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 513 4. Aufgabe: Finden Sie heraus, wie viele verschiedene Wörter in dem Roman „Ulysses” von James Joyce mit „ship” enden. Sie finden den Roman in unserem Beispielverzeichnis unter dem Namen „ulysses.txt”. Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 514 36.20 Aufgaben 5. Aufgabe: Finden Sie in „Ulysses” alle Wörter, die aus zwei gleichen Teilwörtern bestehen, wie beispielsweise „dumdum” oder „poohpooh”. Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 514 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 6. Aufgabe: Auch in dieser Aufgabe bleiben wir bei der Weltliteratur. Das längste Wort in „Ulysses” ist übrigens das Wort „Nationalgymnasiummuseumsanatoriumandsuspensoriumsordinaryprivatdocent”. Damit hat dieses Wort noch zwei Buchstaben mehr als das Wort „Grundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung”. Berechnen Sie die Häufigkeitsverteilungen von Wortlängen in einem Text ohne Berücksichtigung von Groß- und Kleinschreibung. Zur Erläuterung: Besteht der Text nur aus dem String „Ein Wort ist nur ein Wort.”, dann haben wir folgende Verteilung: 4 Wörter der Länge 3 2 Wörter der Länge 4 Man könnte eine Häufigkeitsverteilung auch auf die verschiedenen Wörter anwenden, dann sieht die Verteilung so aus: 1 Wort der Länge 4: „Wort” 3 Wörter der Länge 3: „ein”, „nur”, „ist” Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 515 7. Aufgabe: Schreiben Sie ein Programm, welches alle Wörter in dem Roman Ulysses findet, deren Buchstaben in aufsteigender Reihenfolge sortiert sind und mindestens 5 Buchstaben lang sind. Also zum Beispiel „bellow”, da „b” <= „e” <= „l” <= „l” <= „o” <= „w” gilt. Weitere Beispiele sind „booty”, „accept” und „effort”. Alle Großbuchstaben sollen zuvor in Kleinbuchstaben gewandelt werden. Lösung: Lösungen zu Kapitel 36 (Reguläre Ausdrücke), Seite 517 415 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 37 37.1 Typanmerkungen Einführung Mit Python 3.5 und 3.6 wurden die sogenannten Typhinweise (engl. type hints) in Python eingeführt.1 Die Einführung der Typhinweise bedeutet aber keine Abkehr von den Prinzipien von Python. Python bleibt weiterhin eine dy- Bild 37.1 type annotations namisch getypte Sprache. Außerdem sollen Typhinweise niemals verpflichtend werden, noch nicht einmal per Konvention!2 Betrachten wir folgende Funktion: def f(x, y): return x * y Schaut man sich die Definition von f an, so denkt man wohl in erster Linie an eine Funktion, die zwei numerische Argumente erwartet. Diese Funktion lässt sich aber auch mit einer Vielzahl von anderen Typkombinationen aufrufen, ohne dass eine Ausnahme erzeugt wird. >>> def f(x, y): ... return x * y ... >>> >>> f(3, 4) 12 >>> f(3.69, 11.39) 42.0291 >>> >>> f(25, "-") 1 2 Bitte beachten Sie, dass die Beispiele dieses Kapitels nur funktionieren, wenn Sie mindestens Python 3.6 installiert haben. Manche Beispiele funktionieren auch bereits mit Python 3.5. „It should also be emphasized that Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.”, PEP484, 29.09.2004, Guido van Rossum, Jukka Lehtosalo und Łukasz Langa 418 37 Typanmerkungen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. '-------------------------' >>> f("-", 25) '-------------------------' >>> >>> f([3, 5], 2) [3, 5, 3, 5] Ruft man die Funktion aber beispielsweise mit zwei Listen oder zwei Strings auf, erhebt man eine Ausnahme. Es ist natürlich ganz schlechter Stil, eine Funktion ohne Docstring zu definieren. In einem Docstring erhält man normalerweise wichtige Informationen über die möglichen Typen der Eingabeparameter und des Typs des Ausgabeobjekts der Funktion. Schauen wir uns beispielsweise help von der Funktion sample des Moduls random an: sample(population, k) method of random.Random instance Chooses k unique random elements from a population sequence or set . Returns a new list containing elements from the population while leaving the original population unchanged. ... Mittels obiger help-Information wissen wir, dass das Argument population ein sequentieller Datentyp oder eine Menge sein darf. Bei k können wir logisch schließen, dass es sich um eine Integer-Zahl handeln muss, genaugenommen eine Integer-Zahl zwischen 0 und der Anzahl der Elemente von population. Bei Sprachen mit statischer Typdeklaration wird bereits mit der Funktionsdeklaration klargestellt, welche Typen diese erwartet. Viele sahen das Fehlen von statischen Typdeklaration als Schwachpunkt von Python. Die „Type Annotations” sind eine Möglichkeit, Typüberprüfungen schon vor der Laufzeit durchzuführen. Andererseits haben sie keine Auswirkungen, während ein Programm läuft, d.h. es erfolgen keine Typüberprüfungen zur Laufzeit. Es können also keine neuen Laufzeitfehler entstehen. Die Typdeklarationen werden vom Python-Compiler und Interpreter absichtlich komplett ignoriert! Dies bedeutet, dass man Python-Programme weiterhin ohne Typanmerkungen schreiben kann. Die Typüberprüfung wird von speziellen Tools, z.B. mypy, vor der Ausführung eines Programms durchgeführt. 37.2 Einfaches Beispiel Beginnen wir mit einem einfachen Beispiel. Das Programm mypy ist nicht Bestandteil der Python-Installation, d.h. es muss also installiert werden. Wir können dies mit python3 -m pip install mypy tun. 37.3 Variablenanmerkungen Im folgenden Programm haben wir die Funktion f mit Typanmerkungen versehen. Das erste Argument von f muss nun ein Objekt vom Typ int sein und das zweite vom Typ str. Mit der Notation -> str weisen wir f auch einen Ergebnistyp str zu: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def f(x: int, y: str) -> str: return x * y print(f(3, 4)) print(f(3.69, 11.39)) print(f(25, "-")) print(f("-", 25)) Starten wir obiges Programm mit python3 type_hints.py, erhalten wir keine Fehlermeldungen, sondern die folgenden Ausgaben: 12 42.0291 ------------------------------------------------- Das Programm wurde so ausgeführt, als gäbe es keine Typanmerkungen. Sie werden also von Python wie Kommentare betrachtet.3 Mittels mypy können wir jedoch die falschen Typzuweisungen finden: bernd@marvin $ mypy type_hints_variables.py function_with_hints.py:5: error: Argument 2 type "int"; expected "str" function_with_hints.py:6: error: Argument 1 type "float"; expected "int" function_with_hints.py:6: error: Argument 2 type "float"; expected "str" function_with_hints.py:8: error: Argument 1 type "str"; expected "int" function_with_hints.py:8: error: Argument 2 type "int"; expected "str" 37.3 to "f" has incompatible to "f" has incompatible to "f" has incompatible to "f" has incompatible to "f" has incompatible Variablenanmerkungen Auch für Variablen kann ein Typ mittels Anmerkungen deklariert werden. Hierbei gibt es aber erhebliche syntaktische Unterschiede zwischen Python 3.5 und Python 3.6. In Python 3.5 erfolgt die Deklaration mittels eines Kommentars, der hinter einer Variablendefinition steht: 3 Startet man das Programm allerdings mit einer Python-Version kleiner als 3.5, erhalten wir Fehlermeldungen wegen der Syntax der Typanmerkungen, aber nicht wegen der Typinkompatibilität! 419 420 37 Typanmerkungen x = 3 # type: int In Python 3.6 haben wir eine natürlichere Syntax. Im folgenden Beispiel deklarieren wir zuerst eine Variable vom Typ Integer, und dann weisen wir ihr in einer separaten Anweisung den Wert 42 zu. Die Variable y wird gleichzeitig deklariert und definiert: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. x : int x = 42 y : int = 17 Unten stehendes Programm funktioniert, wenn wir es in einer Python-Version größer oder gleich 3.6 starten. Die Typanmerkungen werden auch in diesem Fall wie Kommentare behandelt. x : int x = 42 x = "Hallo" Die Typfehler können wir jedoch finden, wenn wir mypy mit dem Programm starten: bernd@marvin $ mypy type_hints_variables.py type_hints_variables.py:5: error: Incompatible types in assignment ( expression has type "str", variable has type "int") 37.4 Listenbeispiel Im nächsten Beispiel definieren wir eine Funktion silly_merge, die aus zwei Listen zwei zufällige, ab dem ersten Element beginnende Teillisten erzeugt und diese konkateniert. import random def silly_merge(x : list, y : list) -> list: endx = random.randint(0, len(x)) endy = random.randint(0, len(y)) return x[:endx] + y[:endy] print(silly_merge([23, 27, 29, 21, 19], [101, 134, 156, 199])) print(silly_merge([23, "aus der Reihe tanzen", 19], [101, 134, 156, 199])) Wie wir in unserem Beispiel sehen können, kann eine Liste vom Typ list beliebige Datentypen enthalten, so wie Listen auch definiert sind. Oft sollen Listen jedoch homogene Inhalte enthalten, also zum Beispiel nur Integer-Zahlen. Dann wäre der zweite Aufruf von silly_merge nicht korrekt, da die erste Liste einen String enthält. Wir möchten nicht nur deklarieren können, dass eine Variable vom Typ Liste ist, sondern auch den – homogenen – Inhalt der Liste bestimmen können. 37.5 typing-Modul Dieses Problem können wir mit dem typing-Modul lösen, welches wir im folgenden Kapitel einführen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 37.5 typing-Modul Das typing-Modul wurde eingeführt, um komplexere Datentypdeklarationen zu ermöglichen.4 Mit Hilfe des typing-Moduls können wir beispielsweise auch homogene Listen definieren, also Listen, die nur aus einem Datentyp bestehen. Unser silly_merge-Beispiel können wir nun wie folgt umschreiben: import random from typing import List def silly_merge(x: List[int], y: List[int]) -> List[int]: endx = random.randint(0, len(x)) endy = random.randint(0, len(y)) return x[:endx] + y[:endy] print(silly_merge([23, 27, 29, 21, 19], [101, 134, 156, 199])) print(silly_merge([23, "aus der Reihe tanzen", 19], [101, 134, 156, 199])) Aus dem typing-Modul haben wir den Deklarationstyp List importiert, den wir nicht mit list verwechseln dürfen. List[int] deklariert eine Liste, die nur Integer enthalten darf. Rufen wir nun mypy mit obigem Programm5 auf, so erhalten wir folgende Fehlermeldung: bernd@marvin $ mypy type_hints2.py type_hints2.py:12: error: List item 1 has incompatible type "str" Man kann auch Typaliasse anlegen, wie wir im folgenden Beispiel mit Umsaetze demonstrieren: from typing import List Umsaetze = List[float] def umrechnen(umsatz_liste : Umsaetze, faktor : float) -> Umsaetze: 4 5 In der offiziellen Dokumentation befindet sich die Bemerkung: „Das typing-Modul wurde in die StandardLibrary auf vorläufiger Basis eingeführt. Neue Features können hinzugefügt werden und die Benutzerschnittstelle könnte sich auch bei Nebenversionsnummern ändern, falls dies den Entwicklern als notwendig erscheint.” (englisches Original: „The typing module has been included in the standard library on a provisional basis. New features might be added and API may change even between minor releases if deemed necessary by the core developers.” unter der Annahme, dass es unter type_hints2.py gespeichert ist. 421 422 37 Typanmerkungen ergebnis_liste = [] for umsatz in umsatz_liste: ergebnis_liste.append(umsatz*faktor) return ergebnis_liste Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. print(umrechnen([312.9, 543.9, 100.45, 98.99], 1.18)) Der Tupel-Typ des typing-Moduls erlaubt es, inhomogene Datenstrukturen zu deklarieren. Im folgenden Beispiel schreiben wir eine Typdeklaration für eine Liste mit Vierertupel. Die Liste soll einen Einkauf darstellen. Jedes Element von Einkauf ist ein Vierertupel mit Anzahl, Artikelnummer, Artikeltext und Preis. from typing import List, Tuple Artikel = Tuple[int, str, float] Einkauf = List[Artikel] def aufsummieren(warenkorb : Einkauf) -> float: total = sum( anz * preis for anz, nr, txt, preis in warenkorb ) return total korb = [ (2, 68978, "Orangen", 3.98), (1, 70876, "Milch", 1.19), (3, 87866, "Mineralwasser", 3.49)] print(aufsummieren(korb)) Als Nächstes zeigen wir, wie man Signaturen von Callables bzw. Funktionen deklariert. Bei unserem ersten Beispiel handelt es sich um die Funktion polynomial_creator, die wir in Kapitel Dekorateure, Seite 345, eingeführt haben. Wir schreiben sie nun mit entsprechenden Typanmerkungen: from typing import Callable, Union p1 : Callable[[float], float] p2 : Callable[[float], float] def polynomial_creator(a: float, b: float, c: float) -> Callable[[float], float]: def polynomial(x: float) -> float: return a * x**2 + b * x + c return polynomial p1 = polynomial_creator(2, 3, -1) p2 = polynomial_creator(-1, 2, 1) for x in range(-2, 2, 1): print(x, p1(x), p2(x)) 37.5 typing-Modul Wir können auch Dekorateure so wie unseren memoize-Dekorator aus dem Kapitel Dekorateure, Seite 359 mit Typanmerkungen versehen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. from typing import Callable, Any, Dict def memoize(f: Callable[[Any], Any] ) -> Callable[[Any], Any]: memo : Dict[Any, Any] memo = {} def helper(x : Any) -> Any: if x not in memo: memo[x] = f(x) return memo[x] return helper @memoize def fib(n: int) -> int: if n <= 1: return n else: return fib(n-1) + fib(n-2) Der Datentyp Any steht für einen beliebigen Datentyp, also beispielsweise int, float und so weiter. 423 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 38 38.1 Systemprogrammierung Systemprogrammierung Unter Systemprogrammierung versteht man die Programmierung von Software-Komponenten, die Teil des Betriebssystems sind oder die eng mit diesem bzw. mit der darunter liegenden Hardware kommunizieren müssen. Systemnahe Software wie beispielsweise das sys-Modul in Python fungiert als Abstraktionsschicht zwischen der Anwendung, dem Python-Programm und dem Betriebssystem. Mithilfe dieser Abstraktionsschicht lassen sich Applikationen plattformunabhängig implementieren, auch wenn sie direkt auf Betriebssystemfunktionalitäten zugreifen. Python eignet sich besonders gut für die Systemprogrammierung, denn auch hier gelten die Vorteile von Python: ■ einfach und übersichtlich ■ gute Strukturierbarkeit ■ hohe Flexibilität 38.2 Shell Shell ist ein Begriff, der viel benutzt und doch oft unterschiedlich oder auch falsch verstanden wird. Shell kommt aus dem Englischen und bedeutet Schale oder Hülle, beispielsweise die Schale eines PythonEis. Die Funktion einer Eierschale besteht in erster Linie im Schutz des Inneren. Sie trennt und schützt das Innere vom Äußeren. So trennt auch die Shell den Kern des Betriebssystems vom Shell-Benutzer und gibt ihm robuste und einfach zu bedienen- Bild 38.1 Shells und Schalen de Funktionen an die Hand, damit er nicht die komplizierten und fehleranfälligen Betriebssystemfunktionen benutzen muss. Generell unterscheidet man bei Betriebssystemen zwei Arten von Shells: 426 38 Systemprogrammierung ■ die Kommandozeile (CLI) und ■ die grafischen Benutzeroberflächen (GUI) Meistens wird der Begriff jedoch als Synonym für den Kommandozeileninterpreter (CLI) benutzt, so wie z.B. die Bourne-Shell, C-Shell oder Bash unter Linux und Unix. In unserem Fall gibt es zwei Gründe, von Shells zu sprechen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ ■ Die Module shutil, os, sys, die wir in diesem Kapitel besprechen, kennen wir aus den Shells der Betriebssysteme, also beispielsweise von der Bash-Shell von Unix/Linux. Andererseits bilden die genannten Module ihrerseits im oben genannten Sinne Shells. Sie sind gewissermaßen eine Abstraktionsschicht über den Betriebssystemfunktionen, um Python betriebssystemunabhängig zu machen. 38.3 os-Modul Das os-Modul ist das wichtigste Modul zur Interaktion mit dem Betriebssystem und ermöglicht durch abstrakte Methoden ein plattformunabhängiges Programmieren. Gleichzeitig ist es auch mit Funktionen wie os.system() oder der exec*()-Funktionsfamilie möglich, betriebssystemabhängige Programmteile einzubauen. Das os-Modul bietet viele unterschiedliche Methoden wie beispielsweise zum Zugriff auf das Dateisystem. Mithilfe des os-Moduls kann man beispielsweise die Zugriffsrechte einer Datei abfragen. Allerdings geht dies nicht direkt. Man ruft zuerst die Funktion stat aus dem os-Modul auf. Diese liefert ein 10-Tupel mit verschiedenen Dateimetadaten zurück. Die erste Komponente liefert die Zugriffsmaske als Integer. Wenn wir diese in eine Oktalzahl wandeln, erhalten wir die unter Linux gewohnte Sichtweise, d.h. „644” in der letzten Zeile des folgenden Beispiels: >>> import os >>> info = os.stat("abc.py") >>> type(info) <class 'posix.stat_result'> >>> info posix.stat_result(st_mode=33188, st_ino=63253, st_dev=2055, st_nlink =1, st_uid=1000, st_gid=1000, st_size=17, st_atime=1361559201, st_mtime=1361455357, st_ctime=1361455357) >>> info.st_ino 63253 >>> info.st_ino is info[1] True 38.3 os-Modul 38.3.1 Vorbemerkungen Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Warnung: Wir hatten bereits früher erwähnt, dass es sehr schlechter Stil ist, Module mit Sternchen zu importieren, da gegebenenfalls Variablen und Funktionen überschrieben werden können. Importiert man os mittel „from os import *”, wird die eingebaute Funktion open() durch os.open() überschrieben, die ein komplett anderes Verhalten hat. So führt dann beispielsweise die folgende, ansonsten korrekte Anweisung zum Öffnen einer Datei zu einer Fehlermeldung: >>> from os import * >>> fh = open("abc.py") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: open() takes at least 2 arguments (1 given) Für die im Folgenden beschriebenen Funktionen des os-Moduls gilt: ■ ■ ■ Alle Funktionen, die einen Pfad- oder Dateinamen als Argument haben, akzeptieren sowohl Byte- als auch String-Objekte. Das Ergebnis ist dann vom gleichen Typ, falls wieder ein Pfad- oder Dateinamen zurückgeliefert wird. Falls nicht anders vermerkt, sind alle Funktionen, die unter Unix laufen, auch unter dem Mac OS X lauffähig. Alle Funktionen dieses Moduls erzeugen einen OSError, falls ungültige oder nicht verfügbare Datei- oder Pfadnamen oder andere Argumente, die nicht vom Betriebssystem akzeptiert werden, übergeben werden. 38.3.2 Umgebungsvariablen os.environ ist ein Dictionary, das alle Umgebungsvariablen enthält. Wir können wie üblich darauf zugreifen. Beispielsweise liefert uns os.environ["USER"] den aktuellen Namen des Benutzers: >>> os.environ["USER"] 'bernd' Man kann dies natürlich auch über die im Folgenden beschriebene Funktion getenv erreichen: ■ getenv(key, default=None) Liefert den Wert einer Umgebungsvariablen zurück. None wird zurückgeliefert, wenn die Variable nicht existiert. Mit dem optionalen zweiten Parameter kann man einen alternativen Default-Wert setzen. >>> os.getenv("USER") 'bernd' 427 428 38 Systemprogrammierung ■ getenvb( key, default=None) Liefert den Wert einer Umgebungsvariablen zurück. None wird zurückgeliefert, wenn die Variable nicht existiert. Mit dem optionalen zweiten Parameter kann man einen alternativen Default-Wert setzen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. „key”, „default” und das Ergebnis sind Byte-Strings! >>> b = bytes("USER", 'utf-8') >>> os.getenvb(b) b'bernd' ■ putenv(key, value) In der Help-Funktion steht hier nur: putenv(...) putenv(key, value) Change or add an environment variable. Man könnte nun erwarten, dass man eine Environment-Variable erzeugen und verändern kann und dies dann auch im aktuellen Prozess sichtbar ist. Dies ist aber nicht so, wie wir im Folgenden sehen: >>> import os >>> os.putenv("ABC","42") >>> os.getenv("ABC") >>> print(os.getenv("ABC")) None Die obige Umgebungsvariable ist erst bekannt, wenn wir einen spawn durchführen oder ein Unterprozess gestartet wird. Wir starten im folgenden Beispiel eine Subshell und lassen uns dort mittels echo den Wert der Umgebungsvariablen ABC ausgeben. Wir sehen, dass wir jetzt den Wert 42 erhalten: >>> command = """ echo $ABC """ >>> os.popen(command,"w") <os._wrap_close object at 0x8b9e7ac> >>> 42 Es stellt sich natürlich die Frage, wie wir eine Umgebungsvariable im laufenden Prozess ändern bzw. neu erzeugen können. Wir erreichen dies, indem wir direkt das Dictionary os.environ manipulieren: >>> os.environ["ABC"] = "42" >>> os.getenv("ABC") '42' >>> os.environ["ABC"] '42' ■ supports_bytes_environ( ) Wenn dieses Attribut den Wert True hat, existiert auch ein Dictionary os.environb, welches die Umgebungsvariablen als Bytestrings enthält. 38.3 os-Modul Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ unsetenv( var ) Eine Umgebungsvariable wird entfernt. Hier gilt analog das zu putenv Gesagte. Die Umgebungsvariable existiert weiterhin im aktuellen Prozess, wird aber nicht mehr an Unterprozesse weitergegeben: >>> import os >>> os.environ["ABC"] = "42" >>> os.getenv("ABC") '42' >>> os.unsetenv("ABC") >>> os.getenv("ABC") '42' >>> command = """ echo $ABC """ >>> os.popen(command,"w") <os._wrap_close object at 0x8bfd10c> 38.3.3 Dateiverarbeitung auf niedrigerer Ebene In Kapitel 11 (Dateien lesen und schreiben) haben wir gesehen, mit welcher Leichtigkeit und Eleganz sich Dateien mit Python lesen und schreiben lassen. Es stellt sich deshalb sofort die Frage, weshalb das os-Modul eigene Funktionen und Methoden zur Dateiverarbeitung bereitstellt. In den meisten Fällen genügen die eingebauten Python-Funktionalitäten voll und ganz, und der Großteil der Python-Benutzer wird nie die os-Funktionen benötigen. Aber die Dateiverarbeitung von os geht mehr ins Detail, d.h. sie gibt dem Benutzer mehr Möglichkeiten, direkt auf Byteebene zu arbeiten. ■ write Zunächst wollen wir zeigen, wie man die os.write-Funktion auch im Zusammenspiel mit einer von der eingebauten open-Funktion zum Schreiben geöffneten Datei nutzen kann: import os fh = open("nur_text.txt","w") fh.write("Ein wenig Text\n") fh.flush() fd = fh.fileno() os.write(fd, b"Noch etwas Text") fh.close() Startet man obiges Programm,1 erhält man eine Datei namens „nur_text.txt”. Den Inhalt können wir uns mittels cat2 oder mit irgendeinem Editor anschauen: 1 2 Das Programm befindet sich in unserer Beispielsammlung unter dem Name „os_write.py”. type unter Windows 429 430 38 Systemprogrammierung bernd@saturn:~/bodenseo/python/tmp$ python3 os_write.py bernd@saturn:~/bodenseo/python/tmp$ cat nur_text.txt Ein wenig Text Noch etwas Text Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Mit os.write lassen sich nur Bytestrings schreiben. Lässt man das „b” vor dem Ausgabestring in der Zeile „os.write(fd, b"Noch etwas Text")” weg, führt dies deshalb zu folgender Fehlermeldung: bernd@saturn:~/bodenseo/python/tmp$ python3 os_write.py Traceback (most recent call last): File "os_write.py", line 8, in <module> os.write(fd, "Noch etwas Text\n") TypeError: 'str' does not support the buffer interface Sehr wichtig ist es auch, die Methode flush auf das Dateihandle anzuwenden, da ansonsten die Ausgaben von os.write an die falsche Stelle geschrieben werden. Im Folgenden sehen wir den Inhalt der Datei „nur_text.txt” nach einem Programmlauf mit auskommentierter flush-Anweisung. Die beiden Zeilen der Datei sind vertauscht: bernd@saturn:~/bodenseo/python/tmp$ cat nur_text.txt Noch etwas Text Ein wenig Text ■ open(file, flags[, mode]) Mit dieser Funktion wird eine Datei „file” zur Dateibearbeitung auf niedrigem Level geöffnet. Die Art der Bearbeitung ergibt sich sich aus den „flags” und gegebenenfalls dem optionalen „mode”-Parameter. – O_RDONLY Numerischer Wert: 0, gesetztes Bit: keines Datei wird nur zum Lesen geöffnet. – O_WRONLY Numerischer Wert: 1, gesetztes Bit: 0 Datei wird nur zum Schreiben geöffnet. – O_RDWR Numerischer Wert: 2, gesetztes Bit: 1 Datei wird zum Lesen und Schreiben geöffnet. – O_CREAT Numerischer Wert: 64, gesetztes Bit: 6 Datei wird erzeugt, falls sie noch nicht existiert. – O_EXCL Numerischer Wert: 128, gesetztes Bit: 7 Fehlermeldung, falls ein Erzeugen der Datei nicht erfolgreich war, weil die Datei bereits existiert. – O_NOCTTY Numerischer Wert: 256, gesetztes Bit: 8 Falls es sich bei der Datei um ein Terminal-Device handelt, soll es nicht zum Kontrollterminal für den Prozess werden. 38.3 os-Modul Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. – O_TRUNC Numerischer Wert: 512, gesetztes Bit: 9 Falls die Datei existiert, eine reguläre Datei ist und die Datei erfolgreich mittels O_RDWR oder O_WRONLY geöffnet wurde, wird ihre Länge auf 0 abgeschnitten. Die Dateirechte und die Eigentumsverhältnisse bleiben unverändert. Die Funktion sollte keine Effekte auf FIFO-Spezialdateien und auf Termindevice-Dateien haben. Effekte auf andere Dateitypen sind implementierungsabhängig. Was passiert, wenn man O_TRUNC zusammen mit O_RDONLY benutzt, ist undefiniert. – O_APPEND Numerischer Wert: 1024, gesetztes Bit: 10 Mit jedem write wird ans Ende der Datei geschrieben. – O_NDELAY und O_NONBLOCK Numerischer Wert: 2048, gesetztes Bit: 11 Diese Flags beeinflussen aufeinanderfolgende reads und writes. Wenn sowohl O_NDELAY als auch O_NONBLOCK gesetzt sind, hat O_NONBLOCK den Vorrang. Falls O_NDELAY oder O_NONBLOCK gesetzt ist, wird ein open für read-only ohne Verzögerung ausgeführt. Ein open for write-only führt zu einem Fehler, falls die Datei noch von keinem anderen Prozess zum Lesen geöffnet ist. Falls O_NDELAY und O_NONBLOCK nicht gesetzt sind, gilt: Ein open für ein readonly wird solange geblockt, bis ein Prozess die Datei zum Schreiben öffnet. Ein open für ein write-only wird solange geblockt, bis ein Prozess die Datei zum Lesen öffnet. – O_DSYNC Numerischer Wert: 4096, gesetztes Bit: 12 – O_ASYNC Numerischer Wert: 8192, gesetztes Bit: 13 – O_DIRECT Numerischer Wert: 16384, gesetztes Bit: 14 Cache-Effekte sollen eliminiert bzw. reduziert werden. – O_LARGEFILE Numerischer Wert: 32768, gesetztes Bit: 15 – O_DIRECTORY Numerischer Wert: 65536, gesetztes Bit: 16 – O_NOFOLLOW Numerischer Wert: 131072, gesetztes Bit: 17 Symbolischen Links wird nicht gefolgt. – O_NOATIME Numerischer Wert: 262144, gesetztes Bit: 18 – O_RSYNC Numerischer Wert: 1052672, gesetztes Bit: 20 – O_SYNC Numerischer Wert: 1052672, gesetztes Bit: 20 Das folgende Skript öffnet eine Datei für den Write-only-Zugriff „os.O_WRONLY”. os.O_CREAT stellt sicher, dass die Datei „blabla.txt” beim Öffnen erzeugt wird, falls sie noch nicht existiert. 431 432 38 Systemprogrammierung import os fd = os.open( "blabla.txt", os.O_WRONLY|os.O_CREAT ) os.write(fd, b"Etwas Text\n") os.write(fd, b"und noch eine Zeile\n") Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. os.close(fd) Falls blabla.txt noch nicht existiert und man kein „os.O_CREAT” angegeben hat, also fd = os.open( "blabla.txt", os.O_WRONLY), dann erfolgt beim Start des Skripts eine Fehlermeldung wie hier zu sehen: bernd@saturn:~/bodenseo/python/tmp$ python3 os_write.py Traceback (most recent call last): File "os_write.py", line 3, in <module> fd = os.open( "blabla.txt", os.O_WRONLY ) OSError: [Errno 2] No such file or directory: 'blabla.txt' ■ read(fd,n) Die Funktion read liest höchstens „n” Bytes vom Dateideskriptor „fd”. Sie liefert einen Byte-String zurück, der die gelesenen Bytes enthält. Falls das Dateiende der durch „fd” referenzierten Datei erreicht ist, wird ein leerer String zurückgeliefert. Im Folgenden lesen wir die ersten 80 Bytes des Romans „Ulysses” und lassen uns den zurückgelieferten Typ zusätzlich ausgeben: import os, sys fd = os.open("ulysses.txt",os.O_RDWR) res = os.read(fd,80) print(res) print("type of returned object: ", type(res)) os.close(fd) Das Skript liefert folgendes Ergebnis: bernd@saturn:~/bodenseo/python/beispiele$ python3 os_read.py b'ULYSSES\r\n\r\nby James Joyce\r\n\r\n\r\n\r\n\r\n-- I --\r\n\r\ nStately, plump Buck Mulligan came ' type of returned object: <class 'bytes'> ■ ■ close(fd) Die mit dem Dateideskriptor „fd” referenzierte Datei wird geschlossen. Die Funktion liefert keinen return-Wert zurück. openpty( ) Die Funktion openpty() öffnet ein Pseudoterminal-Paar und liefert ein Deskriptorenpaar (master, slave) für pty und tty zurück. Im folgenden Skript sehen wir die Funktionsweise dieser Funktion: 38.3 os-Modul import os master_pty,slave_tty = os.openpty() terminal_name = os.ttyname(slave_tty) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. print(master_pty, slave_tty, terminal_name) Obiges Programm liefert folgende Ausgaben: bernd@saturn:~/bodenseo/python/tmp$ python3 openpty.py 3 4 /dev/pts/3 ■ lseek(fd, pos, how) Die Funktion lseek positioniert die aktuelle Position des Dateideskriptors auf die gegebene Position „pos” in Abhängigkeit von „how”. Mittels lseek haben wir eine index-sequentielle Zugriffsmethode. Parameter: – fd Der Dateideskriptor, der bearbeitet wird. – pos Integer-Wert, der die Position innerhalb der Datei mit dem Dateideskriptor „fd” angibt. Die Position hängt vom Wert von „how” ab. Hat „how” den Wert os.SEEK_SET oder 0, berechnet sich der Wert von pos vom Beginn der Datei. os.SEEK_CUR oder 1, dann berechnet sich „pos” relativ zur aktuellen Position; os.SEEK_END oder 2 bedeutet in Relation zum Dateiende. – how Dieser Parameter bestimmt den Referenzpunkt innerhalb der Datei: os.SEEK_SET oder 0 steht für den Anfang der Datei, os.SEEK_CUR oder 1 bezeichnet die aktuelle Position, und os.SEEK_END oder 2 kennzeichnet das Ende der Datei. import os, sys fd = os.open("customer_numbers.txt",os.O_RDWR) for customer in [2,1,4,0,3]: os.lseek(fd, customer * 7, os.SEEK_SET) res = os.read(fd,6) print("customer number " + str(customer + 1) +": " + str(res)) print("\nEine Iteration über die Kundennummern:") # iteration über die Kundennummern: os.lseek(fd, 0, 0) for customer in range(1,6): res = os.read(fd,6) os.lseek(fd, 1, os.SEEK_CUR) # skip the newline character print("customer number " + str(customer) +": " + str(res)) 433 434 38 Systemprogrammierung print("\n... und nun von hinten:") for customer in range(1,6): os.lseek(fd, -7 * customer, os.SEEK_END) res = os.read(fd,6) print(str(customer) + " from last: " + str(res)) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. os.close(fd) In der ersten for-Schleife des obigen Programms können wir sehen, wie wir auf beliebige Kundennummern zugreifen können, ohne sie sequentiell durchlaufen zu müssen. In der zweiten for-Schleife benutzen wir os.seek mit dem how-Parameter „os.SEEK_CUR”, d.h. nachdem wir eine Kundennummer eingelesen haben, überspringen wir das nächste Byte mit dem os.seek-Kommando. In der letzten for-Schleife durchlaufen wir die Kundennummern von hinten. Die Ausgabe des Programms sieht wie folgt aus: bernd@saturn:~/bodenseo/python/beispiele$ python3 os_seek.py customer number 3: b'ZH8765' customer number 2: b'AB9876' customer number 5: b'QW5185' customer number 1: b'JK3454' customer number 4: b'KL9878' Eine Iteration über die Kundennummern: customer number 1: b'JK3454' customer number 2: b'AB9876' customer number 3: b'ZH8765' customer number 4: b'KL9878' customer number 5: b'QW5185' ... und nun von hinten: 1 from last: b'QW5185' 2 from last: b'KL9878' 3 from last: b'ZH8765' 4 from last: b'AB9876' 5 from last: b'JK3454' ■ dup() Liefert die Kopie eines Datei-Handles3 zurück. 38.3.4 Die exec-„Familie” Die Funktionen execl, execlp, execle, execv, execvp und execvpe des os-Moduls entsprechen den gleichnamigen UNIX/Linux-Funktionen. Jede dieser Funktionen aus der execFamilie ersetzt das aktuelle Prozessabbild durch einen neuen Prozess. Das neue Prozessabbild wird aus einer regulären und ausführbaren Datei konstruiert. Man nennt diese Datei 3 engl. file descriptor Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 38.3 os-Modul Bild 38.2 Systematik der exec-Familie „Prozessabbilddatei”. Die Prozessabbilddatei ist entweder ein ausführbares Objekt oder eine Datendatei, die von einem Interpreter ausgeführt werden kann. Führt man eine dieser Funktionen aus, gibt es keine Rückkehr (return) nach einem erfolgreichen Aufruf, denn der aufgerufene Prozess überlagert das aktuelle Prozessabbild. Schaut man sich die exec*-Funktionen in obigem Schaubild an, fällt auf, dass alle Funktionen exec als Präfix haben. Dann folgen bis zu drei der folgenden Buchstaben: „e”, „l”, „p” oder „v” Diese Buchstaben haben folgende Bedeutung: ■ ■ ■ ■ e Ein Dictionary mit Umgebungsvariablen wird an den auszuführenden Prozess übergeben. v Kommandozeilenargumente werden als Liste oder Tupel an das Skript übergeben. l Kommandozeilenargumente für das aufzurufende Skript werden direkt und einzeln im exec*-Aufruf angegeben. p Die $PATH-Umgebungsvariable wird benutzt, um das Skript, dessen Name als Parameter dem String übergeben wurde, zu finden. Fehler bei einer exec*-Funktion werden als OSError generiert. Zur Beschreibung der folgenden exec-Befehle benutzen wir ein Bash-Skript zum Testen. Wir nehmen an, dass dieses Skript unter dem Namen „test.sh” im Verzeichnis „/home/bernd/bin2” steht. Das Bash-Skript „test.sh”: #!/bin/bash script_name=$0 arg1=$1 current=`pwd` echo $script_name, $arg1 435 436 38 Systemprogrammierung echo "XYZ: "$XYZ echo "PATH: "$PATH echo "current directory: $current" Achtung: test.sh muss ausführbar sein, und bin2 sollte, damit die folgenden Beispiele funktionieren, nicht in $PATH enthalten sein. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ execvp( scriptname, args ) Wie bereits eingangs erklärt, bedeutet das „p” im Namen, dass der Skriptname „scriptname” in den Pfaden der Systemvariablen $PATH gesucht wird. Das „v” bedeutet, dass wir Kommandozeilenargumente mit dem Parameter „args” übergeben können, und zwar in Form einer Liste oder eines Tupels. Wir starten unsere interaktive Python-Shell in einem Verzeichnis, was nicht gleich dem bin2-Verzeichnis ist. Außerdem ist „/home/bernd/bin2” nicht Teil unserer Umgebungsvariablen „$PATH”. Dann passiert Folgendes, wenn wir versuchen, unser Test-Shellskript mit execvp aufzurufen: >>> import os >>> args = ("test","abc") >>> os.execvp("test.sh", args) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.2/os.py", line 353, in execvp _execvpe(file, args) File "/usr/lib/python3.2/os.py", line 398, in _execvpe raise last_exc.with_traceback(tb) File "/usr/lib/python3.2/os.py", line 388, in _execvpe exec_func(fullname, *argrest) OSError: [Errno 2] No such file or directory execvp konnte die Skriptdatei „test.sh” nicht finden, da sie sich im Verzeichnis „/home/bernd/bin2” befindet und dieser Pfad nicht Teil von $PATH ist. Rufen wir unsere interaktive Python-Shell direkt im Verzeichnis „/home/bernd/bin2” auf, funktioniert alles: bernd@saturn:~/bin2$ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> args = ("test", "abc") >>> os.execvp("test.sh", args) ./test.sh, abc XYZ: PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm :/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/ 38.3 os-Modul usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/ bernd/bin:.::bin/bck_scripts/:bin/bck_scripts/ current directory: /home/bernd/bin2 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Alternativ kann man auch den Pfad „/home/bernd/bin2” in die $PATH-Umgebungsvariable mit aufnehmen. Dann wird test.sh immer gefunden – egal, wo wir uns aufhalten, also auch zum Beispiel im Home-Verzeichnis: bernd@saturn:~$ PATH=$PATH:/home/bernd/bin2 bernd@saturn:~$ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> args = ("test", "abc") >>> os.execvp("test.sh", args) /home/bernd/bin2/test.sh, abc XYZ: PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm :/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/ usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/ bernd/bin:.::/home/bernd/bin2 current directory: /home/bernd bernd@saturn:~$ ■ execvpe(scriptname, args, env ) Wählen wir die execvpe-Variante unserer exec-Familie, dann können wir zusätzlich noch neue Umgebungsvariablen beim Aufruf mit übergeben. Dazu schreiben wir die neuen Umgebungsvariablen in ein Dictionary, in unserem Fall „env”. „filename” ist der Name des auszuführenden Skripts und „args” eine Liste oder ein Tupel mit den an „filename” zu übergebenden Argumenten. Das Verzeichnis „/home/bernd/bin2” befindet sich zwar noch in unserer PATH-Umgebungsvariablen, aber beim Aufruf werden nur die in env definierten Umgebungsvariablen an das neue Skript übergeben. Deshalb funktioniert der folgende Aufruf nur, wenn wir uns im bin2-Verzeichnis befinden: >>> import os >>> env = {"ABC":"Von Anfang", "KLM":"bis zum ", "XYZ":"Ende"} >>> args = ("erstes","zweites") >>> os.execvpe("test.sh", args, env) test.sh, zweites XYZ: Ende PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin current directory: /home/bernd/bin2 Wir wollen nun auch PATH und die anderen alten Umgebungsvariablen an das Skript übergeben. Wir können dies mit der update-Methode erreichen. os.environ enthält al- 437 438 38 Systemprogrammierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. le definierten Umgebungsvariablen der aktuellen Shell. Dann können wir unser Skript wieder von allen Orten aufrufen, sofern bin2 noch im PATH enthalten ist: >>> import os >>> env = {"ABC":"Von Anfang", "KLM":"bis zum ", "XYZ":"Ende"} >>> env.update(os.environ) >>> args = ("erstes", "zweites") >>> os.execvpe("test.sh", args, env) /home/bernd/bin2/test.sh, zweites XYZ: Ende PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm :/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/ usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/ bernd/bin:.::bin/bck_scripts/:/home/bernd/bin2 current directory: /home/bernd/bodenseo/python bernd@saturn:~/bodenseo/python$ ■ execv( scriptname, args) Wie „execvp”, aber der Skriptname „scriptname” muss den kompletten Pfad des Skripts enthalten, wenn das Skript sich nicht im aktuellen Arbeitsverzeichnis befindet, da der Name nicht in den Verzeichnissen von der Umgebungsvariablen PATH gesucht wird: >>> import os >>> args = ("arg1", "arg2") >>> os.execv("test.sh", args) Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 2] No such file or directory >>> os.execv("/home/bernd/bin2/test.sh", args) /home/bernd/bin2/test.sh, arg2 XYZ: PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm :/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/ usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/ bernd/bin:.::bin/bck_scripts/:/home/bernd/bin2 current directory: /home/bernd/bodenseo/python bernd@saturn:~/bodenseo/python$ ■ execve(scriptname, args, env ) Analog zu „execv”, allerdings wird bei dieser Variante noch ein Dictionary mit Umgebungsvariablen mit übergeben: >>> import os >>> args = ("arg1", "arg2") >>> env = {"ABC":"Van Anfang", "KLM":"bis zum ", "XYZ":"Ende"} >>> os.execve("/home/bernd/bin2/test.sh", args, env) /home/bernd/bin2/test.sh, arg2 XYZ: Ende PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 38.3 os-Modul current directory: /home/data/bodenseo/python bernd@saturn:~/bodenseo/python$ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ execlp( scriptname, arg0, arg1, ...) Die Funktion „execlp” unterscheidet sich von „execvpe” in der Art, wie die Argumente übergeben werden. Bei „execvpe” hatten wir ein Tupel bzw. eine Liste mit den Parametern als Strings übergeben. Nun wird jeder Parameter direkt in „execlpe” durch Komma getrennt als String angegeben, also arg0, arg1, ... . arg0 entspricht dabei dem Namen des Skripts, wird aber in der Implementierung nicht genutzt. Der Skriptname wird stattdessen an „scriptname” als Name übergeben. „scriptname” und „env” sind wie bereits in execvpe beschrieben. Um das Verhalten dieser Funktion besser demonstrieren zu können, benutzen wir ein Bash-Skript, das alle übergebenen Argumente ausdruckt, nämlich das Bash-Skript „test_params.sh”: #!/bin/bash script_name=$0 echo $script_name while test $# -gt 0 do echo $1 shift done Nun rufen wir das Skript „test_params.sh” mit „execlpe” in der interaktiven Python-Shell auf: >>> import os >>> os.execlp("test_params.sh", "test_params.sh", "a1","a2", "a3", " a4") /home/bernd/bin2/test_params.sh a1 a2 a3 a4 bernd@saturn:~/bodenseo/python$ Wir sehen, dass wir beim Aufruf den Namen des Skripts zweimal übergeben haben. arg0 sollte also auch den Namen des Skripts enthalten, wird aber von exec ignoriert. Wir können stattdessen auch irgendetwas eingeben, und es hat keinen Einfluss auf das Skript: >>> import os >>> os.execlp("test_params.sh", "unsinn.sh", "a1","a2", "a3", "a4") /home/bernd/bin2/test_params.sh a1 a2 a3 a4 bernd@saturn:~/bodenseo/python$ 439 440 38 Systemprogrammierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ execl( ) Analog zu „execlp”. Allerdings müssen wir nun den kompletten Pfad angeben, damit das Skript gefunden werden kann, denn die Informationen aus der Umgebungsvariablen $PATH werden in dieser exec*-Variante nicht verwendet: >>> import os >>> os.execl("test_params.sh", "test_params.sh", "arg1", "arg2") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.2/os.py", line 321, in execl execv(file, args) OSError: [Errno 2] No such file or directory >>> os.execl("/home/bernd/bin2/test_params.sh", "test_params.sh", " arg1", "arg2") /home/bernd/bin2/test_params.sh arg1 arg2 bernd@saturn:~/bodenseo/python$ ■ ■ execlp( scriptname, arg0, arg1, ... , env) Wie execlp, nur dass noch ein Dictionary mit Umgebungsvariablen übergeben wird. Siehe dazu die Beschreibung der Funktion „execvpe”. execle( ) Analog zu execl, aber es wird noch zusätzlich ein Dictionary mit Umgebungsvariablen übergeben: >>> >>> >>> >>> import os env = {"ABC":"Von Anfang", "KLM":"bis zum ", "XYZ":"Ende"} env.update(os.environ) os.execle("/home/bernd/bin2/test.sh", "test.sh", "arg1", "arg2", env) /home/bernd/bin2/test.sh, arg1 XYZ: Ende PATH: /home/bernd/perl5/bin:/home/bernd/bin:/usr/lib/lightdm/lightdm :/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/ usr/games:/usr/local/games:/home/writing/etrusker_ag/bin:/home/ bernd/bin:.::bin/bck_scripts/:/home/bernd/bin2 current directory: /home/bernd/bodenseo/python bernd@saturn:~/bodenseo/python$ 38.3 os-Modul 38.3.5 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ Weitere Funktionen im Überblick abort( ) Der Interpreter wird unmittelbar abgebrochen. Das englische Wort „abort” bedeutet „abbrechen”, aber im Sinne von „fehlschlagen lassen”, „scheitern” und „vorzeitig beenden”. In der Help-Ausgabe zum os-Modul steht unter abort: „This ’dumps core’ or otherwise fails in the hardest way possible on the hosting operating system”. Das bedeutet also, dass der schlimmstmögliche Fehler unter dem jeweiligen Betriebssystem erzeugt wird. Unter Linux bedeutet das zunächst einmal, dass man nach dem Abbruch die folgende Fehlermeldung erhält: >>> os.abort() Aborted (core dumped) bernd@saturn:~/bodenseo/python$ Außerdem klappen zwei Fenster auf, die normalerweise nur bei ernsten Systemfehlern auftauchen: ■ access(path, mode) Die Funktion „os.access” dient zur Überprüfung, welche Rechte das laufende PythonProgramm für den Pfad „path” hat. Mit dem optionalen Parameter „mode” kann eine Bitmaske übergeben werden, welche die zu überprüfenden Rechte enthält. Folgende Werte können einzeln oder mit dem bitweisen ODER kombiniert übergeben werden: os.F_OK : Prüft, ob der Pfad überhaupt existiert. os.R_OK : Prüft, ob der Pfad gelesen werden darf. os.W_OK : Prüft, ob der Pfad geschrieben werden darf. os.X_OK : Prüft, ob der Pfad ausführbar ist. os.access liefert True zurück, wenn alle für „mode” übergebenen Werte auf den Pfad zutreffen. Gilt mindestens ein Zugriffsrecht nicht, wird False zurückgegeben. ■ chdir(path) Das Arbeitsverzeichnis wird auf das durch die Stringvariable „path” bezeichnete Verzeichnis geändert: >>> os.getcwd() '/home/bernd' >>> os.chdir("/home/bernd/bodenseo/python") >>> os.getcwd() '/home/data/bodenseo/python' 441 442 38 Systemprogrammierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ chmod( path, mode) Die Zugriffsrechte eines Pfads bzw. einer Datei werden entsprechend der Eingabemaske „mode” umgesetzt. „mode” muss als Oktalzahl eingegeben werden, d.h. eine Zahl mit Präfix 0o: >>> permissions = oct(os.stat("abc.py")[0]) >>> permissions '0o100644' >>> os.chmod("abc.py",0o644) >>> os.chmod("abc.py",0o755) >>> permissions = oct(os.stat("abc.py")[0]) >>> permissions '0o100755' ■ chown(path, uid, gid) Die Funktion chown ändert den Besitzer und die Gruppen-ID entsprechend der Parameter „uid” und „gid”. Möchte man einen Wert unverändert lassen, setzt man ihn beim Aufruf auf -1. Das Skript benötigt allerdings Superuser-Rechte, um diese Änderungen durchführen zu können. Ruft man chown mit ungenügenden Rechten auf, erhält man folgende Fehlermeldung: >>> os.chown("abc.py",1001,-1) Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 1] Operation not permitted: 'abc.py' Im folgenden Beispiel starten wir die interaktive Python-Shell mit den entsprechenden Superuser-Rechten: bernd@saturn:~/bodenseo/python$ sudo python3 [sudo] password for bernd: Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.chown("abc.py",1001,-1) ■ chroot(path) Auch zum Ausführen dieser Funktion benötigt das Skript Superuser-Rechte. Das rootVerzeichnis des aktuellen Prozesses wird mit diesem Kommando auf den Pfad „path” gesetzt. Im folgenden Beispiel setzen wir das root-Verzeichnis auf „/tmp”. >>> os.chroot("/tmp") 38.3 os-Modul ■ extsep Ein Attribut mit dem Wert des Separators für die Dateikennung4 , also „.” >>> os.extsep '.' ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ ■ fork( ) siehe 39 (Forks) forkpty( ) siehe 39 (Forks) get_exec_path(env=None) Die Funktion get__exec_path liefert die Liste der Verzeichnisse zurück, die zur Ausführung eines Programms durchsucht werden. Diese Verzeichnisse entsprechen den Verzeichnissen der Umgebungsvariablen PATH. Die Umgebungsvariable PATH wird standardmäßig in os.environ gesucht. Wird für den Parameter „env” ein Dictionary übergeben, so wird PATH dort gesucht. >>> os.get_exec_path() ['/home/bernd/perl5/bin', '/home/bernd/bin', '/usr/lib/lightdm/ lightdm', '/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/ usr/bin', '/sbin', '/bin', '/usr/games', '/usr/local/games', '/ home/writing/etrusker_ag/bin', '/home/bernd/bin', '.'] ■ ■ ■ ■ getcwd() Liefert das aktuelle Arbeitsverzeichnis als Unicode-String zurück. getcwdb( ) Liefert das aktuelle Arbeitsverzeichnis als Byte-String zurück. getegid( ) Liefert die effektive Gruppen-ID zurück. geteuid( ) Liefert die effektive Benutzer-ID5 zurück. Die effektive Benutzer-ID ist im Allgemeinen identisch mit der „normalen” Benutzer-ID. >>> os.getuid() 1000 ■ getgid( ) Liefert die Gruppen-ID zurück. >>> os.getgid() 1000 ■ getgroups( ) Die Funktion getgroups liefert eine Liste der Gruppen des aktuellen Prozesses zurück. >>> os.getgroups() [4, 24, 27, 30, 46, 107, 124, 1000] 4 5 file extension engl. user ID 443 444 38 Systemprogrammierung ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ setgroups(list) Die Gruppen des aktuellen Prozesses werden auf die Liste „list” gesetzt. getloadavg( ) Liefert die Anzahl der Prozesse in der Systemwarteschlange der laufenden Prozesse zurück. Die Zahlen entsprechen den Durchschnittswerten für die letzte Minute, die letzten fünf und die letzten 15 Minuten. Falls die Durchschnittsbelastung (load average) nicht ermittelt werden konnte, wird ein OSError generiert. >>> os.getloadavg() (0.07, 0.21, 0.22) ■ getlogin( ) Rückgabewert ist der aktuelle Login-Name. >>> os.getlogin() 'bernd' ■ ■ ■ ■ ■ getpgid(pid ) Die Funktion getpgid() liefert die Prozess-Gruppen-ID des Prozesses mit der Prozess-ID „pid” zurück. Falls der Wert von „pid” 0 ist, liefert getpgid() die Prozessgruppen-ID des aufrufenden Prozesses zurück. getpgrp( ) Liefert die Gruppen-ID des aktuellen Prozesses zurück. getpid( ) Liefert die Prozess-ID des laufenden Prozesses zurück. getppid( ) Liefert des Prozess-ID des Elternprozesses zurück. getresgid( ) Liefert ein Dreiertupel (rgid, egid, sgid) zurück. Dies entspricht den Werten „reale Gruppen-ID” (real group ID), „effektive Gruppen-ID” (effective groupID) und „gespeicherte Gruppen-ID” (saved group ID). >>> os.getresgid() (1000, 1000, 1000) ■ getresuid( ) Liefert ein Dreiertupel (ruid, euid, suid) zurück. Dies entspricht den Werten „reale Benutzer-ID” (real user ID), „effektive Benutzer-ID” (effective user ID) und „gespeicherte Benutzer-ID” (saved user ID). >>> os.getresuid() (1000, 1000, 1000) ■ ■ getuid( ) Liefert die Benutzer-ID vom aktuellen Prozess zurück. kill(pid, sig) Diese Funktion entspricht dem Linux/UNIX-Kommando „kill”. Der Befehl dient dazu, Signale „sig” zu verschicken. Standardmäßig wird bei dem UNIX-Kommando kill das Signal SIGTERM versendet, welches den entsprechenden Prozess dazu auffordert, sich zu 38.3 os-Modul beenden. Im Prinzip ist der Name „kill” irreführend, da das Beenden („Töten”) von Prozessen nur ein Anwendungsfall von kill ist. Allgemein dient kill der Interprozesskommunikation. Im folgenden Beispiel wollen wir einen Prozess mit der pid 7036 beenden: >>> import os >>> os.kill(7036,9) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ ■ ■ ■ killpg(pgid, sig) Eine Prozessgruppe wird mit einem Signal beendet. linesep( ) link(src, dst ) Diese Funktion erzeugt einen Hardlink „dst” auf die Datei „src”. listdir( ) >>> import os >>> os.listdir() ['a.txt', 'abc', 'c.txt', 'b.txt'] >>> os.listdir("/home/bernd/Documents") ['fritz.cfg','unterlagen.pdf', 'test.py', 'output.pdf', 'fritz2.cfg '] ■ ■ lstat(path) Wie stat(path), aber symbolischen Links wird nicht gefolgt. major(device) Die Funktion major extrahiert die Major-Nummer eines raw-Devices, die häufig vom st_dev oder st_rdev Feld von stat()-Ergebnis stammt. Beispiel: siehe minor-Funktion ■ minor(device) Die Funktion minor extrahiert die Minor-Nummer eines raw-Devices, die häufig vom st_dev oder st_rdev Feld von stat()-Ergebnis stammt. Beispiel: import os path = "ulysses.txt" info = os.lstat(path) major_no = os.major(info.st_dev) minor_no = os.minor(info.st_dev) print("Major Device Number :", major_no) print("Minor Device Number :", minor_no) Das Ergebnis sieht wie folgt aus: 445 446 38 Systemprogrammierung bernd@saturn:~/bodenseo/python/beispiele$ python3 os_major.py Major Device Number : 8 Minor Device Number : 7 bernd@saturn:~/bodenseo/python/beispiele$ ■ makedev( ) Diese Funktion erzeugt eine Gerätenummer aus der Minor- und der Major-Nummer. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiel: import os path = "ulysses.txt" info = os.lstat(path) major_no = os.major(info.st_dev) minor_no = os.minor(info.st_dev) print("Major Device Number :", major_no) print("Minor Device Number :", minor_no) dev_no = os.makedev(major_no, minor_no) print("Device Number :", dev_no) Folgende Ausgaben werden von obigem Skript erzeugt: bernd@saturn:~/bodenseo/python/beispiele$ python3 os_makedev.py Major Device Number : 8 Minor Device Number : 7 Device Number : 2055 ■ mkdir(path[, mode]) Die Funktion mkdir() entspricht der Linux/Unix-Funktion mkdir. Ein Verzeichnis „path” wird erzeugt, aber nur, wenn alle oberen Verzeichnisse bereits existieren. Im folgenden Beispiel führt der Versuch, das Verzeichnis „x2/x22/x33” zu erzeugen, zu einer Fehlermeldung, da bereits das Verzeichnis „x2” nicht existiert. Der optionale Parameter „mode” entspricht dem Modus, also z.B. „0755”. Beispiel: >>> import os >>> os.path.isdir("x1") False >>> os.mkdir("x1", 0755) >>> os.path.isdir("x1") True >>> os.path.isdir("x2") False >>> os.mkdir("x2/x22/x33") Traceback (most recent call last): 38.3 os-Modul File "<stdin>", line 1, in <module> OSError: [Errno 2] No such file or directory: 'x2/x22/x33' ■ makedirs(path[, mode] ) Wie der Befehl mkdir, aber makedirs erzeugt auch gegebenenfalls übergeordnete Verzeichnisse, wenn sie noch nicht existieren. Der optionale Parameter „mode” entspricht dem Modus, also z.B. „0755”. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiel: >>> import os >>> os.path.isdir("x2") False >>> os.makedirs("x2/x22/x33") >>> os.path.isdir("x2") True >>> os.path.isdir("x2/x22/x33") True ■ ■ mkfifo(path[, mode]) Funktion zum Erzeugen einer benannten Pipe mit Namen „path”. Optional kann der Modus mit dem Parameter „mode” gesetzt werden. nice(inc) Die Funktion vermindert die Priorität des Prozesses durch „inc” und liefert den Wert der neuen Priorität zurück. >>> >>> 2 >>> 5 >>> 6 >>> 11 ■ import os os.nice(2) os.nice(3) os.nice(1) os.nice(5) popen(command[, mode[, bufsize]] ) Die Funktion popen öffnet eine Pipe. os.popen eignet sich in besonderer Weise, ShellBefehle auszuführen, wie man es von der Kommandozeile des Betriebssystems gewohnt ist. Ein Kommandostring „command” wird dazu von der Default-Shell des Betriebssystems ausgeführt. Der Rückgabewert ist ein Objekt der Klasse „os._wrap_close”. Dieses Objekt dient auch dazu, gegebenenfalls die Ergebnisse des Kommandostrings „command” aufzunehmen. Damit hat man dann die Möglichkeit, im Python-Programm mit diesen Ergebnissen weiterzuarbeiten. Beim Modus „r”, d.h. lesend, werden die Ergebnisse der Ausführung des Kommandostrings „command” im os._wrap_close-Objekt gespeichert. Wählt man den Modus „w”, also schreibend, dann werden die Ergebnisse nicht an das os._wrap_close-Objekt übergeben, sondern an die Standardausgabe des laufenden Programms. 447 448 38 Systemprogrammierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Der Parameter „bufsize” bestimmt die Größe des zu verwendenden Puffers. Falls für „bufsize” der Wert 0 gewählt wird, erfolgt keine Pufferung. Ist der Wert auf 1 gesetzt, ist die Pufferung aktiviert, und zwar ohne Limit. Gibt man eine positive Zahl größer 1 an, entspricht dies der Puffergröße in Bytes. Ein negativer Wert entspricht dem DefaultVerhalten. In einem etwas umfangreicheren Beispiel wollen wir demonstrieren, wie man popen sowohl im „w”-Modus als auch im „r”-Modus betreibt. Zuerst suchen wir mit dem Shell-Kommando „grep catholic *.txt” aus allen Dateien mit der Extension „.txt” alle Zeilen, die das Wort „catholic” enthalten. Da wir popen im Modus „w” starten, wird die Shell-Ausgabe unmittelbar in die Ausgabe unseres Skripts weitergeleitet. Im zweiten popen starten wir den gleichen Befehl im „r”-Modus. Jetzt wird das Ergebnis in einem „os._wrap_close”-Objekt zurückgeliefert, das wir in einer Variablen „res” speichern. Im Folgenden separieren wir alle Wörter, die in diesen Zeilen vorkommen, und geben die Menge dieser Wörter aus, d.h. wir haben mehrfache Vorkommen auf eines reduziert: import os, re command = """ grep catholic *.txt """ print("""Kommando "command" wird im Modus "w" gestartet.""") print("""Die Ausgabe geht in die Standardausgabe des laufenden Programms!""") os.popen(command,"w") print(""" "command" wird wieder gestartet, aber nun im "r"-Modus :""") res = os.popen(command,"r") list_of_lines = list(res) words = [] for line in list_of_lines: line = line[12:] # Abschneiden des Präfixes "ulysses.txt:" words = words + re.split(r"\W+", line) words = set(words) print("""Wörter im Kontext von "catholic":""") print(words) Starten wir das Programm, erhalten wir folgende Ausgabe: bernd@saturn:~/bodenseo/python/beispiele$ python3 os_popen.py Kommando "command" wird im Modus "w" gestartet. Die Ausgabe geht in die Standardausgabe des laufenden Programms! "command" wird wieder gestartet, aber nun im "r"-Modus: ulysses.txt:the holy Roman catholic and apostolic church. ulysses.txt:of their brazen bells: _et unam sanctam catholicam et apostolicam Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 38.3 os-Modul ulysses.txt:Gill's, catholic club, the industrious blind. Why? Some reason. Sun or ulysses.txt:who is this used to say he was a queer breedy man great catholic all the ulysses.txt:are liege subjects of the catholic chivalry of Europe that foundered at ulysses.txt:the most Roman of catholics call _dio boia_, hangman god , is doubtless ulysses.txt:practical catholic: useful at mission time. ulysses.txt:--You as a good catholic, he observed, talking of body and soul, believe ulysses.txt:--That's right, the old tarpaulin corroborated. The Irish catholic ulysses.txt:a cocked hat. He infinitely preferred the sacred music of the catholic ulysses.txt:corporation emergency dustbuckets, the Roman catholic church, ulysses.txt:prepuce, the carnal bridal ring of the holy Roman catholic apostolic ulysses.txt:Roman catholicism at the epoch of and with a view to his matrimony Wörter im Kontext von "catholic": {'', 'all', 'mission', 'queer', 'useful', 'Gill', 'industrious', ' corroborated', 'infinitely', 'to', 'bridal', 'hat', 'good', ' emergency', 'preferred', 'sanctam', '_dio', 'view', 'right', ' old', 'Sun', 'soul', 'observed', 'are', 'carnal', 'et', 'That', 'god', 'He', 'body', 'doubtless', 'apostolic', 'reason', '_et', 'great', 'of', 'matrimony', 'practical', 's', 'or', 'blind', ' dustbuckets', 'chivalry', 'cocked', 'apostolicam', 'church', ' ring', 'Europe', 'liege', 'catholicism', 'tarpaulin', 'their', ' call', 'prepuce', 'music', 'was', 'boia_', 'catholics', 'holy', 'that', 'club', 'believe', 'with', 'he', 'Irish', 'this', 'as', 'breedy', 'catholicam', 'unam', 'and', 'is', 'talking', 'say', ' his', 'at', 'You', 'foundered', 'catholic', 'corporation', ' epoch', 'subjects', 'sacred', 'Why', 'bells', 'used', 'who', ' brazen', 'most', 'Roman', 'hangman', 'The', 'man', 'a', 'Some', 'time', 'the'} ■ readlink( ) Die Funktion readlink liefert einen String zurück, der den kompletten Pfad enthält, auf den ein symbolischer Link „path” zeigt: >>> import os >>> os.readlink("proj") '/home/data/projects/' ■ remove(path ) Entspricht der Funktion unlink. Eine Datei „path” wird entfernt. 449 450 38 Systemprogrammierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ removedirs(path) Ein Verzeichnis wird rekursiv entfernt. Handelt es sich um das einzige Unterverzeichnis des Elternprozesses, so wird auch der Elternprozess entfernt. Dies wird rekursiv solange fortgesetzt, bis ein Pfad nicht mehr das einzige Element eines Elternprozesses ist. bernd@saturn:~/tmp$ tree x x +-- y | +-- a | |-- b | +-- c +-- z |-- a +-- b 7 directories, 0 files Aus dem folgenden Beispiel lernen wir, dass wir keine Verzeichnisse mit removedirs löschen können, die nicht leer sind. Der anfängliche Versuch, das Unterverzeichnis y mit os.removedirs("x/y") zu löschen, scheitert. Nachdem alles aus y gelöscht ist außer dem Verzeichnis c, führt die Ausführung von os.removedirs("x/y/c") auch zu einer Löschung des Elternverzeichnisses „y”. >>> import os >>> os.removedirs("x/y") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.2/os.py", line 179, in removedirs rmdir(name) OSError: [Errno 39] Directory not empty: 'x/y' >>> os.removedirs("x/y/a") >>> os.removedirs("x/y/b") >>> os.removedirs("x/y/c") >>> os.removedirs("x/y") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.2/os.py", line 179, in removedirs rmdir(name) OSError: [Errno 2] No such file or directory: 'x/y' Nach obigen Anweisungen sieht unser Baum wie folgt aus: bernd@saturn:~/tmp$ tree x x +-- z |-- a +-- b 3 directories, 0 files 38.3 os-Modul ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ rename(src, dst) Mit rename kann eine Datei oder Verzeichnis von „src” nach „dst” umbenannt werden. Falls der neue Name „dst” bereits existiert, erfolgt eine Fehlermeldung. renames(old, new) >>> import os >>> tree = os.walk("x") >>> list(tree) [('x', ['y', 'z'], ['b.txt', 'a.txt']), ('x/y', ['a', 'b', 'c'], ['b .txt']), ('x/y/a', [], []), ('x/y/b', [], []), ('x/y/c', [], []) , ('x/z', ['a', 'b'], []), ('x/z/a', [], []), ('x/z/b', [], [])] >>> os.renames("x", "x_new") >>> tree2 = os.walk("x_new") >>> list(tree2) [('x_new', ['y', 'z'], ['b.txt', 'a.txt']), ('x_new/y', ['a', 'b', ' c'], ['b.txt']), ('x_new/y/a', [], []), ('x_new/y/b', [], []), ('x_new/y/c', [], []), ('x_new/z', ['a', 'b'], []), ('x_new ■ rmdir(path) Das Verzeichnis „path” wird entfernt, aber nur, wenn es leer ist. Falls es nicht leer ist, wird die Ausnahme „OSError: [Errno 39] Directory not empty: ’x_new’” generiert: >>> import os >>> os.rmdir("x_new") Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 39] Directory not empty: 'x_new' Will man ein nicht leeres Verzeichnis löschen, empfiehlt es sich, die Funktion rmtree des Moduls shutil zu benutzen: >>> import shutil >>> shutil.rmtree("x_new") ■ ■ ■ ■ ■ ■ ■ setegid(gid) Die effektive Gruppen-ID des aktuellen Prozesses wird auf „gid” gesetzt. setgid(gid) Die Gruppen-ID des aktuellen Prozesses wird auf „gid” gesetzt. seteuid(uid) Die effektive User-ID des aktuellen Prozesses wird auf „uid” gesetzt. setuid( ) Die User-ID des aktuellen Prozesses wird auf „uid” gesetzt. setregid(rgid, egid) Die effektive und reale Gruppen-ID des Prozesses wird gesetzt. setresgid(rgid, egid, sgid) Die effektive, reale und gespeicherte Gruppen-ID des Prozesses wird gesetzt. setpgid( ) Der Systemaufruf setpged() wird ausgeführt. 451 452 38 Systemprogrammierung ■ ■ ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ ■ setpgrp( ) Der aktuelle Prozess wird zum Gruppenleader. setresuid(ruid, euid, suid) Die effektive, reale und gespeicherte User-ID des Prozesses wird gesetzt. setreuid(ruid, euid) Die effektive und reale User-ID des Prozesses wird gesetzt. setsid( ) Der Systemaufruf setsid() wird aufgerufen. stat() Ein stat-Systemaufruf wird durchgeführt. Die Ergebniswerte sind: – st_mode – protection bits, – st_ino – Inode-Nummer – st_dev – Gerätenummer (device) – st_nlink – Anzahl der Hardlinks – st_uid – User-ID des Besitzers – st_gid – Gruppen-ID des Besitzers – st_size – Dateigröße – st_atime – letzte Zugriffszeit auf die Datei – st_mtime – letzte Modifikationszeit der Datei – st_ctime – plattformabhängig: Zeit der letzten Änderung der Metadaten unter Unix/Linux und die Entstehungszeit der Datei unter Windows: >>> import os >>> os.stat("b.txt") posix.stat_result(st_mode=33204, st_ino=16781124, st_dev=2055, st_nlink=1, st_uid=1000, st_gid=1000, st_size=0, st_atime =1361693338, st_mtime=1361693338, st_ctime=1361693475) ■ stat_float_times([newval]) Diese Funktion dient dazu, das Ausgabeverhalten von os.[lf]stat zu beeinflussen: Falls „newval” True ist, liefern zukünftige Aufrufe von stat() (bzw. lstat, fstat) float-Werte zurück. Wenn „newval” False ist, werden zukünftige Aufrufe Integers zurückliefern. Ruft man die Funktion ohne Parameter auf, wird die aktuelle Einstellung zurückgeliefert. ■ strerror(code) Zu einer gegebenen Fehlernummer „code” wird der entsprechende Fehlerstring zurückgeliefert. Im folgenden Beispiel lassen wir uns die ersten 10 von 132 Fehlermeldungen anzeigen: >>> import os >>> for i in range(1,11): ... print(i, os.strerror(i)) ... Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 38.3 os-Modul 1 Operation not permitted 2 No such file or directory 3 No such process 4 Interrupted system call 5 Input/output error 6 No such device or address 7 Argument list too long 8 Exec format error 9 Bad file descriptor 10 No child processes ■ symlink(src, dst) symlink erzeugt einen Link „dst”, der auf „src” zeigt. Im folgenden Beispiel erzeugen wir zunächst einen symbolischen Link namens „link2abc.py” auf die Datei „abc.py” mit der Funktion symlink: >>> os.symlink("abc.py", "link2abc.py") >>> os.path.islink("abc.py") False >>> os.path.islink("link2abc.py") True ■ sysconf( ) Dieser Befehl steht nur unter Linux und anderen Unix-Betriebssystemen zur Verfügung und entspricht dem Betriebssystembefehl sysconf des Betriebssystems. ■ system( command ) „command” ist ein Kommandostring mit einem gültigen Befehl oder einer Befehlsfolge der voreingestellten Betriebssystemshell, also beispielsweise der Bash in Linux. Dieser Kommandostring wird in einer Untershell ausgeführt. Der Rückgabewert ist der ExitStatus des Shellbefehls, also die „0” im folgenden Beispiel: >>> os.system("for i in *.pdf; do echo $i; done") Python.pdf Galois.pdf 0 >>> Diese Funktionalität ist veraltet. Man sollte stattdessen das Modul subprocesses benutzen. ■ times( ) times liefert ein Tupel mit den verschiedenen Prozesszeiten zurück: (utime, stime, cutime, cstime, elapsed_time) >>> os.times() (0.14, 0.04, 0.04, 0.01, 17210084.65) ■ umask( ) 453 454 38 Systemprogrammierung ■ uname( ) Die Funktion uname liefert ein Tupel zurück, welches die folgenden Informationen über das Betriebssystem enthält: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. (sysname, nodename, release, version, machine) >>> import os >>> os.uname() ('Linux', 'saturn', '3.5.0-23-generic', '#35-Ubuntu SMP Thu Jan 24 13:05:29 UTC 2013', 'i686') ■ ■ unlink(path) Eine Datei wird entfernt. Entspricht der Funktion remove(path). urandom(n) Die Funktion urandom liefert n Zufallsbytes zurück, die für kryptografische Anwendungen benutzt werden können: >>> os.urandom(10) b'9\xc3\xe9\x02\xe8\xa2z\xea\xfcQ' >>> os.urandom(10) b'\xdd\x06\x9fK\x17{H{>\xa6' >>> os.urandom(10) b'^)\xdb\x9c\x8b\x99\x82\x96\x89\xc4' ■ ■ ■ ■ ■ utime(path, (atime, mtime) ) Die letzte Zugriffszeit (access time) und die Modifikationszeit (modification time) einer Datei werden auf die Werte des Tupels „(atime, mtime)” gesetzt. Werden keine Zeiten angegeben, werden die Zeiten der Datei „path” auf die aktuelle Zeit gesetzt. wait() Wartet darauf, dass ein Kindprozess terminiert, und liefert ein Tupel zurück, das die PID und den exit-Status enthält, also das Tupel (pid, status). wait3( ) Wie wait, aber es wird ein 3-Tupel (pid, status, rusage) zurückgeliefert. „rusage” bezeichnet die „resource”-Benutzungsinformation. waitpid(pid, options) Wie wait, aber mit den Parametern „pid” und „status”. walk( ) Betrachten wir folgenden Teilbaum eines Dateisystems: 38.3 os-Modul Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Nun wenden wir walk auf diesen Teilbaum an. Wir starten Python in dem Verzeichnis, in dem sich das Unterverzeichnis abc befindet: >>> import os >>> os.walk("abc") <generator object walk at 0xb714ba7c> >>> for path in os.walk("abc"): ... print(path) ... ('abc', ['a', 'b', 'c'], []) ('abc/a', [], ['a1.txt', 'a2.txt']) ('abc/b', [], ['b2.txt', 'b3.txt', 'b1.txt']) ('abc/c', [], ['c1.txt']) Die Funktion eignet sich also ideal, um einen Dateibaum rekursiv zu durchwandern. 38.3.6 os.path - Arbeiten mit Pfaden Man kann das Modul „path” auf zwei Arten importieren: entweder direkt mit „import os.path” oder indirekt über „import os” ■ abspath(name) Liefert einen Pfad zurück, der sich aus dem aktuellen Arbeitsverzeichnis und dem Wert von Namen zusammensetzt. Dabei spielt es keine Rolle, ob der daraus resultierende Pfad wirklich existiert. Im folgenden Beispiel befinden wir uns im Homeverzeichnis des Benutzers bernd. Dort gibt es ein Unterverzeichnis „python”, aber keines, was „nonsense” heißt: bernd@saturn:~$ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.path.abspath(".") '/home/bernd' >>> os.path.abspath("python") '/home/bernd/python' >>> os.path.abspath("nonsense") '/home/bernd/nonsense' ■ basename(p) Liefert die letzte Komponente eines Pfadnamens zurück. Beispiel: >>> os.path.basename("/home/bernd/nonsense") 'nonsense' >>> os.path.basename("/home/bernd/hellO\_world.py") 455 456 38 Systemprogrammierung 'hellO\_world.py' >>> os.path.basename("/home/bernd/python/") '' ■ commonprefix(pf ) Liefert den größtmöglichen Pfad zurück, mit dem alle Pfade der Liste „pf” beginnen. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiel: >>> paths = ["/home/bill/Documents", "/home/bill/bin", "/home/bill/ Documents/articles/"] >>> os.path.commonprefix(paths) '/home/bill/' ■ sep Attribut mit dem Trennsymbol in Pfaden, also in Linux „/” und unter Windows „\\”. >>> os.sep '/' ■ dirname(p) Liefert den Anfang des Pfades „p” bis zum letzten Schrägstrich „/” zurück: >>> path = "/home/bill/documents" >>> os.path.dirname(path) '/home/bill' >>> path = "/home/bill/documents/" >>> os.path.dirname(path) '/home/bill/documents' >>> path = "/home/bill/documents/beispiel.py" >>> os.path.dirname(path) '/home/bill/documents' ■ exists(path) Mit dieser Funktion kann man testen, ob ein Pfad existiert, d.h. „True” wird zurückgeliefert, wenn ein Pfad existiert, ansonsten „False”. „False” wird auch zurückgeliefert, wenn es sich um einen „broken link” handelt. Beispiel: Bei der Funktion „abspath” hatten wir vermerkt, dass in „/home/bernd/” ein Unterverzeichnis „python” existiert, aber keines mit Namen „nonsense”. Im Folgenden prüfen wir dies mittels „exists” nach: bernd@saturn:~$ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.path.exists("/home/bernd/python") True >>> os.path.exists("/home/bernd/nonsense") False 38.3 os-Modul ■ expanduser(path) „~” und „~user” werden durch den vollständigen Pfad ersetzt, d.h. „~” wird mit dem Wert der Shell-Umgebungsvariablen $HOME ersetzt bzw. mit dem Homeverzeichnis des Benutzers „user”. Falls $HOME nicht gesetzt ist oder im Fall von „~user” der Benutzer „user” nicht existiert, wird der Wert von „path” nicht verändert. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Im folgenden Beispiel existiert kein Benutzer mit Namen „homer”, aber einer namens „bill”: >>> os.path.expanduser("~") '/home/bernd' >>> os.path.expanduser("~homer") '~homer' >>> os.path.expanduser("~bill") '/home/bill' >>> ■ expandvars(path) Variablen bzw. Variablen in Pfaden werden durch ihren Wert ersetzt. Falls eine Variable nicht definiert ist, bleibt sie unverändert: >>> os.path.expandvars("$SHELL") '/bin/bash' >>> os.path.expandvars("$ABC") '$ABC' >>> os.path.expandvars("/home/$USER/") '/home/bernd/' ■ getatime(filename) Liefert für einen existierenden Datei- oder Verzeichnisnamen die letzte Zugriffszeit zurück. Der Name kann sowohl durch einen relativen Pfad als auch durch einen absoluten Pfad gegeben sein. Falls ein Name nicht existiert, wird ein OSError produziert mit der Meldung: „ No such file or directory:”. atime entspricht der Unix- bzw. Linux-atime, also dem letzten lesenden oder schreibenden Zugriff auf die Datei. Beispiel: >>> import os >>> os.path.getatime("/home/bernd/bodenseo/python/buch.tex") 1361384210.8701274 >>> os.path.getatime("buch.tex") 1361384210.8701274 >>> os.path.getatime("buecher.tex") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.2/genericpath.py", line 59, in getatime return os.stat(filename).st_atime OSError: [Errno 2] No such file or directory: 'buecher.tex' 457 458 38 Systemprogrammierung ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ getctime(filename) Wie getatime, jedoch wird hier die ChangeTime, also die Zeit der letzten Änderung an der Datei ausgegeben. Änderung bedeutet hier, dass entweder der Inhalt der Datei selbst oder die Metadaten6 der Datei geändert wurden. getmtime(filename) Diese Funktion ist analog zu getctime und getatime. Die mtime ändert sich jedes Mal, wenn in eine Datei geschrieben wird, also auch, wenn sie erstellt wird. Die mtime ändert sich aber im Unterschied zur ctime nicht, wenn sich die Metadaten einer Datei ändern. Wir schauen uns nun getatime, getmtime und getctime im Zusammenspiel an. Zunächst erzeugen wir eine leere Datei names „abc.py”. Wir erkennen, dass die ctime und die mtime gleich sind, während atime einen etwas früheren Wert aufweist; die atime wird beim Schreiben nicht mehr verändert: >>> fh = open("abc.py","w") >>> fh.close() >>> os.path.getatime("abc.py") 1361433115.8920438 >>> os.path.getmtime("abc.py") 1361433693.0429058 >>> os.path.getctime("abc.py") 1361433693.0429058 Nun ändern wir die Zugriffsrechte der Datei „abc.py”. Wie erwartet ändert sich nun nur die ctime, da sich die mtime nur ändert, wenn wirklich der Inhalt einer Datei verändert wurde, was ja hier nicht der Fall ist: >>> os.chmod("abc.py",755) >>> os.path.getatime("abc.py") 1361433115.8920438 >>> os.path.getmtime("abc.py") 1361433693.0429058 >>> os.path.getctime("abc.py") 1361433817.0595207 Jetzt ändern wir wirklich den Inhalt, indem wir weiteren Text an die Datei anhängen. Wir erkennen, dass nun sowohl getmtime und getctime die gleichen Werte zurückliefern: >>> fh = open("abc.py","a") >>> fh.write("Just some spam!") 15 >>> fh.close() >>> os.path.getatime("abc.py") 1361433115.8920438 >>> os.path.getmtime("abc.py") 1361433951.7121885 >>> os.path.getctime("abc.py") 1361433951.7121885 6 Als Metadaten bezeichnet man in diesem Zusammenhang „Zugriffsrechte”, „Besitzer” oder Ähnliches. 38.3 os-Modul Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ getsize(filename) Liefert die Dateigröße zurück, d.h. die Anzahl der Bytes. >>> >>> >>> 0 >>> >>> 17 >>> >>> 17 ■ fh = open("abc.py","w") fh.close() os.path.getsize("abc.py") fh = open("abc.py","a") fh.write("Do you like spam?") fh.close() os.path.getsize("abc.py") isabs(s) Die Funktion prüft, ob es sich bei einem Dateinamen um einen absoluten oder einen relativen Dateinamen handelt. Dabei spielt es keine Rolle, ob ein Pfadname wirklich im Dateisystem existiert. Es handelt sich um eine reine syntaktische Prüfung. >>> os.path.isabs("/monty/python") True >>> os.path.isabs("python") False >>> os.path.exists("/monty/python") False Achtung: Obwohl es sich bei Pfadnamen wie „~/Documents” oder „~monty” um absolute Pfadnamen handelt, wird in diesem Fällen jedoch der Wert „False” zurückgeliefert: >>> os.path.isabs("~/python") False >>> os.path.isabs("~monty/python") False ■ isdir(s) Liefert True zurück, wenn der Pfadname „s” ein existierendes Verzeichnis bezeichnet: >>> os.path.isdir("beispiele") True >>> os.path.isdir("monty") False >>> os.path.isdir("abc.py") False >>> os.path.exists("abc.py") True >>> os.path.exists("/home/bernd/") True 459 460 38 Systemprogrammierung Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ isfile(path) Liefert True zurück, wenn der Pfadname „path” eine existierende Datei bezeichnet: >>> os.path.isfile("abc.py") True >>> os.path.isfile("/home/bernd/bodenseo/python/abc.py") True >>> os.path.isfile("/home/bernd/bodenseo/python/spam.py") False ■ islink(path) Liefert True zurück, wenn es sich bei „path” um einen symbolischen Link handelt, ansonsten wird False zurückgegeben. Im folgenden Beispiel erzeugen wir zunächst einen symbolischen Link namens „link2abc.py” auf die Datei „abc.py” mit der Funktion symlink: >>> os.symlink("abc.py", "link2abc.py") >>> os.path.islink("abc.py") False >>> os.path.islink("link2abc.py") True ■ ismount(path) Testet, ob es sich bei dem Pfad „path” um einen Mount-Punkt handelt: >>> os.path.ismount("/media/bernd/") False >>> os.path.ismount("/media/bernd/CDD8-B9BF") True ■ join(a, *p) Zwei oder mehr Pfadnamenskomponenten werden vereinigt, wobei ein Schrägstrich „/” bei Bedarf eingefügt wird. Falls eine der Komponenten ein absoluter Pfadname ist, werden alle vorher stehenden Komponenten weggelassen. Steht als letzte Komponente ein leerer String, wird ein Schrägstrich angefügt, falls der Pfad sonst nicht mit einem Schrägstrich geendet hätte. >>> os.path.join("abc","def","xyz") 'abc/def/xyz' >>> os.path.join("/home/monty","def","xyz") '/home/monty/def/xyz' >>> os.path.join("abc","/home/monty","xyz") '/home/monty/xyz' >>> os.path.join("abc","home/monty","xyz") 'abc/home/monty/xyz' >>> os.path.join("abc","home/monty","xyz", "") 'abc/home/monty/xyz/' >>> os.path.join("abc","home/monty","xyz/") 'abc/home/monty/xyz/' 38.3 os-Modul Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ lexists(path) Liefert True zurück, wenn der Pfad „path” existiert. Falls path ein symbolischer Link ist, wird auch True zurückgeliefert, wenn es sich um einen „broken link” handelt. Zum Verständnis des folgenden Beispiels muss man wissen, dass es in dem existierenden Verzeichnis „/home/bernd/” kein Unterverzeichnis „monty” gibt und dass es sich bei „/home/bernd/bodenseo/python/monty” um einen „broken link” handelt. >>> os.path.lexists("/home/bernd/monty") True >>> os.path.lexists("/home/bernd/monty") False >>> os.path.lexists("/home/bernd/bodenseo/python/monty") True ■ normcase(s) Normalisiert Groß- und Kleinschreibung eines Pfades. Diese Funktion hat keine Wirkung unter Posix, also nicht unter Linux und Unix. Unter Windows spielt aber Großund Kleinschreibung bei Pfad- und Dateinamen keine Rolle, d.h. „ABC.PY”, „abc.py” oder „Abc.py” sind alles gültige Schreibweisen für das gleiche Dateiobjekt. Wendet man normcase unter Windows auf diese Namen an, erhält man jeweils den Namen komplett in Kleinschreibung. Beispiel unter Windows: >>> os.path.normcase("ABC.PY") 'abc.py' >>> os.path.normcase("Abc.py") 'abc.py' >>> os.path.normcase("abc.py") 'abc.py' >>> os.path.normcase("c:/Users/Bernd/") 'c:\\users\\bernd\\ Unter Linux sieht es hingegen wie folgt aus: >>> os.path.normcase("ABC.PY") 'ABC.PY' >>> os.path.normcase("Abc.py") 'Abc.py' >>> os.path.normcase("abc.py") 'abc.py' ■ normpath(path) Der Pfad „path” wird normalisiert, d.h. mehrfache Schrägstriche und Schrägstriche am Ende eines Pfades werden entfernt. >>> os.path.normpath("abc.py/") 'abc.py' >>> os.path.normpath("/home///bernd//abc.py/") '/home/bernd/abc.py' 461 462 38 Systemprogrammierung ■ realpath(filename) Die Funktion „realpath” liefert den kanonischen Pfad, d.h. ein Pfad ohne symbolische Links, eines Pfades „filename” zurück. Symbolische Links werden dabei aufgelöst, wie wir im Beispiel sehen können. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. In der folgenden interaktiven Python-Shell befinden wir uns im Verzeichnis „/home/ bernd/bodenseo/python/beispiele/intranet”. Dort legen wir einen symbolischen Link „intranet” auf „/var/www/intranet” an: >>> os.symlink("/var/www/intranet", "intranet") >>> os.path.realpath("intranet") '/home/data/intranet' >>> os.path.realpath("/home/bernd/bodenseo/python/beispiele/intranet ") '/home/data/intranet' ■ relpath(path, start=None) Es wird ein Pfadname relativ zur aktuellen Position für den Pfad „path” generiert: >>> os.getcwd() '/home/data/bodenseo/python/beispiele' >>> os.path.relpath("/home/bernd/") '../../../../bernd' ■ samefile(f1, f2) Es wird getestet, ob zwei Pfadnamen die gleiche Datei referenzieren. Es wird nicht geprüft, ob zwei Dateien den gleichen Inhalt haben, wie wir im Folgenden auch erkennen können: >>> os.path.samefile("abc.py", "../python/abc.py") True >>> os.symlink("abc.py", "monty.py") >>> os.path.samefile("abc.py", "monty.py") True >>> import shutil >>> shutil.copyfile("abc.py", "beispiele/abc.py") >>> os.path.samefile("beispiele/abc.py", "monty.py") False >>> os.path.samefile("beispiele/abc.py", "abc.py") False ■ split(p) Ein Pfadname wird in den Dateinamen bzw. das tiefste Verzeichnis und den Basispfad, also alles vor dem letzten Schrägstrich aufgeteilt. Beide Teile können jeweils leer sein. Beispiel: >>> os.path.split("/abc/xyz/beispiel.py") ('/abc/xyz', 'beispiel.py') >>> os.path.split("/abc/xyz/") ('/abc/xyz', '') 38.4 shutil-Modul >>> os.path.split("/abc/xyz") ('/abc', 'xyz') >>> os.path.split("xyz.py") ('', 'xyz.py') Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ splitdrive(p) Diese Funktion ist im Prinzip auf das Windows-Betriebssystem zugeschnitten. Ein Pfadname wird in den Laufwerksnamen (drive) und den Rest des Pfades zerlegt. Unter Linux und Unix ist der erste Wert immer der leere String, weil es keine Laufwerkskomponente geben kann. Das folgende Beispiel wurde unter Windows erstellt: >>> os.path.splitdrive("c:\\windows\users") ('c:', '\\windows\\users') Unter Linux sieht es wie folgt aus, auch wenn der Pfad keinen Sinn ergibt: >>> os.path.splitdrive("c:/abc/xyz/beispiel.py") ('', 'c:/abc/xyz/beispiel.py') ■ splitext(p) Die Dateierweiterung wird von einem Pfad abgesplittet, d.h. alles, was nach dem letzten Punkt folgt: >>> os.path.splitext("/abc/xyz/beispiel.py") ('/abc/xyz/beispiel', '.py') >>> os.path.splitext("/abc/xyz/beispiel") ('/abc/xyz/beispiel', '') >>> os.path.splitext("/abc/xyz/beispiel/") ('/abc/xyz/beispiel/', '') >>> 38.4 shutil-Modul Im vorigen Abschnitt hatten wir bereits eine Datei unter Benutzung des shutil-Moduls kopiert. Viele Python-Nutzer, die shutil noch nicht kennen, vermuten die Kopierfunktion zunächst im os-Modul. Dieses Modul stellt eine plattformunabhängige Schnittstelle zur Verfügung, um Dateien und Dateibäume zu kopieren, zu entfernen oder zu archivieren. Bei der Beschreibung der folgenden Funktionen benutzen wir häufig die String-Parameter „src” und „dst”. Dabei bezeichnet „src” die Quelldatei7 bzw. das Quellverzeichnis, und „dst” steht für die Zieldatei bzw. Zielverzeichnis.8 7 8 src steht für das englische Wort source, was auf Deutsch Quelle bedeutet. dst steht dabei als Abkürzung für destination, was auf Deutsch Ziel bedeutet. 463 464 38 Systemprogrammierung ■ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ ■ ■ copyfile(src, dst) Kopiert die Datei „src” nach „dst”. Wenn die Datei unter dst bereits existiert, wird sie überschrieben. Dabei muss der Pfad dst schreibbar sein. Ansonsten wird ein IOError geworfen. copy(src, dst) Kopiert die Datei src nach dst unter Beibehaltung der Zugriffsrechte. Existiert „dst” bereits, wird „dst” überschrieben. Entspricht „dst” einem Ordner, wird innerhalb dieses Ordners eine Datei mit dem Dateinamen von src angelegt oder überschrieben, falls diese Datei bereits in „dst” existiert. copy2(src, dst) Wie copy, allerdings werden auch die Zugriffsrechte, der Eigentümer und der Zeitstempel mitkopiert. Der Befehl entspricht dem Unix/Linux-Befehl: „cp -p src dst” copyfileobj(fsrc, fdst, length=16384) Der Inhalt des zum Lesen geöffneten Dateiobjekts „fsrc” wird in das zum Schreiben geöffnete „fdst”-Objekt kopiert. Mit dem optionalen Parameter kann gesteuert werden, wie viele Bytes jeweils gelesen und anschließend gespeichert werden. >>> >>> >>> >>> >>> ■ ■ ■ fh = open("abc.txt") fhw = open("spam.txt", "w") shutil.copyfileobj(fh,fhw) fh.close() fhw.close copymode(src, dst) Die Bits für die Zugriffsrechte werden von „src” nach „dst” kopiert. copystat(src, dst) Alle Statusinformationen, d.h. mode bits, atime, mtime, flags, werden von „src” nach „dst” kopiert. copytree(src, dst, symlinks=False, ignore=None, copy_function=<function copy2>, ignore_dangling_symlinks=False) Ein Verzeichnisbaum „src” wird rekursiv in einen Verzeichnisbaum „dst” kopiert. Wir betrachten das Beispiel, das wir bereits in Beispiel 38.3.5 (Weitere Funktionen im Überblick) kennengelernt haben: >>> import shutil >>> shutil.copytree("abc","xyz") Der Baum „dst” darf noch nicht existieren. Wenn wir obigen Befehl wieder anwenden, erhalten wir deshalb eine Fehlermeldung: >>> shutil.copytree("abc","xyz") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.2/shutil.py", line 203, in copytree os.makedirs(dst) File "/usr/lib/python3.2/os.py", line 152, in makedirs mkdir(name, mode) OSError: [Errno 17] File exists: 'xyz' 38.4 shutil-Modul Interessant ist vor allen Dingen der Fall, wenn es sich bei dem Pfadnamen „dst” um einen Pfad handelt, der im Innern des durch „src” bezeichneten Teilbaums liegt. Anders ausgedrückt ist „src” ein Teilstring von „dst”, wenn man von Pfadnamen in kanonischer Darstellung ausgeht (also ohne Links). Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir kopieren nun den gesamten Teilbaum „abc” in das Verzeichnis „abc” mit dem neuen Namen „xyz”, also parallel zu den Verzeichnissen „a”, „b” und „c”. Mithilfe der Funktion walk des Moduls os schauen wir uns die Bäume jeweils an: >>> import shutil >>> import os >>> os.walk("abc") <generator object walk at 0xb71d6054> >>> list(os.walk("abc")) [('abc', ['a', 'b', 'c'], []), ('abc/a', [], ['a1.txt', 'a2.txt']), ('abc/b', [], ['b2.txt', 'b3.txt', 'b1.txt']), ('abc/c', [], [' c1.txt'])] >>> shutil.copytree("abc","abc/xyz") >>> list(os.walk("abc")) [('abc', ['xyz', 'a', 'b', 'c'], []), ('abc/xyz', ['a', 'b', 'c'], []), ('abc/xyz/a', [], ['a1.txt', 'a2.txt']), ('abc/xyz/b', [], ['b2.txt', 'b3.txt', 'b1.txt']), ('abc/xyz/c', [], ['c1.txt']), ('abc/a', [], ['a1.txt', 'a2.txt']), ('abc/b', [], ['b2.txt', ' b3.txt', 'b1.txt']), ('abc/c', [], ['c1.txt'])] Falls der optionale Parameter symlinks auf True gesetzt ist, werden symbolische Links im Quellbaum auch als symbolische Links im Zielbaum realisiert. Ist der Parameter auf False gesetzt, was per Default der Fall ist, werden die Inhalte der Dateien, auf die symbolische Links zeigen, kopiert. Falls ein symbolischer Link nicht existiert, wird dies als Fehler ausgegeben. Um diese Fehlermeldung zu unterdrücken, setzt man den optionalen Parameter ignore_dangling_symlinks auf True. Der optionale „ignore”-Parameter ist ein aufrufbares Objekt wie zum Beispiel eine Funktion. Es erhält den Pfad „src” und eine Liste der Namen „names”, die von copytree besucht werden, als Parameter, so wie sie von os.listdir() erzeugt wird. Das aufrufbare Objekt liefert dann eine Liste „ignored_names” zurück. Also eine Liste der Namen, die nicht von copytree kopiert werden sollen. Man kann auch eine eigene Funktion übergeben, die festlegt, wie die Dateien zu kopieren sind. Dazu dient der optionale Parameter copy_function. Als Default wird copy2() benutzt. ■ get_archive_formats() Liefert eine Liste der unterstützten Formate zum Archivieren und Dearchivieren zurück. Jedes Element dieser Ergebnisliste ist ein Zweiertupel mit dem Namen und einer Beschreibung: >>> shutil.get_archive_formats() [('bztar', "bzip2'ed tar-file"), ('gztar', "gzip'ed tar-file"), (' tar', 'uncompressed tar file'), ('zip', 'ZIP file')] 465 466 38 Systemprogrammierung ■ get_unpack_formats() Liefert eine Liste der Dekomprimierformate zurück. Jedes Element der Ergebnisliste entspricht einem Tupel der Form: (Name, Extension, Beschreibung) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. >>> shutil.get_unpack_formats() [('bztar', ['.bz2'], "bzip2'ed tar-file"), ('gztar', ['.tar.gz', '. tgz'], "gzip'ed tar-file"), ('tar', ['.tar'], 'uncompressed tar file'), ('zip', ['.zip'], 'ZIP file')] ■ ignore_patterns(*patterns) Diese Funktion kann als „ignore”-Parameter bei copytree benutzt werden. Die „patterns” entsprechen glob-Pattern der Linux-Shells. ■ make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, dry_ run=0, owner=None, group=None, logger=None) Eine Archivdatei wird erzeugt. Dafür stehen die gängigen Archivierungsformate zur Verfügung, wie zum Beispiel das zip- oder tar-Format. Die Funktion liefert den vollständigen Pfad und Namen der erzeugten Archivdatei zurück. „base_name” ist der Name der Archivdatei, die ohne die übliche Endung erzeugt werden soll. Der vollständige Name der Archivdatei ergibt sich mit dem Parameter „format”, welcher der Extension des Archivformats entspricht, also „zip”, „tar”, „bztar” oder „gztar”. Der vollständige Name der Archivdatei wird somit durch die folgende Konkatenation gebildet: base_name + "."+ format „root_dir” entspricht dem Root-Verzeichnis, von dem relative Pfadnamen in base_dir beginnen. „base_dir” ist das Root-Verzeichnis des zu archivierenden Teilbaums. Im folgenden Beispiel wird ein Verzeichnisbaum mit dem Namen „./abc” archiviert. Dieser Baum muss sich im aktuellen Unterverzeichnis befinden, denn „root_dir” ist auf „.” gesetzt: >>> import shutil >>> shutil.make_archive("my_archive", "bztar", ".", "./abc") '/home/bernd/tmp/my_archive.tar.bz2' Die erzeugte Archivdatei befindet sich nun im aktuellen Verzeichnis. Nun rufen wir obigen Befehl in einem Verzeichnis auf, in dem sich kein Unterverzeichnis „./abc” befindet, d.h. in unserem Beispiel das Homeverzeichnis des Benutzers „bernd”. Wir erhalten dann eine Fehlermeldung: >>> import shutil >>> shutil.make_archive("my_archive", "bztar", ".", "./abc") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.2/shutil.py", line 585, in make_archive filename = func(base_name, base_dir, **kwargs) File "/usr/lib/python3.2/shutil.py", line 426, in _make_tarball tar.add(base_dir, filter=_set_uid_gid) 38.4 shutil-Modul Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. File "/usr/lib/python3.2/tarfile.py", line 2065, in add tarinfo = self.gettarinfo(name, arcname) File "/usr/lib/python3.2/tarfile.py", line 1937, in gettarinfo statres = os.lstat(name) OSError: [Errno 2] No such file or directory: './abc' Falls wir den Pfad in base_dir nicht relativ, sondern absolut angeben, wird der Teilbaum „abc” unabhängig vom Wert von root_dir gefunden. Die Archivdatei wird jedoch im von root_dir definierten Pfad abgelegt: >>> import shutil >>> shutil.make_archive("my_archive", "bztar", "/home/bernd/", "/ home/bernd/tmp/abc") '/home/bernd/my_archive.tar.bz2' ■ move(src, dst) Dieser Befehl ist ähnlich wie der Unix/Linux-Befehl „mv”. „move” dient dazu, eine Datei oder ein Verzeichnis an einen anderen Speicherort zu verschieben. Falls der Zielpfad „dst” ein Verzeichnis oder ein symbolischer Link auf ein Verzeichnis ist, dann wird das durch „src” bezeichnete Objekt, also beispielsweise eine Datei oder ein Verzeichnis, in das Verzeichnis „dst” verschoben. Wenn das Verzeichnis „dst” noch nicht existiert, wird es erzeugt. Wenn das Ziel „dst” bereits existiert, aber kein Verzeichnis, sondern eine Datei ist, wird diese überschrieben, sofern die entsprechenden Rechte vorhanden sind. Dies aber nur, falls es sich bei „src” auch um eine Datei handelt. Ist „src” ein Verzeichnis, erhält man eine Fehlermeldung. ■ ■ ■ register_archive_format(name, function, extra_args=None, description=”) Ein Archivformat registrieren. „name” ist der Name des Formats. „function” ist eine Funktion („callable”), die benutzt wird, um ein Archiv zu erzeugen. Der optionale Parameter „extra_args” ist ein sequentielles Datenobjekt mit (name, value)-Tupels, die der Funktion „function” übergeben werden. „description” ist optional und kann die Beschreibung des Formats enthalten. Außerdem wird „description” von der get_archive_formats()-Funktion zurückgeliefert. register_unpack_format(name, extensions, function, extra_args=None, description=”) Analog zu register_archive_format, allerdings für ein Format zum Entpacken. „name” ist der Name des Formats. „extensions” ist eine Liste von Extensions für dieses Format. „function” ist eine Funktion, die benutzt wird, um ein Archiv zu entpacken. Als Parameter wird ein Archiv übergeben. Falls die Funktion mit dem Archiv nicht klarkommt, d.h. es nicht entpacken kann, wird ein ReadError generiert. Der optionale Parameter „extra_args” ist ein sequentielles Datenobjekt mit (name, value)-Tupels, die der Funktion „function” übergeben werden. „description” ist optional und kann die Beschreibung des Formats enthalten. Außerdem wird „description” von der get_unpack_formats()Funktion zurückgeliefert. rmtree(path, ignore_errors=False, onerror=None) Ein Verzeichnisbaum „path” wird rekursiv gelöscht. Falls „ignore_errors” gesetzt ist, werden Fehler ignoriert. Falls „onerror” gesetzt ist, d.h. ein Zeiger auf ein aufrufbares Objekt, 467 468 38 Systemprogrammierung dann wird dieses Objekt mit den Argumenten (func, path, exc_info) aufgerufen, wobei „func” entweder „os.listdir”, „os.remove” oder „os.rmdir” ist, „path” der Pfad ist, der den Fehler verursacht hat, und exc_info ein Tupel ist, was von sys.exc_info() zurückgeliefert wurde. Falls sowohl „ignore_errors” auf False gesetzt ist und „onerror” auf None, dann wird eine Ausnahme generiert. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ ■ ■ unpack_archive(filename, extract_dir=None, format=None) Entpackt ein Archiv. „filename” ist der Name des Archivs. „extract_dir” ist der Name des Zielverzeichnisses, wohin das Archiv entpackt werden soll. Falls dieser optionale Parameter nicht angegeben wird, wird das aktuelle Arbeitsverzeichnis als Zielverzeichnis verwendet. „format” ist das verwendete Archivierungsformat, also eines der Formate „zip”, „tar”, oder „gztar” oder ein anderes registriertes Format (siehe register_archive_format). Falls kein Format angegeben wird, prüft „unpack_archive”, ob es für die Dateiendung einen registrierten Entpacker gibt. Falls keiner gefunden wird, wird ein ValueError generiert. unregister_archive_format(name) Ein Format wird deregistriert. unregister_unpack_format(name) Entfernt das pack-Format aus der Registrierung. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 39 39.1 Forks Fork Schon lange vor der Biologie hat sich die Informatik mit dem Klonen beschäftigt. Aber man nannte es nicht Klonen sondern Forken. fork bedeutet in Englisch Gabel, Gabelung, Verzweigung und als Verb gabeln, aufspalten und verzweigen. Aber es bedeutet auch eine Aufspaltung oder als Verb aufspalten. Im letzteren Sinn wird es bei Betriebssystemen verwendet, vor allem bei Unix und Linux. Prozesse werden aufgespalten – geklont würde man heute eher sagen – und führen dann ein eigenständiges „Leben”. In der Informatik steht der Begriff Fork für die Bezeichnung verschiedener Sachverhalte: ■ ■ ■ Bild 39.1 Zellteilung Ein vom Betriebssystem bereitgestellter Systemaufruf, der den bestehenden Prozess aufspaltet – und dabei eine exakte Kopie des Prozesses erzeugt – und dann beide Prozesse gewissermaßen parallel laufen lässt. In der Software-Entwicklung bezeichnet ein Fork eine Abspaltung von einem (Haupt-) Projekt. Die Fähigkeit einiger Dateisysteme zur Unterteilung von Dateien. 39.2 Fork in Python Falls Sie Python nur unter Windows benutzen, können Sie den Rest des Kapitels getrost überspringen, da Forks unter Windows nicht unterstützt werden. Sie laufen aber unter Linux und Unix. Beim Systemaufruf os.fork erzeugt der aktuelle Prozess eine Kopie von sich selbst, welche dann als Kindprozess des erzeugenden Programms läuft. Der Kindprozess übernimmt die Daten und den Code vom Elternprozess und erhält vom Betriebssystem eine eigene Prozessnummer, eine sogenannte PID (engl. „Process IDentifier”). Der Kindprozess läuft als 470 39 Forks eigenständige Instanz des Programms, unabhängig vom Elternprozess. Am Rückgabewert von fork() erkennt man, in welchem Prozess man sich befindet. 0 kennzeichnet den Kindprozess. Im Fehlerfall liefert fork() einen Wert kleiner 0 zurück, und kein Kindprozess wird erzeugt. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Um Prozesse forken zu können, müssen wir das Modul os in Python importieren. Das folgende Beispiel-Skript zeigt einen Elternprozess (Parent), der sich beliebig oft forken kann, solange man als Benutzer des Skripts kein „q” bei der Eingabeaufforderung eingibt. Sowohl der Kindprozess als auch der Elternprozess machen nach dem fork mit der if-Anweisung weiter. Im Elternprozess hat newpid einen von 0 verschiedenen Wert, sodass die Ausführung im else-Teil der if-Anweisung fortfährt. Im Kindprozess hat newpid den Wert 0, sodass dort die Funktion child() aufgerufen wird. Die exit-Anweisung os.exit(0) in der child-Funktion ist notwendig, da sonst der Kindprozess in den Elternprozess zurückkehren würde, und zwar zur input-Anweisung. import os def child(): print('A new child ', os._exit(0) os.getpid( )) def parent(): while True: newpid = os.fork() if newpid == 0: child() else: pids = (os.getpid(), newpid) print("parent: %d, child: %d" % pids) if input( ) == 'q': break parent() Starten wir dieses Programm, erhalten wir folgende Ausgaben: bernd@saturn:~/bodenseo/python/beispiele$ python3 fork.py parent: 5023, child: 5024 A new child 5024 q bernd@saturn:~/bodenseo/python/beispiele$ Das Diagramm in Bild 39.2 veranschaulicht nochmals, was genau beim Forking passiert: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 39.2 Fork in Python Bild 39.2 Forking Im nächsten Skript erzeugen wir drei Kindprozesse, in denen wir jeweils vier printAusgaben im Sekundenabstand absetzen: import os, time def counter(count): for i in range(count): time.sleep(1) print('%s. count of [%s]' % (i, os.getpid())) for i in range(3): pid = os.fork() if pid == 0: counter(4) os._exit(0) else: print('Another process spawned: %d' % pid) print('Exit of parent process') 471 472 39 Forks Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Ausgabe sieht wie folgt aus: bernd@saturn:~/bodenseo/python/beispiele$ python3 fork_counter.py Another process spawned: 6428 Another process spawned: 6429 Another process spawned: 6430 Exit of parent process bernd@saturn:~/bodenseo/python/beispiele$ 0. count of [6428] 0. count of [6429] 0. count of [6430] 1. count of [6428] 1. count of [6429] 1. count of [6430] 2. count of [6429] 2. count of [6428] 2. count of [6430] 3. count of [6429] 3. count of [6428] 3. count of [6430] bernd@saturn:~/bodenseo/python/beispiele$ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Teil V Lösungen zu den Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 40 40.1 Lösungen zu den Aufgaben Lösungen zu Kapitel 5 (Sequentielle Datentypen) 1. Aufgabe Wir haben die folgenden Datentypen kennengelernt: ■ ■ ■ ■ str: Zeichenketten, meist als Strings bezeichnet list: Listen tuple: Tupel bytes und bytearray: Binärdaten 2. Aufgabe Wenn die Variable „liste” eine Liste bezeichnet, dann spricht man das erste Element mit liste[0] an. Beispiel: >>> liste = [45,87,79] >>> liste[0] 45 >>> 476 40 Lösungen zu den Aufgaben 3. Aufgabe Nehmen wir an, dass l eine Variable vom Typ list ist, dann können wir das letzte Element am einfachsten mit l[-1] ansprechen. Falls Sie l[len(l) - 1] als Lösung gefunden hatten, ist dies auch korrekt, aber weniger elegant als obige Lösung. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Beispiel: >>> l = ["Hallo", "Welt", "Das letzte Element"] >>> l[-1] 'Das letzte Element' >>> l[len(l) - 1] 'Das letzte Element' >>> 4. Aufgabe >>> >>> >>> >>> t = (4,7,9) s = "Ich bin ein String" l = [45,98,"787",[3,4]] t2 = (4,8,[45,98]) >>> t[0] 4 >>> t[3] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: tuple index out of range Der höchste Index des Tupels entspricht 2, sodass wir oben versucht hatten, auf einen nicht existierenden Index zuzugreifen, und erhielten daher die Meldung „IndexError: tuple index out of range”. >>> t(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object is not callable Zugriffe auf Tupel oder Listen benötigen eckige Klammern. Mit runden Klammern wird auf Funktionen zugegriffen. Daher die Fehlermeldung „TypeError: ’tuple’ object is not callable”, d.h. t ist „nicht aufrufbar” (not callable), da es keine Funktion, sondern ein Tupel ist. >>> s[4] 'b' >>> s[4] = "x" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment 40.1 Lösungen zu Kapitel 5 (Sequentielle Datentypen) Strings sind unveränderlich. >>> l[2][0] = "g" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Wir haben wieder versucht, einen String zu verändern. >>> l[3][0] = "g" >>> l [45, 98, '787', ['g', 4]] >>> t2[2][0] = 42 5. Aufgabe Stellt man die Schrittweite des Slicing-Operators auf step = 2, kann man die beiden verborgenen Sätze extrahieren, wenn man einmal mit dem Index 0 und einmal mit dem Index 1 beginnt: >>> s = 'DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre lsbiscthh inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg Mmaaxcihmte..' >>> s[::2] 'Der ist der beste Lehrer, der sich nach und nach überflüssig macht.' >>> s[1::2] 'In dem Sinne ist Hilfe zur Selbsthilfe immer unsere oberste Maxime.' >>> Anmerkung: Beachten Sie bitte, dass zwischen den einzelnen Wörtern des Strings s jeweils zwei Leerzeichen stehen! Vielleicht interessiert es Sie auch, wie wir den String erzeugt hatten. Um das Folgende zu verstehen, müssen Sie allerdings das Kapitel 31 (Listen-Abstraktion/List Comprehension) bereits bearbeitet haben. >>> s = "Der ist der beste Lehrer, der sich nach und nach überflüssig macht." >>> t = "In dem Sinne ist Hilfe zur Selbsthilfe immer unsere oberste Maxime." >>> s = "".join(["".join(x) for x in zip(s,t)]) >>> s 'DIenr diesmt Sdienrn eb eisstte HLielhfree rz,u rd eSre lsbiscthh inlafceh iumnmde rn aucnhs eürbee rofbleürssstieg Mmaaxcihmte..' >>> 477 478 40 Lösungen zu den Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 6. Aufgabe >>> 3 >>> 20 >>> 3 >>> 1 >>> 2 >>> len(x) len(title) len(address) len(nested) len(book) 40.2 Lösungen zu Kapitel 6 (Dictionaries) 1. Aufgabe menu = {"en" : {"file":"File", "new":"New","open":"Open", "save":"Save ","save as":"Save as", "print preview":"Print Preview", "print":" Print", "close":"Close", "exit":"Exit"}, "fr" : {"file":"Fichier", "new":"Nouveau","open":"Ouvrir", " save":"Enregistrer","save as":"Enregistrer sous", "print preview ":"Apercu avant impressioner", "print":"Imprimer", "close":"Fermer ", "exit":"Quitter"}, "de" : {"file":"Datei", "new":"Neu","open":"Öffnen", "save":" Speichern","save as":"Speichern unter", "print preview":" Druckansicht", "print":"Drucken", "close":"Schließen", "exit":" Verlassen"}, "it" : {"file":"File", "new":"Nuovo","open":"Apri", "save":" Salva","save as":"Salva come", "print preview":"Anteprima di stampa", "print":"Stampa", "close":"Chiudi", "exit":"Esci"} } language = input("Which language? ") current_dic = menu[language] print(current_dic) Ein Aufruf des obigen Programms liefert beispielsweise folgende Ausgaben: $ python3 nested_dictionaries.py Which language? it 40.2 Lösungen zu Kapitel 6 (Dictionaries) {'print preview': 'Anteprima di stampa', 'exit': 'Esci', 'save as': ' Salva come', 'file': 'File', 'close': 'Chiudi', 'print': 'Stampa', 'new': 'Nuovo', 'save': 'Salva', 'open': 'Apri'} Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 2. Aufgabe board = { ("a",1) ("d",1) ("e",1) ("f",1) ("h",1) ("a",2) ("b",2) ("c",2) ("e",2) ("f",2) ("g",2) ("h",2) ("c",3) ("f",3) ("d",4) ("h",4) ("c",6) ("d",6) ("d",6) ("h",6) ("a",7) ("b",7) ("d",7) ("e",7) ("f",7) ("g",7) ("a",8) ("d",8) ("e",8) ("f",8) ("g",8) ("h",8) } : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : ("Turm", "weiss"), ("Dame", "weiss"), ("König", "weiss"), ("Läufer", "weiss"), ("Turm", "weiss"), ("Bauer", "weiss"), ("Bauer", "weiss"), ("Bauer", "weiss"), ("Bauer", "weiss"), ("Bauer", "weiss"), ("Bauer", "weiss"), ("Bauer", "weiss"), ("Springer", "weiss"), ("Springer", "weiss"), ("Bauer", "weiss"), ("Läufer", "weiss"), ("Bauer", "schwarz"), ("Bauer", "schwarz"), ("Läufer", "schwarz"), ("Bauer", "schwarz"), ("Bauer", "schwarz"), ("Bauer", "schwarz"), ("Springer", "schwarz"), ("Bauer", "schwarz"), ("Bauer", "schwarz"), ("Bauer", "schwarz"), ("Turm", "schwarz"), ("Dame", "schwarz"), ("König", "schwarz"), ("Läufer", "schwarz"), ("Springer", "schwarz"), ("Turm", "schwarz") print(board[("g",8)]) Ruft man das Programm auf, erhält man folgende Ausgabe: $ python3 chess.py ('Springer', 'schwarz') 479 480 40 Lösungen zu den Aufgaben 40.3 Lösungen zu Kapitel 9 (Verzweigungen) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 1. Aufgabe age = int(input("Alter des Hundes: ")) print() if age < 0: print("Das stimmt wohl kaum!") elif age == 1: print("entspricht ca. 14 Jahre") elif age == 2: print("entspricht ca. 22 Jahre") elif age > 2: human = 22 + (age -2)*5 print("Menschenjahre", human) ### input('press Return>') 2. Aufgabe jahr = int(input("Jahreszahl: ")) if jahr % 4 == 0: if jahr % 100 == 0: if jahr % 400 == 0: schaltjahr = True else: schaltjahr = False else: schaltjahr = True else: schaltjahr = False if schaltjahr: print(jahr, "ist ein Schaltjahr") else: print(jahr, "ist kein Schaltjahr") Alternativ zu obiger Lösung kann man die Aufgabe auch wie folgt lösen. In diesem Fall müssen wir die Variable schaltjahr auf False vorbesetzen: 40.3 Lösungen zu Kapitel 9 (Verzweigungen) #!/usr/bin/python3 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. jahr = int(input("Jahreszahl: ")) schaltjahr = False if jahr % 4 == 0: schaltjahr = True if jahr % 100 == 0: schaltjahr = False if jahr % 400 == 0: schaltjahr = True if schaltjahr: print(jahr, "ist ein Schaltjahr") else: print(jahr, "ist kein Schaltjahr") 3. Aufgabe hours = int(input("hours: ")) minutes = int(input("minutes: ")) seconds = int(input("seconds: ")) if seconds == 59: seconds = 0 if minutes == 59: minutes = 0 if hours == 23: hours = 0 else: hours += 1 else: minutes += 1 else: seconds += 1 # Wandlung in Strings hours = str(hours) if len(hours) < 2: hours = "0" + hours minutes = str(minutes) if len(minutes) < 2: minutes = "0" + minutes seconds = str(seconds) 481 482 40 Lösungen zu den Aufgaben if len(seconds) < 2: seconds = "0" + seconds Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. print("New time: " + hours + ":" + minutes + ":" + seconds) Mithilfe einer formatierten Ausgabe kann man das Hinzufügen von Nullen bei einstelligen Werten einfacher realisieren. Die obige Lösung hat zum einen den Vorteil, dass sie keinen Vorgriff auf noch nicht behandelte Themen macht, und zum anderen wird die ifAnweisung bei der Wandlung weiter vertieft. Allerdings möchten wir Ihnen die elegantere Lösung nicht vorenthalten: hours = int(input("hours: ")) minutes = int(input("minutes: ")) seconds = int(input("seconds: ")) if seconds == 59: seconds = 0 if minutes == 59: minutes = 0 if hours == 23: hours = 0 else: hours += 1 else: minutes += 1 else: seconds += 1 print("New time: {0:02d}:{1:02d}:{2:02d}".format(hours,minutes,seconds )) 40.4 Lösungen zu Kapitel 10 (Schleifen) 1. Aufgabe Haben Sie die den römischen Zahlen zugrunde liegenden Regeln erkannt? Die Regeln: 1. Stehen gleiche Zahlen nebeneinander, werden sie addiert. Beispiele: III = 3 oder XX = 20 2. Es dürfen höchstens drei Grundzahlen nebeneinander stehen. Ausnahme „M”. 40.4 Lösungen zu Kapitel 10 (Schleifen) 3. Steht eine Zahl mit einem kleineren Wert neben einer Zahl mit einem größeren Wert, so wird der kleinere Wert vom größeren subtrahiert. Beispiele: IV entspricht 5 - 1 (also 4) oder XL entspricht 50 - 10. 4. Die Grundzahlen I, X und C dürfen nur von der nächsthöheren Zahl (Zahlzeichen) subtrahiert werden. Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. roman_number = input("Bitte römische Zahl eingeben: ") decimal = 0 roman_digits = { 'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000 } previous_value = 0 for char in roman_number: if char in roman_digits: if roman_digits[char] > previous_value: decimal -= previous_value else: decimal += previous_value previous_value = roman_digits[char] decimal += previous_value print("Ergebnis: " + str(decimal)) Ein Aufruf des Programms sieht so aus: $ python3 roman2decimal.py Bitte römische Zahl eingeben: MCDXLVIII Ergebnis: 1448 2. Aufgabe Es gibt natürlich viele Wege, diese Aufgabe zu lösen. Man hätte auch eine for-Schleife wählen können. Wir haben uns für eine Lösung entschieden, bei der wir eine while-Schleife beenden, sobald die zurückgelegte Entfernung kleiner als eine Schranke eps ist: sum = 0 old_sum = -1 eps = 0.0001 i = 1 483 484 40 Lösungen zu den Aufgaben while sum - old_sum > eps: old_sum = sum sum += i i = i / 2 print(i, sum) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Ausgabe dieses Programms liefert uns folgende Werte: $ python3 frog.py | more 0.5 1 0.25 1.5 0.125 1.75 0.0625 1.875 0.03125 1.9375 0.015625 1.96875 0.0078125 1.984375 0.00390625 1.9921875 0.001953125 1.99609375 0.0009765625 1.998046875 0.00048828125 1.9990234375 0.000244140625 1.99951171875 0.0001220703125 1.999755859375 6.103515625e-05 1.9998779296875 3.0517578125e-05 1.99993896484375 Aus den numerischen Werten liegt es nahe zu folgern, dass der Frosch mit seiner Sprungweise nicht mehr als 2 Meter zurücklegen wird. Damit schafft er es also nicht, die 2,5 m breite Straße zu überqueren. Übrigens kann man auch den n-ten Summenwert der folgenden Reihe entnehmen: 1+ 2n − 1 3 7 15 + + + . . . n−1 5 4 8 2 3. Aufgabe Eine Lösung sieht wie folgt aus: koerner = 0 for i in range(64): koerner += 2 ** i print(koerner) Startet man dieses Programm, erhält man die in der Aufgabenstellung angegebene Anzahl von Weizenkörnern: $ python3 weizenkorn.py 18446744073709551615 40.5 Lösungen zu Kapitel 11 (Dateien lesen und schreiben) In obiger Lösung haben wir in jedem Schritt der Schleife eine Exponentiation, die natürlich viel Rechenzeit benötigt. Wir können diese Exponentiation durch eine Summe ersetzen, indem wir eine zusätzliche Variable „zuwachs” einführen, die die Menge der Weizenkörner pro Schritt beinhaltet: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. koerner = 0 zuwachs = 1 for i in range(64): koerner += zuwachs zuwachs += zuwachs print(koerner) Aber wenn wir nun schon am „schneller machen” sind, dann machen wir es gleich richtig schnell. Um diese Aufgabe zu lösen, haben wir uns ja bewusst unwissend gestellt. Für dieses Problem gibt es schließlich eine Formel. Die Anzahl der Körner auf n Feldern berechnet sich als: n X 2i = 2n+1 − 1 i =0 Damit brauchen wir für die Lösung der Aufgabe nur noch die interaktive Python-Shell: >>> 2 ** 64 -1 18446744073709551615 >>> Für die mathematisch Interessierten: Allgemein gilt für die Summe von Potenzen mit konstanter Basis k und steigendem Exponenten: n X i =0 ki = k n+1 − 1 k −1 40.5 Lösungen zu Kapitel 11 (Dateien lesen und schreiben) 1. Aufgabe Die Variable output ist ein leerer String, wenn wir uns in einer Zeile mit einem Vornamen befinden. Befinden wir uns in einer Zeile mit einem Nachnamen, enthält die Variable output den zugehörigen Vornamen. fobj_in = open("namen.txt") fobj_out = open("namen2.txt", "w") output = "" 485 486 40 Lösungen zu den Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. for line in fobj_in: if output: output += " " + line.rstrip() + "\n" fobj_out.write(output) output = "" else: output = line.rstrip() fobj_in.close() fobj_out.close() 2. Aufgabe Die Variable „counter” dient dazu, die aktuelle Zeilennummer aufzunehmen. Immer, wenn eine Zeilennummer durch drei teilbar ist, geben wir den Inhalt der vorhergehenden drei Zeilen aus. Die einzige Ausnahme stellt die Zeilennummer 0 dar, denn in diesem Fall kann noch nichts ausgegeben werden. fh = open("musicians.txt") counter = 0 new_line = "" for line in fh: line = line.rstrip() if counter % 3 == 0: if counter: print(new_line) _ new line = line elif counter % 3 == 1: new_line += " " + line else: new_line += " (" + line + ")" counter += 1 fh.close() 3. Aufgabe fh_in = open("musicians.txt") fh_out = open("musicians2.txt", "w") counter = 0 new_line = "" for line in fh_in: line = line.rstrip() if counter % 3 == 0: if counter: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 40.6 Lösungen zu Kapitel 16 (Listen und Tupel im Detail) fh_out.write(new_line + "\n") new_line = line elif counter % 3 == 1: new_line += " " + line else: new_line += " (" + line + ")" counter += 1 fh_out.close() fh_in.close() 40.6 Lösungen zu Kapitel 16 (Listen und Tupel im Detail) 1. Aufgabe Die folgende Funktion „rev” kehrt die Reihenfolge einer Liste um. Die Funktion ist „destruktiv”, d.h. sie leert die Eingabeliste. def rev(lst): revl = [] while lst: el = lst.pop() revl.append(el) return revl liste = ["a","b","c","d"] liste = rev(liste) print liste 2. Aufgabe def flatten(x): """flatten(sequence) -> list""" result = [] for el in x: #if isinstance(el,(list, tuple)): if (type(el) == list) or (type(el) == tuple): result.extend(flatten(el)) else: result.append(el) return result 487 488 40 Lösungen zu den Aufgaben liste = [["test", ["noch tiefer", "auch"]],1,(2,(2,3,4))] print(flatten(liste)) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Obige Funktion testen wir in der Python-Shell: bernd@saturn:~/bodenseo/python/beispiele$ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from flatten import flatten >>> liste = [["test", ["noch tiefer", "auch"]],1,(2,(2,3,4))] >>> flatten(liste) ['test', 'noch tiefer', 'auch', 1, 2, 2, 3, 4] >>> 3. Aufgabe >>> l = [3,31,33,4,41,2,1,10,22] >>> sorted(l, key = str) [1, 10, 2, 22, 3, 31, 33, 4, 41] 4. Aufgabe def letter_frequency(line): relevant_letters = "abcdefghijklmnopqrstuvwxyz" frequency_dict = {} for char in line.lower(): if char in relevant_letters: if char in frequency_dict: frequency_dict[char] += 1 else: frequency_dict[char] = 1 f = list(frequency_dict.items()) f.sort(key = lambda x: x[1],reverse=True) return f txt = letter_frequency(open("ulysses.txt").read()) print(txt) Started man dieses Programm, erhält man die Buchstabenhäufigkeiten des Romans „Ulysses” von James Joyce: 40.6 Lösungen zu Kapitel 16 (Listen und Tupel im Detail) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. bernd@saturn:~/bodenseo/python/beispiele$ python3 letter_frequency.py [('e', 143221), ('t', 101654), ('a', 94103), ('o', 92704), ('i', 82494), ('n', 81133), ('s', 77656), ('h', 73053), ('r', 70986), (' l', 55522), ('d', 49609), ('u', 33767), ('m', 31891), ('c', 30481) , ('g', 28189), ('f', 27016), ('w', 26424), ('y', 24571), ('p', 22879), ('b', 21413), ('k', 12196), ('v', 9865), ('j', 2404), ('x ', 1464), ('q', 1343), ('z', 1079)] bernd@saturn:~/bodenseo/python/beispiele$ 5. Aufgabe Wir geben zuerst die elegantere Lösung mit der lambda-Notation. Weiter unten finden Sie eine Lösung ohne lambda: umsaetze = [ ('John', 'Miller', 46, 18.67), ('Randy', 'Steiner', 48, 27.99), ('Tina', 'Baker', 53, 27.23), ('Andrea', 'Baker', 40, 31.75), ('Eve', 'Turner', 44, 18.99), ('Henry', 'James', 50, 23.56)] print("Sortiert nach Verkaufserlös:") for i in sorted(umsaetze, key=lambda x: x[2] * x[3]): print(i) print("\nSortiert nach Nachname und Vorname:") for i in sorted(umsaetze, key=lambda x: x[1] + x[0]): print(i) Speichern wir das obige Programm unter „umsaetze.py”, erhalten wir folgende Ausgaben beim Start: bernd@venus:~/bodenseo/python/beispiele$ python3 umsaetze.py Sortiert nach Verkaufserlös: ('Eve', 'Turner', 44, 18.99) ('John', 'Miller', 46, 18.67) ('Henry', 'James', 50, 23.56) ('Andrea', 'Baker', 40, 31.75) ('Randy', 'Steiner', 48, 27.99) ('Tina', 'Baker', 53, 27.23) Sortiert nach Nachname und Vorname: ('Andrea', 'Baker', 40, 31.75) ('Tina', 'Baker', 53, 27.23) ('Henry', 'James', 50, 23.56) ('John', 'Miller', 46, 18.67) ('Randy', 'Steiner', 48, 27.99) 489 490 40 Lösungen zu den Aufgaben ('Eve', 'Turner', 44, 18.99) bernd@venus:~/bodenseo/python/beispiele$ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Das folgende Programm stellt eine Lösung ohne die Verwendung von lambda dar. Wir müssen deshalb explizit zwei Funktionen definieren. Sie finden auch diese Lösung in unserem Beispielverzeichnis unter dem Namen „umsaetze2.py”: umsaetze = [ ('John', 'Miller', 46, 18.67), ('Randy', 'Steiner', 48, 27.99), ('Tina', 'Baker', 53, 27.23), ('Andrea', 'Baker', 40, 31.75), ('Eve', 'Turner', 44, 18.99), ('Henry', 'James', 50, 23.56)] def gesamtpreis(x): return x[2] * x[3] def name(x): return x[1] + x[0] print("Sortiert nach Verkaufserlös:") for i in sorted(umsaetze, key=gesamtpreis): print(i) print("\nSortiert nach Nachname und Vorname:") for i in sorted(umsaetze, key=name): print(i) 40.7 Lösungen zu Kapitel 14 (Funktionen) 1. Aufgabe Die Funktion in Python: def txt2loeffelsprache(text, vokale = {"a", "e", "i", "o", "u", "ä", "ö", "ü "}, diphtonge = {"ei", "au", "ie", "eu"}, fueller = "lef"): text = text.lower() kodiert = "" while text: first2 = text[:2] # ein oder zwei Zeichen first = text[0] Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 40.7 Lösungen zu Kapitel 14 (Funktionen) if first2 in diphtonge: kodiert += first2 + fueller + first2 text = text[2:] elif first in vokale: kodiert += first + fueller + first text = text[1:] else: kodiert += first text = text[1:] return kodiert words = ["Löffelsprache", "Donauauen", "zweieiig", "Treueeid"] for word in words: print(txt2loeffelsprache(word)) print("\nUnd nun mit anderer Füllsilbe:") for word in words: print(txt2loeffelsprache(word, fueller="lol")) Der Wert der Variablen first2 besteht aus den ersten beiden Buchstaben des Strings text, außer wenn Text nur aus einem Buchstaben besteht. In letzterem Fall liefert text[:2] den String text komplett zurück, also den einen Buchstaben. Starten wir obiges Programm, erhalten wir folgende Ausgabe: $ python3 loeffelsprache.py Löleföffelefelspralefachelefe Dolefonalefaulefualefaulefuelefen zwelefeilefielefeilefiilefig Trelefeulefuelefeelefeilefid Und nun mit anderer Füllsilbe: Lölolöffelolelspralolachelole Dololonalolaulolualolauloluelolen zweloleilolieloleiloliilolig Treloleulolueloleeloleilolid 2. Aufgabe Wir benutzen zum Test der Funktion loeffelsprache2txt die Funktion txt2loeffelsprache, d.h. wir prüfen mit word.lower() == loeffelsprache2txt(loeffel), ob sich ein mittels txt2loeffelsprache gewandelter Text wieder zurückwandeln lässt. def txt2loeffelsprache(text, vokale = {"a", "e", "i", "o", "u", "ä", "ö", "ü "}, diphtonge = {"ei", "au", "ie", "eu"}, fueller = "lef"): 491 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 492 40 Lösungen zu den Aufgaben text = text.lower() kodiert = "" while text: first2 = text[:2] # ein oder zwei Zeichen first = text[0] if first2 in diphtonge: kodiert += first2 + fueller + first2 text = text[2:] elif first in vokale: kodiert += first + fueller + first text = text[1:] else: kodiert += first text = text[1:] return kodiert def loeffelsprache2txt(loeffeltext, vokale = {"a", "e", "i", "o", "u", "ä", "ö", "ü "}, diphtonge = {"ei", "au", "ie", "eu"}, fueller = "lef"): txt = loeffeltext for diphtong in list(diphtonge): txt = txt.replace(diphtong + fueller + diphtong, diphtong) for vokal in list(vokale): txt = txt.replace(vokal + fueller + vokal, vokal) return txt words = ["Löffelsprache", "Donauauen", "zweieiig", "Treueeid"] for word in words: loeffel = txt2loeffelsprache(word) print(loeffel, loeffelsprache2txt(loeffel)) if word.lower() == loeffelsprache2txt(loeffel): print("okay") else: print("Problem: " + loeffel) 3. Aufgabe Die einfachste Lösung lässt sich unter Benutzung des Teilbereichsoperators mit negativem dritten Index erzielen: def is_palindrome(word): word = word.lower() return word == word[::-1] 40.7 Lösungen zu Kapitel 14 (Funktionen) Man kann diesen Test auch über direkte Buchstabenvergleiche durchführen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def is_palindrome(word): word = word.lower() for i in range(len(word)//2): if word[i] != word[-(i+1)]: return False return True 4. Aufgabe In der folgenden Funktion epalindrome vergleichen wir jeweils den Buchstaben ganz links mit dem am weitesten rechts stehenden Buchstaben. Um diese Buchstaben zu finden, überlesen wir zuvor in den beiden inneren while-Schleifen solange Sonderzeichen und Leerzeichen, bis wir auf einen Buchstaben stoßen. def is_epalindrome(phrase): left = 0 right = -1 middle = len(phrase) // 2 phrase = phrase.lower() while True: while left <= middle and not phrase[left].isalpha(): left += 1 while -right <= middle and not phrase[right].isalpha(): right -= 1 if left <= middle and -right <= middle: if phrase[left] != phrase[right]: return False left, right = left + 1, right - 1 else: return True return True Mit integriertem Test sieht unsere Funktion wie folgt aus: def is_epalindrome(phrase): left = 0 right = -1 middle = len(phrase) // 2 phrase = phrase.lower() while True: while left <= middle and not phrase[left].isalpha(): left += 1 while -right <= middle and not phrase[right].isalpha(): right -= 1 if left <= middle and -right <= middle: 493 494 40 Lösungen zu den Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. if phrase[left] != phrase[right]: return False left, right = left + 1, right - 1 else: return True return True words = ("Anna", "Lager", "Reittier", "Lagerregal", "x", "aka") phrases = ( "Ein Eis esse sie nie.", "Ein Esel lese nie.", "Nein, kann Anna knien?", "To be or not to be!", "Oh, Cello voll Echo!", "!!", ".", ",d!.,", "d,....!d,,!", ",!", "a,", ",a" ) for phrase in phrases + words: print(phrase, end=" ist ") if is_epalindrome(phrase): print("ein Palindrom!") else: print("kein Palindrom!") Ausgabe: $ python3 palindrome.py Ein Eis esse sie nie. ist ein Palindrom! Ein Esel lese nie. ist ein Palindrom! Nein, kann Anna knien? ist ein Palindrom! To be or not to be! ist kein Palindrom! Oh, Cello voll Echo! ist ein Palindrom! !! ist ein Palindrom! . ist ein Palindrom! ,d!., ist ein Palindrom! d,....!d,,! ist ein Palindrom! ,! ist ein Palindrom! a, ist ein Palindrom! ,a ist ein Palindrom! Anna ist ein Palindrom! Lager ist kein Palindrom! Reittier ist ein Palindrom! Lagerregal ist ein Palindrom! x ist ein Palindrom! aka ist ein Palindrom! 40.8 Lösungen zu Kapitel 15 (Rekursive Funktionen) 40.8 Lösungen zu Kapitel 15 (Rekursive Funktionen) 1. Aufgabe Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Mathematisch können wir es wie folgt formulieren: f(1) = 3, f(n+1) = f(n) + 3 Die Funktion in Python: def mult3(n): if n == 1: return 3 else: return mult3(n-1) + 3 for i in range(1,10): print(mult3(i)) 2. Aufgabe def sum_n(n): if n== 0: return 0 else: return n + sum_n(n-1) 3. Aufgabe def pascal(n): if n == 1: return [1] else: line = [1] previous_line = pascal(n-1) for i in range(len(previous_line)-1): line.append(previous_line[i] + previous_line[i+1]) line += [1] return line print(pascal(6)) 495 496 40 Lösungen zu den Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Alternativ können wir eine Lösung mit List Comprehension schreiben: def pascal(n): if n == 1: return [1] else: p_line = pascal(n-1) line = [ p_line[i]+p_line[i+1] for i in range(len(p_line)-1)] line.insert(0,1) line.append(1) return line print(pascal(6)) 4. Aufgabe def fib_pascal(n,fib_pos): if n == 1: line = [1] fib_sum = 1 if fib_pos == 0 else 0 else: line = [1] (previous_line, fib_sum) = fib_pascal(n-1, fib_pos+1) for i in range(len(previous_line)-1): line.append(previous_line[i] + previous_line[i+1]) line += [1] if fib_pos < len(line): fib_sum += line[fib_pos] return (line, fib_sum) def fib(n): return fib_pascal(n,0)[1] # and now printing out the first ten Fibonacci numbers: for i in range(1,10): print(fib(i)) 5. Aufgabe Iterative Lösung für das Sieb des Eratosthenes für die ersten 100 Zahlen: from math import sqrt def sieve(n): # returns all primes between 2 and n primes = list(range(2,n+1)) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 40.8 Lösungen zu Kapitel 15 (Rekursive Funktionen) max = sqrt(n) num = 2 while num < max: i = num while i <= n: i += num if i in primes: primes.remove(i) for j in primes: if j > num: num = j break return primes print(sieve(100)) In diesem Kapitel geht es jedoch um rekursive Funktionen, und in der Aufgabenstellung haben wir auch bei der Berechnung der Primzahlen Rekursion verlangt. Um die folgende Lösung besser verstehen zu können, empfehlen wir, unser Kapitel über Listen-Abstraktion (List Comprehension) zurate zu ziehen: from math import sqrt def primes(n): if n == 0: return [] elif n == 1: return [] else: p = primes(int(sqrt(n))) no_p = [j for i in p for j in range(i*2, n + 1, i)] p = [x for x in range(2, n + 1) if x not in no_p] return p print(primes(100)) 6. Aufgabe memo = {0:0, 1:1} def fib(n): if not n in memo: memo[n] = fib(n-1) + fib(n-2) return memo[n] def find_index(*x): """ finds the natural number i with fib(i) = n """ 497 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 498 40 Lösungen zu den Aufgaben if len(x) == 1: # started by user # find index starting from 0 return find_index(x[0],0) else: n = fib(x[1]) m = x[0] if n > m: return -1 elif n == m: return x[1] else: return find_index(m,x[1]+1) 7. Aufgabe # code from the previous example with the functions fib() and find_index() print(" index of a | a | b | sum of squares | index ") print("=====================================================") for i in range(15): square = fib(i)**2 + fib(i+1)**2 print( " %10d | %3d | %3d | %14d | %5d " % (i, fib(i), fib(i+1) , square, find_index(square))) Die Ausgabe für das vorige Skript sieht wie folgt aus: index of a | a | b | sum of squares | index ===================================================== 0 | 0 | 1 | 1 | 1 1 | 1 | 1 | 2 | 3 2 | 1 | 2 | 5 | 5 3 | 2 | 3 | 13 | 7 4 | 3 | 5 | 34 | 9 5 | 5 | 8 | 89 | 11 6 | 8 | 13 | 233 | 13 7 | 13 | 21 | 610 | 15 8 | 21 | 34 | 1597 | 17 9 | 34 | 55 | 4181 | 19 10 | 55 | 89 | 10946 | 21 11 | 89 | 144 | 28657 | 23 12 | 144 | 233 | 75025 | 25 13 | 233 | 377 | 196418 | 27 14 | 377 | 610 | 514229 | 29 40.9 Lösungen zu Kapitel 19 (Alles über Strings . . . ) 40.9 Lösungen zu Kapitel 19 (Alles über Strings . . . ) 1. Aufgabe Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Die Lösung besteht darin, dass man zweistufig split anwendet: fh = open("addresses.txt", encoding="iso-8859-1") for address in fh: address = address.strip() (lastname, firstname, city, phone) = address.split(';') print(firstname + " " + lastname + ", " + city + ", " + phone) Die Ausgabe des Programms „split_addresses_mixed.py”: bernd@saturn:~/bodenseo/python/beispiele$ python3 split_addresses_mixed.py Meyer Frank, Radolfzell, 07732/43452 Rabe Peter, Konstanz, 07531/70021 Huber Ottmar, Rosenheim, 08031/7877-0 Rabe Anna, Radolfzell, 07732/2343 Lindner Oskar, Konstanz, 07531/890 List Anna, München, 089/3434544 List Anna, München, 089/3434544 Huber Franziska, Rosenheim, 08031/787878 Rabe Sarah, Konstanz, 07531/343454 bernd@saturn:~/bodenseo/python/beispiele$ 2. Aufgabe Die Lösung besteht darin, dass wir einmal ein normales split mit maxsize = 1 und ein rsplit mit maxsize = 1 aufrufen: fh = open("addresses.txt", encoding="iso-8859-1") l = [] for address in fh: address = address.strip() t = (address.split(';',1)[0], address.rsplit(";",1)[1]) l.append(t) print(l) 499 500 40 Lösungen zu den Aufgaben Alternativ kann man auch ein split ohne maxsize verwenden und dann auf den ersten und letzten Index zugreifen: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. fh = open("addresses.txt", encoding="iso-8859-1") l = [] for address in fh: address = address.strip() t = address.split(';') l.append((t[0], t[-1])) print(l) 3. Aufgabe >>> s = """Hat der alte Hexenmeister\nSich doch einmal wegbegeben!\r\ nUnd nun sollen seine Geister\rAuch nach meinem Willen leben.\n""" >>> s.splitlines() ['Hat der alte Hexenmeister', 'Sich doch einmal wegbegeben!', 'Und nun sollen seine Geister', 'Auch nach meinem Willen leben.'] >>> s = "\n".join(s.splitlines()) + "\n" >>> s 'Hat der alte Hexenmeister\nSich doch einmal wegbegeben!\nUnd nun sollen seine Geister\nAuch nach meinem Willen leben.\n' >>> print(s) Hat der alte Hexenmeister Sich doch einmal wegbegeben! Und nun sollen seine Geister Auch nach meinem Willen leben. >>> 4. Aufgabe def findnth(str, substr, n): num = 0 start = -1 while num < n: start = str.find(substr, start+1) if start == -1: return -1 num += 1 return start 40.9 Lösungen zu Kapitel 19 (Alles über Strings . . . ) if __name__ == "__main__": s = "abc xyz abc xyz abc xyz" print(findnth(s, "xyz", 1)) print(findnth(s, "xyz", 3)) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Dieses Programm befindet sich in unserem Beispielverzeichnis unter „findnth.py”. Startet man das Programm, erhält man folgende Ausgaben: bernd@saturn:~/bodenseo/python/beispiele$ python3 findnth.py 4 20 bernd@saturn:~/bodenseo/python/beispiele$ 5. Aufgabe Für die Funktion „replacenth” benutzen wir die Funktion „findnth”, die wir im vorigen Beispiel erstellt hatten, um die Position des n-ten Vorkommens zu ermitteln. Für die eigentliche Ersetzung brauchen wir dann lediglich Slicing und die Konkatenation von Strings: def findnth(str, substr, n): num = 0 start = -1 while num < n: start = str.find(substr, start+1) if start == -1: return -1 num += 1 return start def replacenth(source, search, replacement, n): pos = findnth(source, search, n) if pos == -1: return source return source[:pos] + replacement + source[pos+len(search):] Beispiel für die Anwendung der Funktion „replacenth”: bernd@saturn:~/bodenseo/python/beispiele$ python3 Python 3.2.3 (default, Oct 19 2012, 19:53:57) [GCC 4.7.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from replacenth import replacenth >>> replacenth("And yes he said yes!","yes","no",2) 'And yes he said no!' >>> replacenth("And yes he said yes!","yes","no",1) 'And no he said yes!' >>> 501 502 40 Lösungen zu den Aufgaben 40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte) 1. Aufgabe Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Als Lösung bietet sich natürlich an, das Attribut x in eine Property zu wandeln. class Robot(object): def __init__(self, name): self.name = name def __GetName(self): return self.__name def __SetName(self, name): if name == "Hugo": self.__name = "Marvin" else: self.__name = name name = property(__GetName, __SetName) x = Robot("Marvin") y = Robot("Hugo") print(x.name, y.name) 2. Aufgabe Ein Objekt der Klasse „robot” wird von den Attributen self.position, self.orientation und self.name definiert: self.position enthält die Position eines Roboters in einem zweidimensionalen Koordinatensystem. Die Koordinate wird in Form einer Zweierliste gespeichert. self.orientation Ein Roboter kann in eine der vier Richtungen „north”, „east”, „south” und „west” gerichtet sein. Das ist dann auch die Richtung, in die er sich bewegt, wenn man die Methode move auf ihn anwendet. self.name Alle Dinge sollten einen Namen haben, so auch unsere Robot-Objekte. Ein Name darf aber nicht mehr als 10 Zeichen enthalten, was wir in einer Property prüfen. Enthält er mehr als 10 Zeichen, reduzieren wir den Namen auf 10 Zeichen. 40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte) """ Roboterklasse zur Positionsbeschreibung und Veränderung von Objekten in einem zweidimensionalen Koordinatensystem. """ Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class Robot(object): def __init__(self, x=0, y=0, orientation = "north", name="marvin") : self.position = [x,y] self.orientation = orientation self.name = name def __repr__(self): s = "Robot(" + str(self.position[0]) + ', ' + str(self. position[1]) s += ', "' + self.orientation + '"' s += ', "' + self.name + '")' return s def __str__(self): """ Stringdarstellung einer Instanz """ s = self.name + " faces " + self.orientation s += " at position " + str(self.position) return s def move(self, distance): """ Methode zum Bewegen eines Roboters in Richtung seiner aktuellen Orientierung. Wird ein Roboter x mit x.move(10) aufgerufen und ist dieser Roboter östlich orientiert, also x.getPosition() == ,,east'' und ist beispielsweise [3,7] die aktuelle Position des Roboters, dann bewegt er sich 10 Felder östlich und befindet sich anschließend in Position [3,17]. """ if self.orientation == "north": self.position[1] += distance elif self.orientation == "south": self.position[1] -= distance elif self.orientation == "west": self.position[0] -= distance elif self.orientation == "east": self.position[0] -= distance @property def orientation(self): 503 504 40 Lösungen zu den Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. """ Die Property orientation enthält die aktuelle Orientierung , d.h. eine der Richtungen "west", "south", "east" oder "north ".""" return self.__orientation @orientation.setter def orientation(self, orientation): """ Mit der Methode newOrientation ändern wir die Orientierung des Roboters. Mögliche Orientierungen sind "north","south"," west","east" """ if orientation in {"north","south","west","east"}: self.__orientation = orientation @property def name(self): return self.__name @name.setter def name(self, name): max_name_len = 10 if len(name) > max_name_len: self.__name = name[:max_name_len] else: self.__name = name if __name__ == "__main__": x = Robot(3, 4, "north", "Marvin") print(x) x.move(10) x.orientation = "west" x.move(7) print(x) new_name = "Alexandria-Maria" x.name = new_name # name ist zu lang, wird abgeschnitten print("Hi, this is " + x.name) print(x) print(repr(x)) # Roboter x wird mittels repr geklont: y = eval(repr(x)) print(y) Ruft man obiges Modul direkt auf, erhält man folgende Ausgaben: 40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. $ python3 robot.py Marvin faces north at position [3, 4] Marvin faces west at position [-4, 14] Hi, this is Alexandria Alexandria faces west at position [-4, 14] Robot(-4, 14, "west", "Alexandria") Alexandria faces west at position [-4, 14] 3. Aufgabe from datetime import date today = date.today() class Roboter: def __init__(self, name, baujahr, energie=100 ): self.name = name self.baujahr = baujahr self.energie = energie def __befinden(self): alter = today.year - self.baujahr if alter > 20: if self.energie < 50: return "In Anbetracht meines Alters nicht so schlecht !" else: return "Super. Vor allem in Anbetracht meines Alters!" else: if self.energie < 50: return "Ausgelaugt" else: return "Super!" befinden = property(__befinden) if __name__ == "__main__": a = Roboter("Marvin", 1979, 49) b = Roboter("Caliban", 1993, 53) c = Roboter("Awesom-O", 2004, 90) d = Roboter("Clank", 2002, 30) print(a.befinden) print(b.befinden) print(c.befinden) print(d.befinden) 505 506 40 Lösungen zu den Aufgaben Das Programm liefert folgende Ausgaben: Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. $ python3 robot_energie.py In Anbetracht meines Alters nicht so schlecht! Super. Vor allem in Anbetracht meines Alters! Super! Ausgelaugt 4. Aufgabe """ This module is implementing a Cave Man arithmetic "CaveInt", the simplest numeral system representing the natural numbers. A number n is represented by a string consisting of n tally marks (vertical strokes). The number 0 - completely unknown to Neanderthal Man is - is represented by an empty string """ class CaveInt(object): def __init__(self,n): """ A CaveInt can be either initialized by an integer or by a string consisting solely of tally marks. The number 0 and negative numbers are turned into an empty string. A positive integer n will be turned into a string with n tally marks. """ self.number = "" if type(n) == int: self.number = "|" * n elif type(n) == str: if len(n) == n.count("|"): self.number = n def __str__(self): """ String representation of a CaveInt """ return self.number def __int__(self): """ A CaveInt is converted into an integer """ return len(self.number) def __add__(self, other): """ Addition of two CaveInt numbers """ 40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. res = CaveInt(self.number + other.number) return res def __sub__(self, other): """ Subtraction of two CaveInt numbers """ diff = int(self) - int(other) diff = diff if diff > 0 else 0 res = CaveInt(diff) return res def __mul__(self,other): """ Multiplication of two CaveInt numbers """ res = CaveInt(self.number * len(other.number)) return res def __truediv__(self,other): """ Division is of course integer division """ d = int(len(self.number) / len(other.number)) res = CaveInt(d) return res if __name__ == "__main__": x = CaveInt("||||") y = CaveInt(12) print(x,y) z = x + y print(z) z = x - y print(z) z = y - x print(z) z = CaveInt("") print(z) z = CaveInt(0) print(z) z = y / x print(z) z = x * y print(z, int(z)) 507 508 40 Lösungen zu den Aufgaben 5. Aufgabe class Plist(list): Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __init__(self, l): list.__init__(self, l) def push(self, item): self.append(item) def splice(self, offset, length, replacement): self[offset:offset+length] = replacement if __name__ == "__main__": x = Plist([33,456,8,34,99]) x.push(47) print(x) x.splice(2,3,["Hey", "you"]) print(x) Startet man das Programm, erhält man folgende Ausgabe: $ python3 standardklassen.py [33, 456, 8, 34, 99, 47] [33, 456, 'Hey', 'you', 47] 6. Aufgabe class Length: __metric = { "mm" : 0.001, "cm" : 0.01, "m" : 1, "km" : 1000, "in" : 0.0254, "ft" : 0.3048, "yd" : 0.9144, "mi" : 1609.344 } def __init__(self, value, unit = "m" ): self.value = value self.unit = unit def Converse2Metres(self): return self.value * Length.__metric[self.unit] 40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def __add__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() + other else: l = self.Converse2Metres() + other.Converse2Metres() return Length(l / Length.__metric[self.unit], self.unit ) def __iadd__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() + other else: l = self.Converse2Metres() + other.Converse2Metres() self.value = l / Length.__metric[self.unit] return self def __sub__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() - other else: l = self.Converse2Metres() - other.Converse2Metres() return Length(l / Length.__metric[self.unit], self.unit ) def __isub__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() - other else: l = self.Converse2Metres() - other.Converse2Metres() self.value = l / Length.__metric[self.unit] return self def __mul__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() * other else: l = self.Converse2Metres() * other.Converse2Metres() return Length(l / Length.__metric[self.unit], self.unit ) def __imul__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() * other else: l = self.Converse2Metres() * other.Converse2Metres() self.value = l / Length.__metric[self.unit] return self def __truediv__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() / other 509 510 40 Lösungen zu den Aufgaben Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. else: l = self.Converse2Metres() / other.Converse2Metres() return Length(l / Length.__metric[self.unit], self.unit ) def __itruediv__(self, other): if type(other) == int or type(other) == float: l = self.Converse2Metres() / other else: l = self.Converse2Metres() / other.Converse2Metres() self.value = l / Length.__metric[self.unit] return self def __radd__(self, other): return Length.__add__(self,other) def __rsub__(self, other): return Length.__sub__(self,other) def __rmul__(self, other): return Length.__mul__(self,other) def __rtrudiv__(self, other): return Length.__truediv__(self,other) def __str__(self): return str(self.Converse2Metres()) def __repr__(self): return str((self.value, self.unit)) if __name__ == "__main__": x = Length(4) print(x) print(repr(x)) print(x + 5.6) print(7 + x) x += Length(4, "yd") print(x) x += 4 x -= Length(4, "m") print(x) z = 4 + Length(8,"yd") y = Length(4.5, "yd") + x / Length(0.77,"in") print(repr(y)) print(y) 40.10 Lösungen zu Kapitel 21 (Grundlegende Aspekte) 7. Aufgabe from abc import ABC, abstractmethod from math import pi Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. class BasisFigure(ABC): def __init__(self, size1, size2): self.size1 = size1 self.size2 = size2 super().__init__() @abstractmethod def area(self): pass @abstractmethod def circumference(self): pass class Rectangle(BasisFigure): def area(self): return self.size1 * self.size2 def circumference(self): return 2 * (self.size1 + self.size2) class Circle(BasisFigure): def __init__(self, radius): super().__init__(radius, None) def area(self): return self.size1 * pi ** 2 def circumference(self): return 2 * pi * self.size1 x = Rectangle(2, 3) y = Circle(3) for obj in (x, y): print(obj.area(), obj.circumference()) Dieses Programm liefert die folgenden Ergebnisse zurück: 6 10 29.608813203268074 18.84955592153876 511 512 40 Lösungen zu den Aufgaben 40.11 Lösungen zu Kapitel 34 (Tests und Fehler) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 1. Aufgabe Die Formulierungen für den Doctest sind in Ordnung. Das Problem liegt in der Implementierung der Fibonacci-Funktion. Diese rekursive Lösung ist höchst ineffizient. Sie müssen nur Geduld haben, bis der Test terminiert. Wie viele Stunden, Tage oder Wochen Sie warten müssen, hängt von Ihrem Rechner ab ,. 40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke) 1. Aufgabe (1) (2) (3) (4) (5) (6) abc ac abbb bbc aabcd b (a) (b) (c) (d) (e) (f) (g) ab+c? a?b*c b+c* ^b+c*$ a.+b?c b{2,}c? ^a{1,2}b+.?d* (a) matcht 1, 3, 5 (b) matcht 1, 2, 4, 5 (c) matcht 1, 3, 4, 5, 6 (d) matcht 4, 6 (e) matcht 1, 5 (f) matcht 3, 4 (g) matcht 1, 3, 5 2. Aufgabe Die Frage lautete, auf welchen der Strings (1) (2) (3) (4) (5) xx xyxyxyx xxyxy xyxx xxyxyx 40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke) der Ausdruck x(xy)*x passt. Vielleicht hatten Sie irrtümlich gedacht, dass das Fragezeichen zum regulären Ausdruck gehört. Es gehört natürlich zur Frage. Antwort: Nur 2 matcht nicht, denn in allen anderen kommt der Teilausdruck „xx” vor! Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Würde das Fragezeichen zum Ausdruck gehören, würde der reguläre Ausdruck auf alle Strings passen. 3. Aufgabe Ein regulärer Ausdruck, der das Problem löst, lautet (wenn man annimmt, dass der ZipCode am Ende einer Zeile stehen muss, so wie es bei US-Briefen der Fall ist): r"\b[0-9]{5}(-?[0-9]{4})?$" Ein kleines Testprogramm finden Sie im Folgenden: import re destinations = ["Minneapolis, MN 55416", "Defreestville NY 12144-7050", "Anchorage AK 99577-0727", "Juneau AK 99750-0077", "Mountain View, CA 94043", "Atlanta, GA 30309", "Washington TX 778800305"] erroneous = ["Minneapolis, MN 554169", "Defreestville NY 12144-705", "Anchorage AK 9957-0727", "Juneau AK 99750--0077", "Mountain View, CA 94 043", "Atlanta, GA 3030-12345", "Washington TX 778-800305"] for dest in destinations + erroneous: if re.search(r"[^-\d][0-9]{5}(-?[0-9]{4})?$",dest): print(dest + " okay") else: print(dest + " not okay") Startet man obiges Programm, erhält man folgende Ausgabe: $ python3 us_zip_codes.py Minneapolis, MN 55416 okay Defreestville NY 12144-7050 okay Anchorage AK 99577-0727 okay Juneau AK 99750-0077 okay 513 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 514 40 Lösungen zu den Aufgaben Mountain View, CA 94043 okay Atlanta, GA 30309 okay Washington TX 778800305 okay Minneapolis, MN 554169 not okay Defreestville NY 12144-705 not okay Anchorage AK 9957-0727 not okay Juneau AK 99750--0077 not okay Mountain View, CA 94 043 not okay Atlanta, GA 3030-12345 not okay Washington TX 778-800305 not okay 4. Aufgabe >>> import re >>> x = open("ulysses.txt").read() >>> words = set(re.findall(r"\b(\w+ship)\b", x)) >>> words {'pumpship', 'courtship', 'battleship', 'lordship', 'marksmanship', ' pleasureship', 'chelaship', 'fellowship', 'lightship', 'fathership ', 'Lordship', 'debtorship', 'consulship', 'township', 'friendship ', 'worship', 'steamship'} >>> len(words) 17 >>> 5. Aufgabe >>> import re >>> x = open("ulysses.txt").read() >>> words = set(re.findall(r"\b((\w{2,})\2)\b", x)) >>> words {('eeee', 'ee'), ('cancan', 'can'), ('dumdum', 'dum'), (' schschschschschsch', 'schschsch'), ('lala', 'la'), ('ooddleooddle ', 'ooddle'), ('chinchin', 'chin'), ('chokeechokee', 'chokee'), (' murmur', 'mur'), ('bangbang', 'bang'), ('nono', 'no'), (' hellohello', 'hello'), ('nyumnyum', 'nyum'), ('rara', 'ra'), (' tartar', 'tar'), ('hiphip', 'hip'), ('geegee', 'gee'), ('jamjam', 'jam'), ('poohpooh', 'pooh'), ('addleaddle', 'addle'), ('usus', ' us'), ('papa', 'pa'), ('looloo', 'loo'), ('curchycurchy', 'curchy '), ('pullpull', 'pull'), ('yumyum', 'yum'), ('whatwhat', 'what'), ('teetee', 'tee'), ('quisquis', 'quis'), ('coocoo', 'coo'), (' rere', 're'), ('puffpuff', 'puff')} >>> words = { w[0] for w in words} >>> words 40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. {'whatwhat', 'jamjam', 'yumyum', 'dumdum', 'nyumnyum', 'pullpull', ' rere', 'usus', 'quisquis', 'addleaddle', 'curchycurchy', ' hellohello', 'looloo', 'nono', 'tartar', 'schschschschschsch', ' papa', 'ooddleooddle', 'cancan', 'murmur', 'teetee', 'rara', 'eeee ', 'geegee', 'hiphip', 'bangbang', 'lala', 'chokeechokee', ' chinchin', 'coocoo', 'poohpooh', 'puffpuff'} 6. Aufgabe import re x = open("ulysses.txt").read().lower() words = list(re.findall(r"\b([a-z]+)\b",x)) word_frequency = {} for word in words: l = len(word) if l in word_frequency: word_frequency[l] += 1 else: word_frequency[l] = 1 print("Wortlänge Häufigkeit") word_frequency_list = list(word_frequency.items()) for (wl, freq) in sorted(word_frequency_list): print("{0:8d} {1:8d}".format(wl,freq)) Startet man obiges Programm, erhält man unten stehende Ausgabe: $ python3 word_lengths.py Wortlänge 1 14930 2 43064 3 59122 4 47067 5 32513 6 23706 7 19561 8 11989 9 7901 10 4834 11 2706 12 1426 13 714 14 295 15 113 Häufigkeit 515 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 516 40 Lösungen zu den Aufgaben 16 17 18 19 20 21 22 23 24 25 26 27 28 30 33 34 36 37 39 69 53 30 16 6 7 6 1 2 1 3 2 1 1 1 1 1 2 1 1 1 Möchte man nur die Häufigkeitsverteilungen für verschiedene Wörter nach Wortlängen berechnen, braucht man nur die Zeile words = list(re.findall(r"\b([a-z]+)\b",x)) durch folgende Zeile zu ersetzen: words = set(re.findall(r"\b([a-z]+)\b",x)) Man benutzt also eine Menge statt einer Liste. Dadurch werden automatisch mehrfache Vorkommen eines Wortes eliminiert. Das Ergebnis sieht nun deutlich anders aus: $ python3 word_lengths.py Wortlänge Häufigkeit 1 26 2 141 3 732 4 2038 5 3279 6 4331 7 4566 8 4273 9 3399 10 2441 11 1619 12 938 13 490 14 214 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 40.12 Lösungen zu Kapitel 36 (Reguläre Ausdrücke) 15 16 17 18 19 20 21 22 23 24 25 26 27 28 30 33 34 36 37 39 69 100 42 23 13 6 6 4 1 2 1 3 2 1 1 1 1 1 2 1 1 1 7. Aufgabe import re txt = open("ulysses.txt").read() words = re.split(r"[^a-zA-Z-]+", txt) unique_words = set(words) ordered_words = set() for word in words: if len(word) <= 4: continue word_aufsteigend_sortiert = "".join(sorted(list(word.lower()))) if word == word_aufsteigend_sortiert: ordered_words.add(word) print(ordered_words) 517 518 40 Lösungen zu den Aufgaben 40.13 Lösungen zu Kapitel 30 (lambda, map, filter und reduce) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 1. Aufgabe orders = [ ["34587","Learning Python, Mark Lutz", 4, 40.95], ["98762","Programming Python, Mark Lutz", 5, 56.80], ["77226","Head First Python, Paul Barry", 3, 32.95]] min_order = 100 invoice_totals = list(map(lambda x: x if x[1] >= min_order else (x[0], x[1] + 10) , map(lambda x: (x[0],x[2] * x[3]), orders))) print(invoice_totals) 2. Aufgabe from functools import reduce orders = [ ["34587",("5464", 44.95)], ["34588",("5464", ["34588",("5464", ["34587",("8732", 39.95)] ] 4, 9.99), ("8274",18,12.99), ("9744", 9, 9, 9.99), ("9744", 9, 44.95)], 9, 9.99)], 7, 11.99), ("7733",11,18.99), ("9710", 5, min_order = 100 invoice_totals = list(map(lambda x: [x[0]] + list(map(lambda y: y[1]*y [2], x[1:])), orders)) invoice_totals = list(map(lambda x: [x[0]] + [reduce(lambda a,b: a + b , x[1:])], invoice_totals)) invoice_totals = list(map(lambda x: x if x[1] >= min_order else (x[0], x[1] + 10), invoice_totals)) print (invoice_totals) 40.14 Lösungen zu Kapitel 31 (Listen-Abstraktion/List Comprehension) 40.14 Lösungen zu Kapitel 31 (Listen-Abstraktion/ List Comprehension) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 1. Aufgabe >>> n = 30 >>> [(x, y) for x in range(1,n+1) for y in range(1,n+1) if not x % y and x != y ] [(2, 1), (3, 1), (4, 1), (4, 2), (5, 1), (6, 1), (6, 2), (6, 3), (7, 1), (8, 1), (8, 2), (8, 4), (9, 1), (9, 3), (10, 1), (10, 2), (10, 5), (11, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 6), (13, 1) , (14, 1), (14, 2), (14, 7), (15, 1), (15, 3), (15, 5), (16, 1), (16, 2), (16, 4), (16, 8), (17, 1), (18, 1), (18, 2), (18, 3), (18, 6), (18, 9), (19, 1), (20, 1), (20, 2), (20, 4), (20, 5), (20, 10), (21, 1), (21, 3), (21, 7), (22, 1), (22, 2), (22, 11), (23, 1), (24, 1), (24, 2), (24, 3), (24, 4), (24, 6), (24, 8), (24, 12), (25, 1), (25, 5), (26, 1), (26, 2), (26, 13), (27, 1), (27, 3), (27, 9), (28, 1), (28, 2), (28, 4), (28, 7), (28, 14), (29, 1), (30, 1), (30, 2), (30, 3), (30, 5), (30, 6), (30, 10), (30, 15)] 2. Aufgabe Die Wahrscheinlichkeit dafür, dass die Summe der Augenzahlen 7 ist, berechnet sich aus dem Quotienten der Anzahl aller Würfe mit Summe 7 („casts_of_sum_7”), dividiert durch die Anzahl aller möglichen Würfe („all_casts”): >>> all_casts = [ (x,y) for x in range(1,7) for y in range(1,7)] >>> casts_of_sum_7 = [ x for x in all_casts if sum(x) == 7 ] >>> casts_of_sum_7 [(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)] >>> probability7 = len(casts_of_sum_7) / len(all_casts) >>> probability7 0.16666666666666666 3. Aufgabe def prob_for_sum(number): all_casts = [ (x,y) for x in range(1,7) for y in range(1,7)] casts_of_sum = [ x for x in all_casts if sum(x) == number ] return len(casts_of_sum) / len(all_casts) 519 520 40 Lösungen zu den Aufgaben if __name__ == "__main__": for i in range(14): print("sum: {0:2d}, prob: {1:5.2f} %".format(i,prob_for_sum(i) 100 ) ) * Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Startet man obiges Programm, erhält man folgende Ausgabe: $ python3 sum_of_dices_prob.py sum: 0, prob: 0.00 % sum: 1, prob: 0.00 % sum: 2, prob: 2.78 % sum: 3, prob: 5.56 % sum: 4, prob: 8.33 % sum: 5, prob: 11.11 % sum: 6, prob: 13.89 % sum: 7, prob: 16.67 % sum: 8, prob: 13.89 % sum: 9, prob: 11.11 % sum: 10, prob: 8.33 % sum: 11, prob: 5.56 % sum: 12, prob: 2.78 % sum: 13, prob: 0.00 % 40.15 Lösungen zu Kapitel 32 (Generatoren und Iteratoren) 1. Aufgabe def fibonacci(): """Ein Fibonacci-Zahlen-Generator""" a, b = 0, 1 while True: yield a a, b = b, a + b f = fibonacci() counter = 0 for x in f: print x, counter += 1 if (counter > 10): break 40.15 Lösungen zu Kapitel 32 (Generatoren und Iteratoren) 2. Aufgabe Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. def city_generator(): cities = ["Paris","Berlin","London","Vienna"] for city in cities: yield city def round_robin(g): while True: for i in g(): yield i if __name__ == "__main__": for i in round_robin(city_generator): print(i) Dies Ausgabe dieses Programms sieht wie folgt aus: Paris Berlin London Vienna Paris Berlin London Vienna Paris Berlin London Vienna Paris ... 3. Aufgabe def city_generator(): cities = ["Paris","Berlin","London","Vienna"] for city in cities: yield city def pendulum(g): while True: l = [] for i in g(): l.append(i) yield i 521 522 40 Lösungen zu den Aufgaben while l: yield l.pop() if __name__ == "__main__": for i in pendulum(city_generator): print(i) Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Starten wir dieses Programm, erhalten wir die folgende Ausgabe: Paris Berlin London Vienna Vienna London Berlin Paris Paris Berlin London Vienna Vienna London Berlin Paris ... 4. Aufgabe def numbers(): for i in range(1,10): yield i def pair_sum(g): it = g() previous = next(it) while True: try: x = next(it) yield x + previous previous = x except StopIteration: it = g() if __name__ == "__main__": for i in pair_sum(numbers): print(i, end=", ") 40.15 Lösungen zu Kapitel 32 (Generatoren und Iteratoren) Die Ausgabe sieht wie folgt aus: 3, 5, 7, 9, 11, 13, 15, 17, 10, 3, 5, 7, 9, 11, 13, 15, 17, 10, 3, 5, 7, 9, 11, ... Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 5. Aufgabe import random def random_ones_and_zeros(): p = 0.5 while True: x = random.random() message = yield 1 if x < p else 0 if message != None: p = message x = random_ones_and_zeros() next(x) # wir sind nicht am return-Wert interessiert for p in [0.2, 0.8]: x.send(p) print("\nprobabiliy: " + str(p)) for i in range(15): print(next(x), end=" ") Die Ausgabe sieht wie folgt aus: probabiliy: 0 0 1 0 0 0 probabiliy: 1 1 1 1 1 0 0.2 0 0 0 0 0 0 0 0 0 0.8 1 0 0 1 1 0 1 1 1 523 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Stichwortverzeichnis ∧ bei regulären Ausdrücken 394 . Match eines beliebigen Zeichens 392 $ bei regulären Ausdrücken 394 % Modulo-Operator 23 16-Bit-Unicode-Zeichen → Escape-Zeichen 32-Bit-Unicode-Zeichen → Escape-Zeichen A Abfragemethode → Getter Abgeleitete Klasse → Unterklasse abort 441 __abs__ 236 abspath 455 Abstrakte Klassen 297 Abstrakte Methoden 297 access 441 __add__ 233, 235, 506 – Beispiel 236 all 350 Allgemeine Klasse → Basisklasse Anagramm – als Beispiel einer Permutation 328 __and__ 235 Änderungsmethode → Setter Anführungszeichen → Escape-Zeichen Angestelltenklasse 252→ Personklasse Ankerzeichen 396 Anweisungsblöcke 67 Anzahl der Elemente einer Liste 40 Anzahl der Elemente eines Tupels 40 Anzahl der Zeichen eines Strings 40 append 143, 145 Archivdatei erzeugen 466 Arität 126 ASCII-Zeichen hexadezimal → Escape-Zeichen ASCII-Zeichen oktal → Escape-Zeichen Asimovsche Gesetze 228 assertAlmostEqual 378 assertCountEqual 378 AssertEqual 377 assertEqual 378 assertFalse 379 assertGreater 379 assertGreaterEqual 379 assertIn 379 AssertionError 376 assertIs 379 assertIsInstance 379 assertIsNone 379 assertIsNot 379 assertItemsEqual 379 assertLess 379 assertLessEqual 379 assertListEqual 379 assertNotRegexpMatches 379 assertTrue 379 assertTupleEqual 379 assoziative Arrays 43 assoziatives Feld 43 atime 457 Attribute 205 – dynamische Erzeugung 207 – private 217 – protected 217 – public 217 AttributeError 258 Aufspalten von Zeichenketten 176 Augmented Assignment → erweiterte Zuweisungen augmented assignment 145 Ausgabe – formatieren 93 – in Datei umleiten 95 – in Fehlerkanal umleiten 95 Auskellern 143 Ausnahme – StopIteration 324 526 Stichwortverzeichnis Ausnahmebehandlung 193 – except 193 – finally 198 – try 193 Automatentheorie 387 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. B Barry 365 basename 455 Bash 426 Bash-Befehle ausführen 453 Basisklasse 251 bedingte Anweisungen 70 Bibliothek 158 Bierdeckelarithmetik 302 Bierdeckelnotation 302 binäre Operatoren 235 Binärsystem 302 Binärzahlen 25 Blöcke 67 Boehm 365 Boolesche Werte 26 Bourne-Shell 426 break 81 Bruch – kürzen 243 – Kürzungszahl 243 – Repräsentierung in Python 242 – vollständig gekürzte Form 243 Bruchklasse 241 Bruchrechnen 242 bztar-Datei → Archivdatei erzeugen C C3 superclass linearization 273 Calendar-Klasse 264 CalendarClock-Klasse 264 __call__ 355 callable 355 Call-by-Reference 123 Call-by-Value 123 Caret-Zeichen 393 Cast 30 cast-Operator 66 Casting 30 Catullus 88 CaveInt 506→ Neanderthal-Arithmetik center 187 chdir 441 chmod 441 Chomsky-Grammatik 133 chown 442 chroot 442 clear → Dictionary CLI 425 Clock-Klasse 264 close 432 Closure 359 Codeblock 75, 417 commonprefix 456 complex 27, 236 continue 81 copy 464→ Dictionary copy2 464 copy-Modul 111 copyfile 464 copyfileobj 464 copymode 464 copystat 464 copytree 464 count 147, 183 ctime 457 D data hiding → Geheimnisprinzip Datei 87 – lesen 87 – öffnen 87 – schreiben 89 – zum Schreiben öffnen 89 Dateibaum – rekursiv durchwandern 454 Dateibearbeitung mit os 429 Dateideskriptor 432, 433 Dateigröße 459 Daten konservieren 381 Daten sichern mit Pickle 381 Datenabstraktion 212 Datenkapselung 204, 212 Datenpersistenz 381 Datentypen 21 Deadly Diamond of Death 272 Decorator → Dekorateur deepcopy 111 Definition einer Variablen 22 Dekorateur 341 – Anwendungsfälle 350 – classmethod 261 – Einführung 341 – Funktionsdekorateur 341 – Klassendekorateur 341 – Memoisation 358 – mit Parametern 352 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Stichwortverzeichnis – zur Überprüfung von Funktionsargumenten 350 Dekoration – mit Klasse 357 __del__ 229 deleter 220 Destruktor 211 Dezimalpunkt 26 Dezimalsystem 302 Diamond-Problem 272 Dictionary 43, 49 – aus Listen erzeugen 54 – clear 48 – copy 49 – flache Kopie 49, 112 – get 47 – items 50 – keys 50 – nested Dictionaries 48 – pop 50 – popitem 50 – setdefault 51 – tiefe Kopie 49, 112 – update 51 – verschachtelte Dictionaries 48 Differenz 27 dirname 456 Division – ganzzahlige 23 __doc__ 115 – bei Dekorateuren 354 Docstring 115 – property 220 doctest 370 Donald Michie → Memoisation dup 434 Dynamische Attribute 227 Dynamische Erzeugung von Klassen 281 dynamische Typdeklaration 29 E Eigenschaften 205 Eingabe 65 Eingabeaufforderung – robust 194 Eingabeprompt 7, 65 Einkellern 143 Einrückungen 67 Einschubmethode → Hook-Methode Elternklasse → Basisklasse endliche Automaten 387, 394 __eq__ 226, 235 erben → Vererbung errare humanum est 365 erweiterte Zuweisungen 23, 235 Escape-Zeichen – 16-Bit-Unicode-Zeichen 35 – 32-Bit-Unicode-Zeichen 35 – Anführungszeichen 35 – ASCII-Zeichen hexadezimal 35 – ASCII-Zeichen oktal 35 – Hochkomma 35 – Horizontaler Tabulator 35 – Rückschritt 35 – Seitenumbruch 35 – Unicode-Zeichen 35 – Vertikaler Tabulator 35 – Zeilenumbruch 35 escape-Zeichen 35 Euklidischer Algorithmus 243 eval 66 except 193 execl 440 execle 440 execlp 439 execlpe 440 execv 438 execve 438 execvp 436 execvpe 437 exists 456 expandvars 457 explizite Typumwandlung 30 extend 144 extsep 443 F Fabrikfunktion 345 f-Strings 104 Fakultätsfunktion 133 – iterative Lösung 135 – rekursive Implementierung 134 False 26 Fehler – Semantik 366 – Syntax 365 Fehlerarten 365 fib → Fibonacci-Modul fiblist → Fibonacci-Modul Fibonacci 136 Fibonacci-Folge → Fibonacci-Zahlen – rekursive Berechnung 142 Fibonacci-Modul 367 – fib-Funktion 367 527 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 528 Stichwortverzeichnis – fiblist-Funktion 367 Fibonacci-Zahlen – formale mathematische Definition 136 – Generator 339, 520 – Modul zur Berechnung der Fibonacci-Zahlen 367 – rekursive Berechnung mit Dekorateuren 358 – rekursive Funktion 135 filter 307 finally 198 find 147, 183 Finite State Machine 394 firstn-Generator 327 flache Kopie 49, 112 __float__ 236 __floordiv__ 235 flüchtige Daten 381 Flussdiagramm 68 for-Schleife 80 – optionaler else-Teil 80 – StopIteration 324 – Unterschied zur while-Schleife 81 – Vergleich zu C 80 fork 443, 469 Forking 469 forkpty 443 formale Sprachen 387 format-Methode 97 Formatstring 95 Fraction-Klasse 250 fractions-Modul 250 fromkeys → Dictionary Frosch – Aufgabe mit Summenbildung 84 – Straßenüberquerung 84 frozensets 59 fully qualified 158 functools 313 Funktionen 113 – Attribute 207 – Fabrikfunktionen 345 – globale Variablen 120 – lokale Variablen 120 – Parameterübergabe 122 – Rückgabewerte 118 – statische Variablen 207 – überladen 253 – Überschreiben 253 – variadische 126 – verschachtelte Funktionen 342 – Zählen der Aufrufe 207 Funktionen als Parameter 343 Funktionsabschluss 359 Funktionsdekorateur → Dekorateur G Ganze Zahlen 24 ganzzahlige Division 23, 27 Gaußsche Summenformel 77 __ge__ 235 Geheimnisprinzip 212 Generator – Allgemein 323 – CLU 323 – Endlosschleife in 326 – firstn 327 – Icon 323 – isclice zur Ausgabe von unendlichen Generatoren 327 – pair_sum 339 – pendulum 339 – Permutationen 328 – round_robin 339 – yield 325 – zur Erzeugung von Variationen 329 Generator-Ausdrücke 331 Generator-Comprehension 319 Generatoren – return 332 – send 332 Generatoren-Abstraktion 319 get → Dictionary get_archive_formats 465 getatime 457 getattr 207 getcwd 443, 462 getcwdb 443 getegid 443 getenv 427 getenvb 428 geteuid 443 get_exec_path 443 getgid 443 getgroups 443 getloadavg 444 getlogin 444 getmtime 458 getpgid 444 getpgrp 444 getpid 444 getppid 444 getresgid 444 getresuid 444 getsize ⇒ os.path Stichwortverzeichnis Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Getter 214 getuid 444 get_unpack_formats 465 ggT 243 größter gemeinsamer Teiler 243 Gruppierungen 401 __gt__ 235 GUI 425 gztar-Datei → Archivdatei erzeugen H hasattr 207 Hash 43 __hex__ 236 Hexadezimalzahlen 25 Hochkomma → Escape-Zeichen Hook-Methode 377 Hooks → Hook-Methode Horizontaler Tabulator → Escape-Zeichen I __iadd__ 235 __iand__ 235 id 22, 107 Identitätsfunktion 22, 107 __idiv__ 235 if-Anweisung 70 __ifloordiv__ 235 ignore_patterns 466 __ilshift__ 235 __imod__ 235 implizite Typumwandlung 30 import-Anweisung 158 __imul__ 235 in – Dictionaries 46 index 147, 183 index-Funktionen 434 Inheritance → Vererbung __init__ 233 __init__-Methode 209 __init__.py 164 input 46, 65 insert 148 Instanz 203, 205 Instanzattribute 205, 207, 227 Instanzvariablen 209 int 24, 236 Integer 24 interaktive Shell – Befehlsstack 11 – Starten unter Linux 7 – Starten unter Windows 9 – Unterstrich 11 __invert__ 236 __ior__ 235 __ipow__ 235 __irshift__ 235 isabs 459 isalnum 188 isalpha 188 isdigit 188 isdir 459 isfile 459 isinstance 31 islice 327 – als Ersatz für firstn 327 islink 460 islower 188 ismount 460 is_palindrome 131 is_prime 350 isspace 188 istitle 188 __isub__ 235 isupper 188 items → Dictionary itemgetter 154 __iter__ 324 Iteration – for-Schleife 323 – über Dictionary 83 – über String 83 Iteratoren 323 – for-Schleife 323 itertools 327 – islice 327 __ixor__ 235 J join 183, 460 Junit 375 K kanonischer Pfad 462 Keller 143 Kellerspeicher 143, 261 keys → Dictionary KeyError 45 kill 444 killpg 445 Kindklasse → Unterklasse Klasse – Instanz 205 529 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 530 Stichwortverzeichnis – Kopf 204 – Körper 204 – minimale Klasse in Python 204 klassen – abstrakte 297 Klassenattribute 206, 227 Klassendekorateur 357→ Dekorateur Klassenmethoden 231, 259 Klassenobjekt 206 Kombination 329 Kommandozeilenparameter 125 Kompilierung von regulären Ausdrücken 407 Komplexe Zahlen 27 Komponententest 367 Konstruktor 209 Kontrollstrukturen 70 Kopieren – von Unterlisten 110 – von verschachtelten Listen 107 Koroutinen 332 Kundenklasse → Personklasse Kürzen 243 L lambda 307 Länge von dictionaries 52 __le__ 235 len 52 len-Funktion 40 Leonardo von Pisa → Fibonacci-Zahlen Lesbia 88 lexists 460 Liber Abaci 136 Lieferant → Personklasse linesep 445 link 445 Linux 426 – Python unter 7 list – append 143 – augmented assignment 145 – extend 144 – find 147 – index 147 – Packing 149 – pop 143 – sort 151 – Unpacking 149 List Comprehension 315 – Kreuzprodukt 317 – pythagoreisches Tripel 316 – Sieb des Eratosthenes 318 – Syntax 316 – Vergleich mit konventionellem Code 316 – Wandlung Celsius in Fahrenheit 316 – Weitere Beispiele 316 – zugrundeliegende Idee 317 list:count 147 list:insert 148 list:remove 147 list-Klasse 203 listdir 445 list_iterator 324 Listen-Abstraktion 315 – Kreuzprodukt 317 – pythagoreisches Tripel 316 – Sieb des Eratosthenes 318 – Syntax 316 – Vergleich mit konventionellem Code 316 – Wandlung Celsius in Fahrenheit 316 – Weitere Beispiele 316 – zugrundeliegende Idee 317 ljust 187 Löffelsprache 130 __long__ 236 lower 186 lseek 433 __lshift__ 235 lstat 445 __lt__ 235 M Magische Methoden 233 – __add__ 233 – binäre Operatoren 235 – Erweiterte Zuweisungen 235 – __floordiv__ 235 – __init__ 209, 233 – __mod__ 235 – __mul__ 235 – __new__ 233 – __pow__ 235 – __repr__ 222, 233 – __str__ 222 – __sub__ 235 – __truediv__ 235 – unäre Operatoren 236 – Vergleichsoperatoren 235 Magische Operatoren – Beispielklasse Length 236 __main__ 205, 367 major 445 make_archive 466 makedev 446 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Stichwortverzeichnis makedirs 447 Manager-Funktion 288 map 307, 310 Mapping 43 Marvin 504 Match eines beliebigen Zeichens 392 Match-Objekte 401 Matching-Problem 390 – Over Matching 390 – Under Matching 390 math-Modul 158 maxsplit – split 176 Mehrfachschnittstellenvererbung 263 Mehrfachvererbung 263 – Beispiel CalendarClock 264 Mehrfachzuweisung 36 Memoisation 139, 358 – mit functools.lru_cache 360 – mit Klasse 359 Memoization → Memoisation Memoize 359 memoize-Funktion 359 Mengen 57 – add 59 – clear 59 – copy 60 – difference 60 – difference_update 61 – isdisjoint 62 – issubset 63 – issuperset 63 – Obermenge 63 – pop 64 – remove 62 – Teilmenge 63 Mengen-Abstraktion 318 Mengenlehre 57 Metaklassen 285 Method Resolution Order 273, 276 Methoden 204, 208 – abstrakte 297 – __init__ 209 – Overloading → Überladen – Overwriting → Überschreiben – Überladen 257 – überlagern 253 – Überschreiben 257 – Unterschiede zur Funktion 208 minor 445 mkdir 446 mkfifo 447 __mod__ 235 modulare Programmierung 157 modulares Design 157 Modularisierung 157 Module 157 – dir() 161 – Dokumentation 162 – dynamisch geladene C-Module 159 – eigene Module schreiben 161 – Inhalt 161 – lokal 157 – math 158 – Modularten 159 – Namensraum 158 – re 389 – Suchpfad 160 __module__ – bei Dekorateuren 354 Modulo-Division 27 Modulo-Operator 23 Modultest 367 – unter Benutzung von __name__ 367 Monty Python 44 move 467 MRO 273, 276 mtime 457 __mul__ 235, 506 mypy 418 N __name__ 205, 367 – bei Dekorateuren 354 Namensraum – umbenennen 159 __ne__ 235 Neanderthal-Arithmetik 302 Nebeneffekte 124 __neg__ 236 Nested Dictionaries 48 __new__ 233 next 324 nice 447 Noam Chomsky 133 None 114 normcase 461 normpath 461 NotImplemented 247 O Oberklasse 251 object 251 Objekt 203 531 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 532 Stichwortverzeichnis Objektorientierte Programmiersprache 201 Objektorientierte Programmierung 201 __oct__ 236 Oktalzahlen 25 OOP 201 open 430 open() 87 openpty 432 operator-Modul 154 – itemgetter 154 Operatoren überladen – binäre Operatoren 235 – erweiterte Zuweisungen 235 – unäre Operatoren 236 – Vergleichsoperatoren 235 Operatorüberladung 233 __or__ 235 os – abort 441 – access 441 – chdir 441 – chmod 441 – chown 442 – chroot 442 – close 432 – dup 434 – execl 440 – execle 440 – execlp 439 – execlpe 440 – execv 438 – execve 438 – execvp 436 – execvpe 437 – extsep 443 – fork 443 – forkpty 443 – getcwd 443 – getcwdb 443 – getegid 443 – getenv 427 – getenvb 428 – geteuid 443 – get_exec_path 443 – getgid 443 – getgroups 443 – getloadavg 444 – getlogin 444 – getpgid 444 – getpgrp 444 – getpid 444 – getppid 444 – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – getresgid 444 getresuid 444 getuid 444 kill 444 killpg 445 linesep 445 link 445 listdir 445 lseek 433 lstat 445 major 445 makedev 446 makedirs 447 minor 445 mkdir 446 mkfifo 447 nice 447 open 430 openpty 432 popen 447 putenv 428 read 432 readlink 449 remove 449 removedirs 449 rename 451 renames 451 rmdir 451 sep 456 setegid 451 seteuid 451 setgid 451 setgroups 444 setpgid 451 setpgrp 451 setregid 451 setresgid 451 setresuid 452 setreuid 452 setsid 452 setuid 451 stat 452 stat_float_times 452 strerror 452 supports_bytes_environ 428 symlink 453 sysconf 453 system 453 times 453 umask 453 uname 454 unlink 454 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Stichwortverzeichnis – unsetenv 428 – urandom 454 – utime 454 – wait 454 – wait3 454 – waitpid 454 – walk 454 – write 429 os-Modul 426, 441 os.path 455 os.python – abspath 455 – basename 455 – commonprefix 456 – dirname 456 – exists 456 – expandvars 457 – getatime 457 – getmtime 458 – getsize 459 – isabs 459 – isdir 459 – isfile 459 – islink 460 – ismount 460 – join 460 – lexists 460 – normcase 461 – normpath 461 – realpath 461 – relpath 462 – samefile 462 – split 462 – splitdrive 463 – splitext 463 Over Matching 390 Overloading 253→ Überladen Overwriting 253→ Überschreiben P Packing 149 Paket 164 Paketkonzept 164 Palindrome 131 Parameter 115 – beliebige Anzahl 126 – Defaultwert 115 – optional 115 – Schlüsselwort 118 – Standardwert 115 Parameterliste 114 Parameterübergabe 122 partion 183 Pascalsches Dreieck 141 – Fibonacci-Zahlen 141 peek 143 Permutationen 328 – mit Wiederholungen 328 – ohne Wiederholungen 328 Permutations-Generator 328 Persistente Daten 381 Personenklasse 252 Personklasse → Vererbung Pfadname – absolut 459 – relativ 459 pickle-Modul 381 Platzhalter 96 Polymorphismus 255 Polynom-Fabrikfunktion 345 pop 143, 261→ Dictionary popen 447 popitem → Dictionary __pos__ 236 Potenzieren 27 __pow__ 235 print 93 – Anweisung 93 – Ausgabe in Fehlerkanal 95 – end 93 – file 95 – Funktion 93 – sep 93 – sys.stderr 95 – Umlenkung in Datei 95 printf 95 private Attribute 217 Produkt 27 Programmablaufplan 68 Programmiersprache – Unterschied zu Skriptsprache 17 Properties 215 – Definition mit Dekorateuren 221 property – deleter 220 – mit Dekorateuren 221 protected Attribute 217 public Attribute 217 push 143, 261 putenv 428 Q Quantoren 399 Quotient 27 533 534 Stichwortverzeichnis Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. R __radd__ 238 random-Methode – sample 330 raw-Strings 35 read 432 readlink 449 re-Modul 389 realpath 461 reduce 313 Referenzen 21 Regeln zur Interpretation von römischen Zahlen 482 register_archive_format 467 register_unpack_format 467 reguläre Ausdrücke 387 – Alternativen 407 – Anfang eines Strings matchen 394 – beliebiges Zeichen matchen 392 – compile 407 – Ende eines Strings matchen 394 – Kompilierung 407 – optionale Teile 398 – Qunatoren 399 – Teilstringsuche 389 – Überlappungen 389 – vordefinierte Zeichenklassen 397 – Wiederholungen von Teilausdrücken 399 – Zeichenauswahl 393 reguläre Auswahl – Caret-Zeichen, Zeichenklasse 393 reguläre Mengen 387 reguläre Sprachen 387 Rekursion 133 – Beispiel aus der natürlichen Sprache 133 rekursiv → Rekursion rekursive Funktion 133, 134 relpath 462 remove 147, 449 removedirs 449 rename 451 renames 451 replace 186 __repr__ 222, 233 Restdivision 27 return 114 rfind 183 rindex 183 rjust 187 rmdir 451 rmtree 467 Robot-Klasse 502 Roboter → Roboterklasse Robotergesetze 228 Roboterklasse 204, 301 römische Zahlen 83 Rossum, Guido van 307 round_robin 339 __rshift__ 235 rsplit 179 – Folge von Trennzeichen 181 rstrip 187 Rückgabewerte 118 Rückschritt → Escape-Zeichen Rückwärtsreferenzen 401 S samefile 462 sample 330 Schachbrett mit Weizenkörnern 85 Schaltjahrberechnung 74, 266 Schaltjahre 74 Schleife – break 81 – continue 81 Schleifen 70, 75 – Endekriterium 75 – for-Schleife 80 – kopfgesteuert 79 – Schleifendurchlauf 75 – Schleifenkopf 75 – Schleifenkörper 75 – Schleifenzähler 75 – while-Schleife 76 Schleifenzähler 75 Schlüssel – Dictionary 43 – zulässige Typen 47 Schlüsselwortparameter 118 Seiteneffekte 124 Seitenumbruch → Escape-Zeichen Sekunden – Additon zu Uhrzeiten 74 semantische Fehler 366 sep 456 Sequentielle Datentypen 33 Sequenz 33 set 57 set comprehension 318 setdefault → Dictionary setegid 451 seteuid 451 setgid 451 setgroups 444 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. Stichwortverzeichnis setpgid 451 setpgrp 451 setregid 451 setresgid 451 setresuid 452 setreuid 452 sets – add 59 – clear 59 – copy 60 – difference 60 – difference_update 61 – discard 61 – intersection 62 – isdisjoint 62 – issubset 63 – issuperset 63 – Operationen auf sets 59 – pop 64 – remove 62 setsid 452 Setter 214 setuid 451 setUp-Methode 377 Shell 425 – Bash 426 – Bourne 426 – C-Shell 426 – CLI 425 – GUI 425 – Herkunft und Bedeutung des Begriffes 425 Shell-Skripte ausführen 453 shelve-Modul 383 Shihram 85 shutil – copy 464 – copy2 464 – copyfile 464 – copyfileobj 464 – copymode 464 – copystat 464 – copytree 464 – get_archive_formats 465 – get_unpack_formats 465 – ignore_patterns 466 – make_archive 466 – move 467 – register_archive_format 467 – register_unpack_format 467 – rmtree 467 – unpack_archive 468 – unregister_archive 468 – unregister_unpack_format 468 shutil-Modul 463 Sieb des Eratosthenes 318 – Rekursive Berechnung 141 – Rekursive Funktion mit Mengen-Abstraktion 319 silly_merge 420 Simula 67 201 singleton-Klasse 292 Skriptsprache – Unterschied zu Programmiersprache 17 Slicing 38 Slots 279 sort 151 – eigene Sortierfunktionen 152 – reverse 152 – Umkehrung der Sortierreihenfolge 152 sorted 151 spezialisierte Klasse → Unterklasse splice 303 split 176, 462 – Folge von Trennzeichen 181 – maxsplit 176 splitdrive 463 splitext 463 splitlines 182 Sprachfamilie 387 sprintf 95 Stack 143 – Stapelspeicher 143 Standardausnahmebehandlung 195 Standardbibliothek 158 Standardklassen als Basisklassen 261 Stapelspeicher 143, 261 stat 452 stat_float_times 452 staticmethod 229 Statische Attribute 227 Statische Methoden 229 statische Typ-Deklaration 29 Stelligkeit 126 Stephen Cole Kleene 387 StopIteration 324 str 26, 222, 242 strerror 452 Strings – escape-Zeichen 35 – raw 35 – Suchen und Ersetzen 186 String-Tests 188 Stringinterpolation 95 Stringliterale 104 535 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. 536 Stichwortverzeichnis Stringmethoden – Alles in Großbuchstaben 186 – Alles in Kleinbuchstaben 186 – capitalize 186 – center 187 – count 183 – find 183 – index 183 – isalnum 188 – isalpha 188 – isdigit 188 – islower 188 – isspace 188 – istitle 188 – isupper 188 – ljust 187 – lower 186 – replace 186 – rfind 183 – rindex 183 – rjust 187 – rstrip 187 – String-Tests 188 – strip 187 – title 186 – upper 186 – zfill 187 Stringmodulo 95 Strings 26 – formatieren 93 strip 187 Strukturierungselement 113 __sub__ 235, 506 Subklasse → Unterklasse Suchen und Ersetzen 186 Suchen von Teilstrings 183 sum 76 Summe 27 Summe von n Zahlen 76 – Berechnung mit while-Schleife 76 SUnit 375 super 273 Superklasse → Basisklasse supports_bytes_environ 428 symlink 453, 460 syntaktische Fehler 365 Syntax 365 – Fehler 365 Syntaxfehler 365 sysconf 453 system 453 Systemprogrammierung 425 T tar-Datei → Archivdatei erzeugen TDD → test-driven development tearDown-Methode 377 Teilbereichsoperator 38 Tests 365 test first development 374 test-driven development 374 TestCase 377 – Methoden 377 – setUp-Methode 377 – tearDown-Methode 377 testCase 375 Testgesteuerte Entwicklung 374 Testgetriebene Entwicklung 373, 374 Testmethoden 377 Textverarbeitung 175 Theoretische Informatik 387 tiefe Kopie 49, 112 times 453 Transiente Daten 381 True 26 __truediv__ 235, 506 try 193 Tupel 36 – entpacken 36 – leere 149 – mit einem Element 149 – Packing 149 – Unpacking 149 tuple 36 Typ-Alias 421 Typ-Anmerkungen 417 – Any 422 – Callables 422 – Dict 422 – List 422 – Listen 420 – Variablen 419 Typ-Variablenanmerkungen 419 type 31, 203 type annotations 417 type conversion 30 type hints 417 type-Klasse 281 TypeError 369 – unhashable type 48 typing-Modul 421 Typumwandlung 30 – explizit 30 – implizit 30 Typverletzung 29 Stichwortverzeichnis Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. U Überladen 253, 257 überlagern 253 Überlappungen 389 Überschreiben 253, 257 umask 453 Umgebungsvariablen 427 uname 454 unäre Operatoren 236 Unärsystem 302 Unary System → Unärsystem Under Matching 390 unhashable type 48 Unicode-Zeichen → Escape-Zeichen unittest 375→ Modultest Unix 426 unlink 454 unpack_archive 468 Unpacking 149 unregister_archive_format 468 unregister_unpack_format 468 unsetenv 428 Unterklasse 251 Unterstrich – Bedeutung in der interaktiven Shell 11 update → Dictionary upper 186 urandom 454 utime 454 V ValueError 194 Variable Anzahl von Parametern 126 Variablen 21 – Referenzen auf Objekte 21 Variablennamen 23 – Binnenversalien 24 – CamelCase 24 – gültige 23 – Konventionen 24 variadische Funktion 126 Variation 329 Vererbung 251 – Beispiel Angestelltenklasse, die von Person erbt 251 – Beispiel Kundenklasse, die von Person erbt 251 – Beispiel Lieferantenklasse, die von Person erbt 251 – Beispiel Personenklasse 251 Vererbungsbaum 263 Vergleichsoperatoren 72, 235 verschachtelte Dictionaries 48 Verschachtelte Funktionen 342 Vertikaler Tabulator → Escape-Zeichen Verzeichnis löschen 451 Verzweigungen 70 Vollständige Induktion 133 W wait 454 wait3 454 waitpid 454 walk 454 Weizenkornaufgabe 85 while-Schleife 76 – optionaler else-Teil 78 with-Anweisung 90 wraps-Dekorateur 355 write 429 X __xor__ 235 Y yield 325 – im Vergleich zur return-Anweisung 325 Z Zahlenratespiel 78 Zeichenauswahl 393 Zeichenkette 26 Zeilenumbruch → Escape-Zeichen Zeitrechnung 74 zfill 187 zip-Datei → Archivdatei erzeugen zip-Funktion 52 zip-Klasse 52 Zugriffsmethoden 214 Zustandsautomat 394 Zustandsmaschine 394 Zuweisung 22 537 Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. EINFÜHRUNG IN PYTHON 3 // Einführung in Python 3 downloaded from www.hanser-elibrary.com by Hochschule Darmstadt + Zeitschriften on November 2, 2021 For personal use only. ■ Besonders geeignet für Programmieranfänger, aber auch für Umsteiger von anderen Sprachen wie C, C++, Java oder Perl ■ Eine systematische Einführung in die PythonProgrammierung, die auf den Erfahrungen aus Hunderten von Schulungen des Autors aufbaut ■ Lehrbuch und Nachschlagewerk ■ Praxisnahe Übungen mit ausführlich dokumentierten Musterlösungen zu jedem Kapitel Die wesentlichen Begriffe und Techniken der Programmierung und die zugrunde liegenden Ideen werden im vorliegenden Buch anschaulich erklärt. Zu den Problemstellungen werden typische Beispiele zur Verdeutlichung verwendet, die sich leicht auf andere Anwendungsfälle übertragen lassen. Ebenso dienen auch die Übungsaufgaben mit ausführlich dokumentierten Musterlösungen nicht nur dazu, den Stoff zu vertiefen, sondern vor allem auch exemplarische Vorgehensweisen zu demonstrieren, die in vielen anderen Bereichen verwendet werden können. Als idealer Einstieg für Programmieranfänger, aber auch für Umsteiger behandelt dieses Buch alle grundlegenden Sprachelemente von Python 3. Aber auch für Python-Kenner bietet das Buch viele weiterführende Themen wie Systemprogrammierung, Threads, Forks, Ausnahmebehandlungen und Modultests. In dieser dritten Auflage wurde der Objektorientierung ein eigener Teil mit hundert Seiten gewidmet. In anschaulicher und leicht verständlicher Weise wird der pythonische Stil demonstriert, der sich deutlich von C++ und Java unterscheidet. Außerdem wurden nun auch eigene Kapitel zu Slots, dynamische Erzeugung von Klassen, Metaklassen und Abstrakten Klassen aufgenommen. Der Diplom-Informatiker Bernd KLEIN genießt internationales Ansehen als Python-Dozent. Bisher hat er über 200 PythonKurse in Deutschland, Schweiz, Österreich, Niederlande, Luxemburg und Kanada auf allen Niveaus durchgeführt. Er ist Gründer und Inhaber des international agierenden Schulungsanbieters Bodenseo. Besondere Anerkennung findet er wegen seiner PythonWebseiten www.python-kurs.eu und www.python-course.eu. Seit 2016 ist er Lehrbeauftragter der Universität Freiburg. AUS DEM INHALT // ■ Datentypen ■ Verzweigungen ■ Schleifen ■ Formatierte Ausgabe und Strings formatieren ■ Flaches und tiefes Kopieren ■ Funktionen ■ Vererbung ■ Slots ■ Klassen ■ lambda, map, filter und reduce ■ Listen-Abstraktion ■ Generatoren und Iteratoren ■ Dekorateure ■ Tests und Fehler ■ Daten konservieren ■ Reguläre Ausdrücke ■ Typ-Anmerkungen ■ Systemprogrammierung ■ Forks Zusätzlich wird auf die Typ-Anmerkungen von Python 3.5 und 3.6 eingegangen. € 25,00 [D] | € 25,70 [A] ISBN 978-3-446-45208-4 www.hanser-fachbuch.de/computer