Inhaltsverzeichnis 1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.10 1.11 1.12 1.13 2 2.1 2.2 2.3 2.4 2.5 2.6 Vorwort. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Über den Autor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Danksagungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Pythons Sicht der Dinge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 1: Kenntnis der Python-Version . . . . . . . . . . . . . . . . . . . . . . . . Punkt 2: Stilregeln gemäß PEP 8. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 3: Unterschiede zwischen bytes, str und unicode . . . . . . . . . . Punkt 4: Hilfsfunktionen statt komplizierter Ausdrücke. . . . . . . . . . Punkt 5: Zugriff auf Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 6: Verzicht auf Abstand und Start-/End-Index . . . . . . . . . . . . Punkt 7: Listen-Abstraktionen statt map und filter. . . . . . . . . . . . . . . Punkt 8: Nicht mehr als zwei Ausdrücke in Listen-Abstraktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 9: Generator-Ausdrücke in Listen-Abstraktionen . . . . . . . . . . Punkt 10: enumerate statt range . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 11: Gleichzeitige Verarbeitung von Iteratoren mit zip. . . . . . . Punkt 12: Verzicht auf else-Blöcke nach for- und while-Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 13: Alle Blöcke einer try/except/else/finally-Anweisung nutzen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 19 21 24 27 30 32 Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 14: Exceptions statt Rückgabe von None . . . . . . . . . . . . . . . . . Punkt 15: Closures und der Gültigkeitsbereich von Variablen. . . . . . Punkt 16: Generatoren statt Rückgabe von Listen . . . . . . . . . . . . . . . Punkt 17: Vorsicht beim Iterieren über Argumente . . . . . . . . . . . . . . Punkt 18: Klare Struktur dank variabler Argumente . . . . . . . . . . . . . Punkt 19: Optionale Funktionalität durch SchlüsselwortArgumente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 36 38 39 42 45 49 49 51 57 59 64 67 7 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Inhaltsverzeichnis 2.7 2.8 3 3.1 3.2 3.3 3.4 3.5 3.6 3.7 4 4.1 4.2 4.3 4.4 4.5 4.6 4.7 5 5.1 5.2 5.3 5.4 5.5 5.6 6 6.1 6.2 6.3 Punkt 20: Dynamische Standardwerte von Argumenten mittels None und Docstrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 21: Eindeutigkeit durch Schlüsselwort-Argumente . . . . . . . . . Klassen und Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 22: Hilfsklassen statt Dictionaries und Tupel . . . . . . . . . . . . . Punkt 23: Funktionen statt Klassen bei einfachen Schnittstellen . . . Punkt 24: Polymorphismus und generische Erzeugung von Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 25: Initialisierung von Basisklassen durch super. . . . . . . . . . . Punkt 26: Mehrfache Vererbung nur für Mix-in-Hilfsklassen . . . . . Punkt 27: Öffentliche statt private Attribute . . . . . . . . . . . . . . . . . . . . Punkt 28: Benutzerdefinierte Container-Klassen durch Erben von collections.abc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 73 79 79 85 89 95 100 105 110 Metaklassen und Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 29: Einfache Attribute statt Getter- und Setter-Methoden . . . Punkt 30: @property statt Refactoring von Attributen . . . . . . . . . . . . Punkt 31: Deskriptoren für wiederverwendbare @property-Methoden verwenden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 32: Verzögerte Zuweisung zu Attributen mittels __getattr__, __getattribute__ und __setattr__ . . . . . . . . . . . . . . . . . . . Punkt 33: Unterklassen mit Metaklassen überprüfen. . . . . . . . . . . . . Punkt 34: Klassen mittels Metaklassen registrieren . . . . . . . . . . . . . . Punkt 35: Zugriff auf Klassenattribute mit Metaklassen . . . . . . . . . . 115 115 120 Nebenläufigkeit und parallele Ausführung . . . . . . . . . . . . . . . . . . . . . Punkt 36: Verwaltung von Kindprozessen mittels subprocess . . . . . Punkt 37: Threads, blockierende Ein-/Ausgabevorgänge und parallele Ausführung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 38: Wettlaufsituationen in Threads mit Lock verhindern . . . . Punkt 39: Threads koordinieren mit Queue . . . . . . . . . . . . . . . . . . . . Punkt 40: Parallele Ausführung mehrerer Funktionen mit Coroutinen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 41: Echte parallele Ausführung mit concurrent.futures . . . . . 147 148 Standardmodule. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 42: Funktions-Decorators mit functools.wraps definieren . . . Punkt 43: contextlib und with-Anweisung statt try/finally-Konstrukt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 44: Verlässliches pickle durch copyreg . . . . . . . . . . . . . . . . . . . 8 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 124 130 136 138 143 152 157 160 169 179 185 185 188 192 Inhaltsverzeichnis 6.4 6.5 6.6 6.7 7 7.1 7.2 7.3 7.4 7.5 8 8.1 8.2 8.3 8.4 8.5 8.6 Punkt 45: Für örtliche Zeitangaben datetime statt time verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 46: Verwendung von Algorithmen und Datenstrukturen der Standardbibliothek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 47: Falls Genauigkeit an erster Stelle steht, decimal verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 48: Module der Python-Community. . . . . . . . . . . . . . . . . . . . . Zusammenarbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 49: Docstrings für sämtliche Funktionen, Klassen und Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 50: Pakete zur Organisation von Modulen und zur Bereitstellung stabiler APIs verwenden. . . . . . . . . . . . . . . . . . . . . . . . Punkt 51: Einrichten einer Root-Exception zur Abschottung von Aufrufern und APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 52: Zirkuläre Abhängigkeiten auflösen . . . . . . . . . . . . . . . . . . Punkt 53: Virtuelle Umgebungen zum Testen von Abhängigkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 203 208 210 213 213 218 223 227 233 Veröffentlichung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 54: Modulbezogener (Module-scoped) Code zur Konfiguration der Deployment-Umgebung . . . . . . . . . . . . . . . . . . . . Punkt 55: Debuggen mit repr-Strings . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 56: Überprüfung mit unittest . . . . . . . . . . . . . . . . . . . . . . . . . . Punkt 57: Interaktives Debuggen mit pdb . . . . . . . . . . . . . . . . . . . . . . Punkt 58: Vor der Optimierung Messungen vornehmen . . . . . . . . . Punkt 59: Nutzung des Arbeitsspeichers und Speicherlecks mit tracemalloc untersuchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 244 247 251 253 Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 241 258 9 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge Die Eigenheiten einer Programmiersprache werden durch die Benutzer festgelegt. Im Laufe der Jahre hat sich in der Python-Community das englische Adjektiv pythonic eingebürgert, das Code beschreibt, der einem bestimmten Programmierstil folgt. Dieser Stil ist nicht reglementiert und spielt für den Compiler keine Rolle. Er hat sich im Laufe der Zeit allmählich durch die im Umgang mit der Sprache gewonnene Erfahrung und die Zusammenarbeit der Community entwickelt. Python-Programmierer befürworten eine klare Ausdrucksweise, ziehen Einfaches Komplexem vor und maximieren die Lesbarkeit. (Geben Sie doch mal import this im Python-Interpreter ein.) Mit anderen Sprachen vertraute Programmierer versuchen oft, Python-Code im Stil von C++ oder Java zu schreiben (oder im Stil derjenigen Sprache, die sie eben am besten kennen). Programmieranfänger gewöhnen sich aber meist schnell an die große Vielfalt von Konzepten, die sich in Python formulieren lassen. Es ist für alle Programmierer gleichermaßen wichtig, die beste Art und Weise zu kennen, mit der die gebräuchlichsten Aufgaben in Python erledigt werden. Davon sind alle Programme betroffen, die Sie jemals schreiben werden. 1.1 Punkt 1: Kenntnis der Python-Version Der größte Teil der Beispiele in diesem Buch verwendet die Syntax der Python-Version 3.4, die am 17. März 2014 veröffentlicht wurde. Es kommen jedoch auch einige Beispiele vor, in denen die Syntax der Version 2.7 verwendet wird (veröffentlicht am 3. Juli 2010), um wichtige Unterschiede aufzuzeigen. Die meisten Hinweise gelten für alle der verbreiteten Python-Laufzeitumgebungen wie CPython, Jython, IronPython, PyPy usw. Auf vielen Systemen sind mehrere Versionen der Standard-Laufzeitumgebung CPython vorinstalliert. Dabei ist die standardmäßige Bedeutung des Befehls python auf der Kommandozeile nicht immer ganz klar. Meist ist python ein Alias für python2.7, gelegentlich verweist es aber auch auf ältere Versionen wie python2.6 oder python2.5. Verwenden Sie den Parameter --version, um die genaue Versionsnummer herauszufinden. 17 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge $ python --version Python 2.7.8 Python 3 ist für gewöhnlich unter der Bezeichnung python3 verfügbar. $ python3 --version Python 3.4.2 Sie können die Versionsnummer auch zur Laufzeit feststellen, indem Sie die Werte des integrierten sys-Moduls überprüfen. import sys print(sys.version_info) print(sys.version) >>> sys.version_info(major=3, minor=4, micro=2, releaselevel='final', serial=0) 3.4.2 (default, Oct 19 2014, 17:52:17) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.51)] Die Python-Versionen 2 und 3 werden von der Python-Community gepflegt. Allerdings wurde die Weiterentwicklung von Python 2 eingestellt. Es werden nur noch Fehlerbehebungen bereitgestellt und Sicherheitslücken geschlossen. Außerdem gibt es Rückportierungen, um den Übergang zur Version 3 zu erleichtern. Darüber hinaus gibt es hilfreiche Werkzeuge wie 2to3 und six, die den Wechsel zu Python 3 fördern sollen. Python 3 wird kontinuierlich weiterentwickelt und erhält Verbesserungen, die in Python 2 niemals Eingang finden werden. Inzwischen sind die meisten der gebräuchlichsten Open-Source-Bibliotheken mit Python 3 kompatibel. Ich empfehle daher dringend, für das nächste Python-Projekt die Version 3 zu verwenden. 1.1.1 Kompakt 쐽 Es gibt zwei verschiedene Python-Versionen, die aktiv verwendet werden: Python 2 und Python 3. 쐽 Es gibt verschiedene Laufzeitumgebungen für Python: CPython, Jython, IronPython, PyPy usw. 쐽 Vergewissern Sie sich, dass auf der Kommandozeile Ihres Systems auch wirklich die erwartete Python-Version ausgeführt wird. 쐽 Verwenden Sie für Ihr nächstes Python-Projekt die Version 3, denn auf sie ist das Hauptaugenmerk der Python-Community gerichtet. 18 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.2 Punkt 2: Stilregeln gemäß PEP 8 1.2 Punkt 2: Stilregeln gemäß PEP 8 Das Dokument Python Enhancement Proposal #8 (Vorschläge zur Verbesserung von Python Nr. 8) oder kurz PEP 8 gibt die Stilregeln zur Formatierung von Python-Code vor. Es steht Ihnen natürlich frei, Ihren Python-Code so zu formatieren, wie Sie es für richtig halten, sofern Sie die Syntax beachten. Allerdings macht ein einheitlicher Stil den Code verständlicher und besser lesbar. Außerdem erleichtert ein einheitlicher Stil die Zusammenarbeit mit anderen Python-Programmierern der Community an größeren Projekten. Und auch wenn nur Sie selbst den Code jemals lesen werden, vereinfacht ein einheitlicher Stil nachträgliche Änderungen. PEP 8 legt eine Vielzahl von Details fest, die beim Schreiben von klarem PythonCode zu beachten sind. Die Regeln werden während der Weiterentwicklung von Python ständig überarbeitet. Es lohnt sich, das gesamte Dokument zu lesen (https://www.python.org/dev/peps/pep-0008/). Hier sind einige der Regeln, die Sie unbedingt einhalten sollten: 쐽 쐽 Leerraum (Whitespace): In Python ist Leerraum syntaktisch von Bedeutung, daher schenken Python-Programmierer dem Einfluss von Leerraum auf die Klarheit des Codes besondere Beachtung. 쐽 Verwenden Sie Leerzeichen zum Einrücken von Text, keine Tabulatoren. 쐽 Verwenden Sie für jede Ebene syntaktisch relevanter Einrückungen vier Leerzeichen. 쐽 Zeilen sollten nicht mehr als 79 Zeichen enthalten. 쐽 Die Fortsetzung eines langen Ausdrucks in der nächsten Zeile sollte um zusätzliche vier Leerzeichen eingerückt werden. 쐽 In Dateien sollten Funktionen und Klassen durch zwei Leerzeilen voneinander getrennt sein. 쐽 Innerhalb einer Klasse sollten Methoden durch eine Leerzeile voneinander getrennt sein. 쐽 Fügen Sie keine Leerzeichen vor bzw. nach Indizes, in Funktionsaufrufen oder in Argumentzuweisungen zu Schlüsselwörtern ein. 쐽 Fügen Sie bei Variablenzuweisungen genau ein Leerzeichen vor und nach dem Zuweisungsoperator ein. Bezeichner: Gemäß PEP 8 sollten zur Benennung der verschiedenen Teile der Sprache unterschiedliche Stile verwendet werden. Dadurch wird es beim Betrachten des Codes vereinfacht, den Typ eines Bezeichners zu erkennen. 쐽 Funktionen, Variablen und Attribute sollten als kleinbuchstaben_mit_ unterstrich geschrieben werden. 쐽 Geschützte Instanzattribute sollten mit _führendem_unterstrich geschrieben werden. 19 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge 쐽 쐽 Private Instanzattribute sollten mit __doppelten_führenden_unterstrichen geschrieben werden. 쐽 Klassen und Exceptions sollten als GroßgeschriebeneWörter geschrieben werden. 쐽 Modul-weite Konstanten sollten IN_GROSSBUCHSTABEN geschrieben werden. 쐽 Instanzmethoden einer Klasse sollten als Bezeichnung des ersten Parameters (der auf das Objekt verweist) self verwenden. 쐽 Klassenmethoden sollten als Bezeichnung des ersten Parameters (der auf die Klasse verweist) cls verwenden. Ausdrücke und Anweisungen: In The Zen of Python heißt es: »Es sollte einen – und vorzugsweise nur einen – offensichtlichen Weg geben, ein Problem zu lösen.« PEP 8 schreibt daher für Ausdrücke und Anweisungen Folgendes fest: 쐽 Verwenden Sie Negierungen möglichst inmitten eines Ausdrucks (if a is not b) statt positive Ausdrücke zu negieren (if not a is b). 쐽 Testen Sie leere Werte wie [] oder '' nicht, indem Sie deren Länge überprüfen (if len(eineliste) == 0). Verwenden Sie stattdessen if not somelist. Leere Werte werden implizit als False bewertet. 쐽 Gleiches gilt für nicht leere Werte wie [1] oder 'hallo'. Die Anweisung if somelist wird für nicht leere Werte implizit als True bewertet. 쐽 Verzichten Sie auf einzeilige if-Anweisungen, for- bzw. while-Schleifen und zusammengesetzte except-Anweisungen. Verteilen Sie sie der Übersichtlichkeit halber auf mehrere Zeilen. 쐽 Platzieren Sie import-Anweisungen immer am Anfang der Datei. 쐽 Verwenden Sie beim Import von Modulen stets absolute Pfadbezeichnungen, nicht die zum aktuellen Modul relativen. Wenn Sie beispielsweise das Modul foo des Pakets bar importieren, sollten Sie nicht import foo, sondern from bar import foo verwenden. 쐽 Verwenden Sie die ausdrückliche Syntax from . import foo, falls Sie auf relative Pfade angewiesen sind. 쐽 Importe sollten in folgender Reihenfolge stattfinden: Zuerst Module der Standardbibliothek, dann Module Dritter, dann die eigenen Module. Die jeweilige Gruppe sollte alphabetisch sortiert sein. Hinweis Pylint (http://www.pylint.org) ist ein beliebtes Werkzeug zur statischen Analyse von Python-Quelltexten. Pylint erzwingt automatisch die Einhaltung der Stilregeln gemäß PEP 8 und kann viele weitere typische Fehler in Python-Programmen entdecken. 20 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.3 Punkt 3: Unterschiede zwischen bytes, str und unicode 1.2.1 Kompakt 쐽 Halten Sie sich beim Schreiben von Python-Code an die Stilregeln gemäß PEP 8. 쐽 Ein einheitlicher Stil erleichtert die Zusammenarbeit mit anderen Programmierern der Python-Community. 쐽 Außerdem erleichtert ein einheitlicher Stil nachträgliche Änderungen des eigenen Codes. 1.3 Punkt 3: Unterschiede zwischen bytes, str und unicode In Python 3 gibt es zwei Datentypen, die Zeichenfolgen repräsentieren: bytes und str. bytes-Instanzen enthalten reine 8-Bit-Werte, in str-Instanzen hingegen sind Unicode-Zeichen gespeichert. In Python 2 gibt es ebenfalls zwei Datentypen, die Zeichenfolgen repräsentieren: str und unicode. Im Gegensatz zu Python 3 enthalten str-Instanzen reine 8-BitWerte und Unicode-Zeichen werden in unicode-Instanzen gespeichert. Es gibt viele verschiedene Möglichkeiten, Unicode-Zeichen als Binärdaten (reine 8-Bit-Werte) zu repräsentieren. Die am häufigsten verwendete Textkodierung ist UTF-8. Den str-Instanzen in Python 3 und den unicode-Instanzen in Python 2 ist jedoch keine Binärkodierung zugeordnet. Um Unicode-Zeichen als Binärdaten zu speichern, müssen Sie die encode-Methode verwenden. Zur Umwandlung der Binärdaten in Unicode-Zeichen dient die Methode decode. Beim Schreiben Ihres Python-Programms sollten Sie darauf achten, die Kodierung und Dekodierung von Unicode an den äußersten Schnittstellen vorzunehmen. Ihr eigentliches Programm sollte die Unicode-Datentypen (str in Python 3 bzw. unicode in Python 2) verwenden und keinerlei Annahmen über die Zeichenkodierung machen. Auf diese Weise können Sie auch alternative Textkodierungen (wie Latin-1, Shift JIS oder Big5) einlesen, gleichzeitig aber bei der Ausgabe strikt auf eine bestimmte Textkodierung setzen (idealerweise UTF-8). Das Vorhandensein der beiden unterschiedlichen Zeichendatentypen führt zu zwei typischen Situationen: 쐽 Sie möchten reine 8-Bit-Werte verarbeiten, die als UTF-8-Zeichen kodiert sind (oder eine andere Textkodierung aufweisen). 쐽 Sie möchten Unicode-Zeichen verarbeiten, denen keine Textkodierung zugeordnet ist. Sie benötigen dann zwei Hilfsfunktionen zur Konvertierung der verschiedenen Zeichentypen, die gewährleisten, dass die Eingabewerte den Erwartungen Ihres Codes entsprechen. 21 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge In Python 3 ist eine Methode erforderlich, die eine str- oder eine bytes-Instanz entgegennimmt und stets eine str-Instanz zurückliefert. def to_str(bytes_or_str): if isinstance(bytes_or_str, bytes): value = bytes_or_str.decode('utf-8') else: value = bytes_or_str return value # str-Instanz Sie benötigen eine weitere Methode, die eine str- oder eine bytes-Instanz entgegennimmt und stets eine bytes-Instanz zurückgibt. def to_bytes(bytes_or_str): if isinstance(bytes_or_str, str): value = bytes_or_str.encode('utf-8') else: value = bytes_or_str return value # bytes-Instanz In Python 2 muss die Methode eine str- oder eine unicode-Instanz entgegennehmen und stets eine unicode-Instanz zurückliefern. # Python 2 def to_unicode(unicode_or_str): if isinstance(unicode_or_str, str): value = unicode_or_str.decode('utf-8') else: value = unicode_or_str return value # unicode-Instanz Und schließlich fehlt eine letzte Methode, die eine str- oder eine unicode-Instanz entgegennimmt und stets eine str-Instanz zurückgibt. # Python 2 def to_str(unicode_or_str): if isinstance(unicode_or_str, unicode): value = unicode_or_str.encode('utf-8') else: value = unicode_or_str return value # str-Instanz 22 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.3 Punkt 3: Unterschiede zwischen bytes, str und unicode Es gibt bei der Verwendung von reinen 8-Bit-Werten und Unicode-Zeichen in Python zwei große Vorbehalte. Zum einen scheinen in Python 2 unicode- und str-Instanzen vom selben Typ zu sein, sofern die str-Instanz nur 7-Bit-ASCII-Zeichen enthält. 쐽 Eine solche str-Instanz kann mit der unicode-Instanz durch den »+«-Operator verknüpft werden. 쐽 Eine solche str-Instanz und die unicode-Instanz können mittels der Operatoren zum Test auf Gleichheit und Ungleichheit miteinander verglichen werden. 쐽 Sie können unicode-Instanzen für Formatstrings wie '%s' verwenden. Dieses Verhalten bedeutet, dass Sie einer Funktion, die eine str- oder unicodeInstanz erwartet, oftmals beide Datentypen übergeben können und alles problemlos funktioniert (sofern es sich nur um 7-Bit-ASCII-Zeichen handelt). In Python 3 hingegen sind bytes- und str-Instanzen niemals äquivalent – nicht mal leere Strings. Sie müssen daher beim Herumreichen von Zeichenfolgen viel genauer auf den Datentyp achten. Der zweite Vorbehalt ergibt sich dadurch, dass in Python 3 Operationen mit DateiHandles (die von der integrierten open-Funktion zurückgegeben werden) standardmäßig von einer UTF-8-Kodierung ausgehen, während Python 2 eine Binärkodierung annimmt. Das kann zu überraschenden Fehlschlägen führen, insbesondere dann, wenn die Programmierer an Python 2 gewohnt sind. Wenn Sie beispielsweise einige zufällige Werte als Binärdaten in eine Datei schreiben möchten, funktioniert der nachstehende Code in Python 2, schlägt in Python 3 jedoch fehl: with open('/tmp/random.bin', 'w') as f: f.write(os.urandom(10)) >>> TypeError: must be str, not bytes Der Grund für diesen Fehler ist das in Python 3 beim open-Befehl neu hinzugekommene Argument encoding, das standardmäßig den Wert 'utf-8' besitzt. Die read- und write-Operationen des Datei-Handles erwarten daher str-Instanzen, die Unicode-Zeichen enthalten, nicht bytes-Instanzen, in denen Binärdaten gespeichert sind. Damit das Ganze wieder funktioniert, müssen Sie angeben, dass die zu lesende Datei im binären Schreibmodus (write binary, 'wb') statt im zeichenweisen Schreibmodus ('w') geöffnet werden soll. Hier verwende ich open so, dass der Aufruf sowohl in Python 2 als auch in Python 3 korrekt funktioniert: 23 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge with open('/tmp/random.bin', 'wb') as f: f.write(os.urandom(10)) Dieses Problem tritt beim Lesen von Dateien ebenfalls auf. Die Lösung ist ebenfalls dieselbe: Geben Sie beim Öffnen der Datei statt 'r' mittels 'rb' an, dass die Datei im binären Modus geöffnet werden soll. 1.3.1 Kompakt 쐽 In Python 3 enthalten bytes-Instanzen reine 8-Bit-Werte und str-Instanzen Unicode-Zeichenfolgen. bytes- und str-Instanzen können nicht durch Operatoren wie > oder + miteinander verknüpft werden. 쐽 In Python 2 enthalten str-Instanzen reine 8-Bit-Werte und unicode-Instanzen Unicode-Zeichenfolgen. str- und unicode-Instanzen können durch Operatoren miteinander verknüpft werden, sofern die str-Instanz ausschließlich 7-BitASCII-Zeichen enthält. 쐽 Verwenden Sie Hilfsfunktionen, um zu gewährleisten, dass die zu verarbeitenden Eingaben im erwarteten Format (8-Bit-Werte, UTF-8-kodierte Zeichen, Unicode-Zeichen usw.) vorliegen. 쐽 Beim Lesen oder Schreiben von Binärdaten müssen Sie die Datei im binären Modus (wie z.B. 'rb' oder 'wb') öffnen. 1.4 Punkt 4: Hilfsfunktionen statt komplizierter Ausdrücke Pythons prägnante Syntax ermöglicht es, in einzeiligen Ausdrücken eine Menge Programmlogik unterzubringen. Nehmen Sie beispielsweise an, Sie möchten den Abfrage-String einer URL dekodieren. Hier sind sämtliche Parameter Ganzzahlen: from urllib.parse import parse_qs values = parse_qs('red=5&blue=0&green=', keep_blank_values=True) print(repr(values)) >>> {'red': ['5'], 'green': [''], 'blue': ['0']} Manche Parameter könnten mehrere Werte besitzen, einige auch nur einen, weitere sind zwar vorhanden, aber leer, und wieder andere fehlen womöglich ganz. Die get-Methode des Dictionarys kann also verschiedene Werte zurückliefern: print('Rot: ', values.get('red')) print('Grün: ', values.get('green')) 24 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.4 Punkt 4: Hilfsfunktionen statt komplizierter Ausdrücke print(Deckkraft: ', values.get('opacity')) >>> Rot: ['5'] Grün: [''] Deckkraft: None Es wäre schön, wenn nicht vorhandenen oder leeren Parametern automatisch der Wert 0 zugewiesen wird. Sie könnten diese Aufgabe mittels boolescher Ausdrücke erledigen, weil die nötige Logik noch keine if-Anweisung oder eine Hilfsfunktion rechtfertigt. Die Python-Syntax macht diese Entscheidung allzu leicht. Der Trick besteht darin, dass sowohl ein leerer String und eine leere Liste als auch die Zahl 0 implizit als False bewertet werden. Die nachstehenden Ausdrücke erhalten daher den Wert des Ausdrucks nach dem or-Operator, wenn der erste Ausdruck als False bewertet wird: # Abfrage-String 'red=5&blue=0&green=' red = mwerte.get('red', [''])[0] or 0 green = werte.get('green', [''])[0] or 0 opacity = werte.get('opacity', [''])[0] or 0 print('Rot: %r' % red) print('Grün: %r' % green) print('Deckkraft: %r' % opacity) >>> Rot: '5' Grün: 0 Deckkraft: 0 Die Abfrage des red-Wertes funktioniert, weil der Schlüssel im values-Dictionary vorhanden ist. Dessen Wert ist eine Liste mit einem einzigen Element, nämlich dem String '5', der implizit als True bewertet wird – red wird also der erste Teil des or-Ausdrucks zugewiesen. Die Zuweisung von 0 an den green-Wert funktioniert, weil der Wert im valuesDictionary aus einer Liste besteht, die einen leeren String enthält, der implizit als False bewertet wird. Der or-Ausdruck wird daher zu 0 ausgewertet. Die Zuweisung von 0 an den opacity-Wert funktioniert, weil der Wert im valuesDictionary überhaupt nicht vorhanden ist. Falls der Schlüssel im Dicitionary fehlt, liefert die get-Methode ihr zweites Argument als Resultat zurück, in diesem Fall eine Liste, die nur aus einem leeren String besteht. Wenn also opacity nicht im Dictionary gefunden wird, verhält es sich genauso wie beim green-Wert. 25 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge Allerdings sind die Ausdrücke schwer verständlich und erledigen die Aufgabe auch nicht vollständig, weil nämlich alle Parameter Ganzzahlen sein sollen, damit sie in mathematischen Ausdrücken verwendet werden können. Zu diesem Zweck könnten Sie die Ausdrücke mittels der integrierten int-Funktion in Ganzzahlen umwandeln: red = int(values.get('red', [''])[0] or 0) Dieser Ausdruck ist nun wirklich schwer lesbar: Er ist voller störender Zeichen (all die Klammern und Anführungszeichen) und unverständlich. Ein Betrachter, der diesen Code erstmals in Augenschein nimmt, bräuchte viel zu lange, um den Ausdruck zu entwirren und herauszufinden, was er eigentlich bewirkt. Es ist zwar durchaus sinnvoll, sich kurzzufassen, aber es lohnt sich nicht, all dies in einer einzigen Zeile unterzubringen. Seit Python 2.5 gibt es die sogenannten ternären Bedingungsoperatoren, also if/ else-Ausdrücke, mit denen sich Ausdrücke wie dieser zugleich klarer, aber auch kurz und bündig formulieren lassen: red = values.get('red', ['']) red = int(red[0]) if red[0] else 0 Schon besser. In weniger komplizierten Fällen können if/else-Ausdrücke das Verständnis wirklich sehr erleichtern. Das obige Beispiel ist jedoch noch immer nicht so gut verständlich wie eine vollständige, über mehrere Zeilen verteilte if/ else-Anweisung. Tatsächlich erscheint die dicht gedrängte Version noch komplizierter, wenn man sie mit der auf mehrere Zeilen aufgeteilten Version vergleicht: green = values.get('green', ['']) if green[0]: green = int(green[0]) else: green = 0 Sie sollten in solchen Fällen eine Hilfsfunktion programmieren, insbesondere wenn diese Programmlogik mehrmals verwendet wird: def get_first_int(values, key, default=0): found = values.get(key, ['']) if found[0]: found = int(found[0]) else: 26 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.5 Punkt 5: Zugriff auf Listen found = default return found Der die Hilfsfunktion aufrufende Code ist viel besser verständlich als der komplizierte or-Ausdruck oder die zweizeilige Version, die einen if/else-Ausdruck verwendet: green = get_first_int(values, 'green') Sobald Ausdrücke komplizierter werden, sollten Sie in Betracht ziehen, die Programmlogik in Hilfsfunktionen auszulagern. Die bessere Lesbarkeit bringt fast immer größere Vorteile als knapp gehaltener Code. Lassen Sie sich von Pythons prägnanter Syntax nicht dazu verleiten, derart schwer durchschaubaren Code zu verfassen. 1.4.1 Kompakt 쐽 Pythons Syntax macht es allzu einfach, komplizierte einzeilige Ausdrücke zu programmieren, die schwer verständlich sind. 쐽 Verlagern Sie komplexe Ausdrücke in Hilfsfunktionen, insbesondere dann, wenn Sie die Programmlogik wiederholt verwenden. 쐽 if/else-Ausdrücke stellen eine besser verständliche Alternative zur Verwendung boolescher Operatoren wie or oder and bereit. 1.5 Punkt 5: Zugriff auf Listen In Python gibt es verschiedene Befehle zum Zugriff auf Objektsammlungen. Auf diese Weise können Sie mit minimalem Aufwand Teilmengen auswählen. Für gewöhnlich nutzt man dazu die integrierten Datentypen list, str und bytes. Sie können die entsprechenden Befehle jedoch auf jede Python-Klasse anwenden, die die speziellen Methoden __getitem__ und __setitem__ implementiert (siehe Punkt 28: Benutzerdefinierte Container-Klassen durch Erben von collections.abc). In der einfachsten Form lautet die Syntax somelist[start:ende], wobei das Element start ausgewählt wird (inklusive), das Element ende aber nicht (exklusive): a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] print('Die ersten Vier:', a[:4]) print('Die letzten Vier: ', a[-4:]) print('Die beiden Mittleren:', a[3:-3]) >>> Die ersten Vier: ['a', 'b', 'c', 'd'] 27 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge Die letzten Vier: ['e', 'f', 'g', 'h'] Die beiden Mittleren: ['d', 'e'] Bei Zugriff auf den Anfang einer Liste können Sie das Element mit dem Index 0 (Null) weglassen: assert a[:5] == a[0:5] Beim Zugriff auf das Ende der Liste ist die Angabe des letzten Index redundant: assert a[5:] == a[5:len(a)] Negative Zahlen geben den Abstand vom Ende der Liste an. Die nachstehenden Beispiele sind schon beim ersten Lesen verständlich – es gibt keine Überraschungen. Ich empfehle daher, die verschiedenen Varianten auch zu verwenden. a[:] # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] a[:5] # ['a', 'b', 'c', 'd', 'e'] a[:-1] # ['a', 'b', 'c', 'd', 'e', 'f', 'g'] a[4:] # ['e', 'f', 'g', 'h'] a[-3:] # ['f', 'g', 'h'] a[2:5] # ['c', 'd', 'e'] a[2:-1] # ['c', 'd', 'e', 'f', 'g'] a[-3:-1] # ['f', 'g'] Zu große oder zu kleine Start- und End-Indizes, die auf nicht vorhandene Listenelemente verweisen, werden korrekt gehandhabt. Auf diese Weise können Sie leicht die maximale Länge einer Eingabe beschränken: die_ersten_zwanzig = a[:20] die_letzten_zwanzig = a[-20:] Wenn Sie hingegen direkt auf einen nicht vorhandenen Index zugreifen, führt das zu einer Fehlermeldung: a[20] >>> IndexError: list index out of range 28 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.5 Punkt 5: Zugriff auf Listen Hinweis Die Verwendung negativer Indizes stellt eine der wenigen Situationen dar, in denen es zu überraschenden Resultaten kommen kann. Beispielsweise funktioniert der Ausdruck somelist[-n:0] problemlos, sofern n größer als eins ist (z.B. somelist[-3:]). Falls n allerdings den Wert null besitzt, liefert der Ausdruck somelist[-0:] eine Kopie der ursprünglichen Liste. Der Zugriff auf eine Liste liefert eine neue Liste zurück. Referenzen auf die ursprüngliche Liste bleiben erhalten. Die ursprüngliche Liste wird durch Modifizierung des Resultats nicht angetastet. b = a[4:] print('Vorher: ', b) b[1] = 99 print('Nachher: ', b) print('Keine Änderung:', a) >>> Vorher: ['e', 'f', 'g', 'h'] Nachher: ['e', 99, 'g', 'h'] Keine Änderung: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] Bei Zuweisungen wird der angegebene Bereich in der ursprünglichen Liste ersetzt. Im Gegensatz zu Tupel-Zuweisungen (wie a, b = c[:2]), müssen die Bereiche nicht gleich groß sein. Die Werte vor und hinter dem angegebenen Bereich bleiben erhalten. Gegebenenfalls wächst oder schrumpft die Liste, um sich an die neuen Werte anzupassen. print('Vorher ', a) a[2:7] = [99, 22, 14] print('Nachher ', a) >>> Vorher ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] Nachher ['a', 'b', 99, 22, 14, 'h'] Falls Sie sowohl die Start- als auch End-Indizes weglassen, wird eine Kopie der ursprünglichen Liste zurückgegeben: b = a[:] assert b == a and b is not a 29 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge Bei einer Zuweisung ohne Start- und End-Index wird der gesamte Inhalt durch eine Kopie des angegebenen Objekts ersetzt (statt eine neue Liste zu erzeugen): b=a print('Vorher ', a) a[:] = [101, 102, 103] assert a is b # Dasselbe Listenobjekt print('Nachher', a) # Hat jetzt anderen Inhalt >>> Vorher ['a', 'b', 99, 22, 14, 'h'] Nachher [101, 102, 103] 1.5.1 Kompakt 쐽 Vermeiden Sie Weitschweifigkeit und lassen Sie die Null als Start-Index oder die Länge der Liste als End-Index weg. 쐽 Der Zugriff auf zu große oder zu kleine Start- und End-Indizes wird korrekt gehandhabt und erleichtert es, auf die ersten oder die letzten Elemente einer Liste zuzugreifen (z.B. a[:20] oder a[-20:]). 쐽 Die Zuweisung an einen Listenbereich ersetzt ihn in der ursprünglichen Liste durch die angegebene Liste, selbst wenn sich deren Längen unterscheiden. 1.6 Punkt 6: Verzicht auf Abstand und Start-/End-Index Neben dem grundlegenden Zugriff auf Listen (siehe Punkt 5: Zugriff auf Listen) kennt Python eine spezielle Syntax, um auf Elemente in einem bestimmten Abstand zuzugreifen: somelist[start,ende:abstand]. Auf diese Weise können Sie jedes n-te Objekt auswählen. Die Abstandsangabe ermöglicht es beispielsweise, die Listenelemente nach geraden und ungeraden Indizes zu gruppieren: a = ['Rot', 'Orange', 'Gelb', 'Grün', 'Blau', 'Lila'] ungerade = a[::2] gerade = a[1::2] print(ungerade) print(gerade) >>> ['Rot', 'Gelb', 'Blau'] ['Orange', 'Grün', 'Lila'] Problematisch ist allerdings, dass diese Syntax häufig unerwartete Ergebnisse liefert, die zu Bugs führen. Es gibt da beispielsweise den bekannten Python-Trick 30 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.6 Punkt 6: Verzicht auf Abstand und Start-/End-Index zum Umkehren der Zeichenreihenfolge eines Bytestrings durch die Verwendung des Abstands -1: x = b'mungo' y = x[::-1] print(y) >>> b'ognum' Das funktioniert mit Bytestrings und ASCII-Zeichen problemlos, schlägt jedoch bei als UTF-8-kodierten Unicode-Zeichen fehl: w = '䅍䅍' x = w.encode('utf-8') y = x[::-1] z = y.decode('utf-8') >>> UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9d in position 0: invalid start byte Sind negative Abstände außer -1 überhaupt sinnvoll? Betrachten Sie folgendes Beispiel: a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] a[::2] # ['a', 'c', 'e', 'g'] a[::-2] # ['h', 'f', 'd', 'b'] Hier besagt ::2, dass jedes zweite Element ausgewählt werden soll. Etwas kniffliger ist ::-2, das ebenfalls jedes zweite Element auswählt, die Zählung jedoch am Ende beginnt und sich nach vorne vorarbeitet. Was bewirkt wohl 2::2? Oder -2::-2 bzw. -2:2:-2 und 2:2:-2? a[2::2] # ['c', 'e', 'g'] a[-2::-2] # ['g', 'e', 'c', 'a'] a[-2:2:-2] # ['g', 'e'] a[2:2:-2] # [] Der entscheidende Punkt ist hier, dass der Abstand bei dieser Syntax manchmal große Verwirrung stiftet. Drei Zahlen innerhalb der eckigen Klammern sind aufgrund der Informationsdichte schon schwierig genug zu entschlüsseln. Wenn dann auch noch Start- und End-Index als Ausgangspunkt des Abstands ins Spiel kommen, geht die Übersicht vollends verloren, insbesondere bei negativem Abstand. 31 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge Um Schwierigkeiten aus dem Weg zu gehen, sollten Sie auf diese Syntax lieber verzichten. Falls Sie darauf angewiesen sind, sollten Sie einen positiven Wert als Abstand verwenden und Start- und End-Index weglassen. Wenn Sie auch auf Startund End-Index nicht verzichten können, sollten Sie zwei aufeinanderfolgende Zuweisungen in Betracht ziehen: b = a[::2] # ['a', 'c', 'e', 'g'] c = b[1:-1] # ['c', 'e'] Erst einen Bereich und danach die Elemente mit konstantem Abstand auszuwählen, erzeugt eine zusätzliche Kopie der Daten. Die erste Operation sollte die Größe des verbleibenden Bereichs möglichst stark verringern. Falls Ihrem Programm die Zeit oder der Speicher für zwei Schritte fehlt, können Sie die islice-Methode des integrierten Moduls itertools verwenden (siehe Punkt 46: Verwendung von Algorithmen und Datenstrukturen der Standardbibliothek), die keine negativen Werte für die drei Parameter zulässt. 1.6.1 Kompakt 쐽 Die Verwendung von Abstand, Start- und End-Index zur Auswahl von Elementen einer Liste kann äußerst verwirrend sein. 쐽 Verwenden Sie vorzugsweise positive Werte für den Abstand und lassen Sie Start- und End-Index weg. Verzichten Sie möglichst auf negative Abstandswerte. 쐽 Verzichten Sie möglichst auf den Einsatz von Abstand, Start- und End-Index zur Auswahl von Listenelementen. Falls Sie alle drei Parameter benötigen, sollten Sie zwei Zuweisungen in Betracht ziehen (eine zur Bereichsauswahl und eine weitere, um die Elemente mit konstantem Abstand auszuwählen) oder die islice-Methode des integrierten itertools-Moduls verwenden. 1.7 Punkt 7: Listen-Abstraktionen statt map und filter Python stellt verschiedene kompakte Befehle zur Verfügung, um von Listen weitere Listen abzuleiten. Die Gesamtheit dieser Ausdrücke wird als Listen-Abstraktion (engl. List Comprehension) bezeichnet. Nehmen Sie beispielsweise an, Sie möchten das Quadrat sämtlicher Zahlen einer Liste berechnen. Zu diesem Zweck könnten Sie den zu berechnenden Ausdruck angeben und in einer Schleife die Eingabewerte durchlaufen: a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] squares = [x**2 for x in a] 32 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.7 Punkt 7: Listen-Abstraktionen statt map und filter print(squares) >>> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] Falls Sie nicht nur Funktionen mit einem einzelnen Argument auf die Werte anwenden, ist die Listen-Abstraktion in einfachen Fällen leichter verständlich als die integrierte map-Funktion. Deren Verwendung erfordert das Anlegen einer lambda-Funktion, die schon rein optisch umständlich wirkt: squares = map(lambda x: x ** 2, a) Im Gegensatz zu map können Sie außerdem leicht Eingabewerte filtern und so die zugehörigen Ergebnisse aus der resultierenden Liste entfernen. Vielleicht möchten Sie nur die Quadratzahlen der geraden Zahlen in der Liste berechnen. Fügen Sie dann der Schleife einfach einen bedingten Ausdruck hinzu: even_squares = [x**2 for x in a if x % 2 == 0] print(even_squares) >>> [4, 16, 36, 64, 100] Die integrierte filter-Funktion kann zusammen mit map eingesetzt werden, um dasselbe Resultat zu erzielen, ist aber viel schwerer verständlich: alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)) assert even_squares == list(alt) Dictionaries und Sets besitzen eigene Pendants zu den Listen-Abstraktionen, die es bei der Programmierung von Algorithmen erleichtern, abgeleitete Datenstrukturen zu erzeugen: obst = {'Apfel': 1, 'Banane': 2, 'Pampelmuse': 3} sorte_dict = {sorte: name for name, sorte in obst.items()} länge = {len(name) for name in sorte_dict.values()} print(sorte_dict) print(länge) >>> {1: 'Apfel', 2: 'Banane', 3: 'Pampelmuse'} {10, 5, 6} 33 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge 1.7.1 Kompakt 쐽 Listen-Abstraktionen sind leichter verständlich als die integrierten map- und filter-Funktionen, weil keine lambda-Ausdrücke erforderlich sind. 쐽 Listen-Abstraktionen ermöglichen ein einfaches Überspringen von Elementen der Eingabeliste. map benötigt für diese Funktionalität Unterstützung durch die filter-Funktion. 쐽 Dictionaries und Sets unterstützen vergleichbare Mechanismen. 1.8 Punkt 8: Nicht mehr als zwei Ausdrücke in Listen-Abstraktionen Neben der grundlegenden Verwendung (siehe Punkt 7: Listen-Abstraktionen statt map und filter) unterstützten Listen-Abstraktionen auch mehrfach verschachtelte Schleifen. So können Sie beispielsweise eine Matrix (eine Liste von Listen) in eine einfache Liste umwandeln. Ich verwende hier zu diesem Zweck eine ListenAbstraktion mit zwei weiteren Ausdrücken, die in der angegebenen Reihenfolge (von links nach rechts) ausgeführt werden: matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] flat = [x for row in matrix for x in row] print(flat) >>> [1, 2, 3, 4, 5, 6, 7, 8, 9] Dieses Beispiel ist einfach, leicht verständlich und stellt eine sinnvolle Verwendung verschachtelter Schleifen dar. Sie können die Matrixform aber auch beibehalten, wenn Sie z.B. die Elemente einer zweidimensionalen Matrix quadrieren möchten. Der Ausdruck ist aufgrund der zusätzlichen eckigen Klammern ein wenig komplexer, aber noch immer gut verständlich: squared = [[x**2 for x in row] for row in matrix] print(squared) >>> [[1, 4, 9], [16, 25, 36], [49, 64, 81]] Falls sich dieser Ausdruck innerhalb einer weiteren Schleife befindet, wird die Listen-Abstraktion so lang, dass sie sich über mehrere Zeilen erstreckt: lists = [ [[1, 2, 3], [4, 5, 6]], 34 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.8 Punkt 8: Nicht mehr als zwei Ausdrücke in Listen-Abstraktionen # ... ] flat = [x for sublist1 in lists for sublist2 in sublist1 for x in sublist2] Eine solche mehrzeilige Listen-Abstraktion ist kaum noch kürzer als die Alternative, die ich hier durch herkömmliche Schleifen formuliere. Die Einrückungen verdeutlichen die Verschachtelung sogar besser als die Listen-Abstraktion: flat = [] for sublist1 in lists: for sublist2 in sublist1: flat.extend(sublist2) Listen-Abstraktionen unterstützen auch mehrere Bedingungen. Beziehen sie sich auf dieselbe Schleife, werden sie implizit mittels des and-Operators miteinander verknüpft. Das folgende Beispiel zeigt zwei gleichwertige Möglichkeiten, gerade Zahlen herauszufiltern, die größer als 4 sind: a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] b = [x for x in a if x > 4 if x % 2 == 0] c = [x for x in a if x > 4 and x % 2 == 0] Die für eine Schleifenebene geltenden Bedingungen werden nach der for-Anweisung angegeben. Das nächste Beispiel filtert die durch 3 teilbaren Elemente einer Matrix heraus, die sich in Zeilen befinden, deren Summe größer oder gleich 10 ist. Die Formulierung mittels Listen-Abstraktion ist zwar kompakt, aber sehr schwer verständlich: matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] filtered = [[x for x in row if x % 3 == 0] for row in matrix if sum(row) >= 10] print(filtered) >>> [[6], [9]] Auch wenn dieses Beispiel ein wenig konstruiert ist, kommt es in der Praxis gelegentlich vor, dass Ausdrücke wie dieser scheinbar genau das Richtige sind. Ich rate jedoch von Listen-Abstraktionen dieser Art dringend ab. Der Code ist für andere nur sehr schwer verständlich. Angesichts der daraus resultierenden Schwierigkeiten lohnt es sich nicht, hier ein paar Zeilen Code einzusparen. 35 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge Die Faustregel lautet, in einer Listen-Abstraktion nicht mehr als zwei Ausdrücke zu verwenden. Dabei kann es sich um zwei Bedingungen, zwei Schleifen oder eine Bedingung und eine Schleife handeln. Sobald es komplizierter wird, sollten Sie lieber normale if- und for-Anweisungen verwenden und eine kleine Hilfsfunktion programmieren (siehe Punkt 16: Generatoren statt Rückgabe von Listen). 1.8.1 Kompakt 쐽 Listen-Abstraktionen unterstützen mehrfach verschachtelte Schleifen und mehrere Bedingungen pro Schleifenebene. 쐽 Listen-Abstraktionen mit mehr als zwei Ausdrücken sind sehr schwer verständlich und sollten vermieden werden. 1.9 Punkt 9: Generator-Ausdrücke in Listen-Abstraktionen An Listen-Abstraktionen (siehe Punkt 7: Listen-Abstraktionen statt map und filter) ist problematisch, dass sie unter Umständen komplett neue Listen erzeugen, die genauso viele Elemente wie die ursprüngliche Liste besitzen. Bei kleinen Listen spielt das kaum eine Rolle, bei sehr langen Eingaben hingegen kann es beträchtlichen Speicherbedarf verursachen, der das Programm sogar zum Absturz bringen kann. Nehmen wir an, Sie möchten eine Datei einlesen und die Anzahl der Zeichen pro Zeile ermitteln. Falls Sie dafür eine Listen-Abstraktion benutzen, muss die Länge jeder einzelnen Zeile im Arbeitsspeicher abgelegt werden. Wenn nun die Eingabedatei sehr groß ist oder es sich womöglich um einen Netzwerk-Socket handelt, bei dem das Einlesen kein Ende nimmt, kann es zu Problemen kommen. Hier ist ein Beispiel für die Verwendung einer Listen-Abstraktion, die nur für kurze Eingaben geeignet ist: value = [len(x) for x in open('/tmp/datei.txt')] print(value) >>> [100, 57, 15, 1, 12, 75, 5, 86, 89, 11] Aus diesem Grund stellt Python Generator-Ausdrücke zur Verfügung, die eine Verallgemeinerung von Listen-Abstraktionen und Generatoren darstellen. Ein solcher Generator-Ausdruck gibt nicht die gesamte Ausgabe zurück, sondern stellt einen Iterator bereit, der für den Ausdruck schrittweise einzelne Werte liefert. Ein Generator-Ausdruck besteht aus einem in runden Klammern eingefassten Listen-Abstraktions-ähnlichen Ausdruck. Ich erstelle hier einen dem obigen Code 36 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.9 Punkt 9: Generator-Ausdrücke in Listen-Abstraktionen äquivalenten Generator-Ausdruck, der allerdings sofort einen Iterator zurückgibt und noch nicht mit der Abarbeitung beginnt. it = (len(x) for x in open('/tmp/datei.txt')) print(it) >>> <generator object <genexpr> at 0x101b81480> Der zurückgelieferte Iterator kann nun dazu verwendet werden, mittels der integrierten next-Funktion schrittweise den Generator-Ausdruck auszuwerten. Auf diese Weise kann der Ausdruck die Datei allmählich einlesen, ohne zu riskieren, dass der Speicherbedarf explodiert. print(next(it)) print(next(it)) >>> 100 57 Darüber hinaus können Generator-Ausdrücke auch verschachtelt werden. Hier verwende ich den soeben erzeugten Generator als Eingabe in einem weiteren Generator-Ausdruck: roots = ((x, x**0.5) for x in it) Bei jedem Fortschreiten dieses Iterators rückt auch der innere Iterator um einen Schritt vor, wodurch wie bei einem Dominoeffekt die Bedingungen ausgewertet und Ein- und Ausgabewerte herumgereicht werden. print(next(roots)) >>> (15, 3.872983346207417) Solche Verknüpfungen von Generatoren sind in Python außerordentlich schnell. Falls Sie nach einem Mechanismus zur Verarbeitung großer Datenströme suchen, sind Generator-Ausdrücke bestens geeignet. Der einzige Vorbehalt besteht darin, dass die von Generator-Ausdrücken zurückgelieferten Iteratoren zustandsbehaftet sind und Sie daher darauf achtgeben müssen, sie nicht mehrfach zu verwenden (siehe Punkt 17: Vorsicht beim Iterieren über Argumente). 37 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge 1.9.1 Kompakt 쐽 Listen-Abstraktionen können bei großen Eingaben durch zu hohen Speicherbedarf Probleme verursachen. 쐽 Generator-Ausdrücke umgehen dieses Problem, indem sie einen Iterator zur schrittweisen Verarbeitung bereitstellen. 쐽 Generator-Ausdrücke können verschachtelt werden, indem der von einem Generator-Ausdruck zurückgegebene Iterator in der for-Anweisung eines weiteren Generator-Ausdrucks verwendet wird. 쐽 Verknüpfte Generator-Ausdrücke werden sehr schnell ausgeführt. 1.10 Punkt 10: enumerate statt range Die integrierte range-Funktion kann für Schleifen verwendet werden, die einen Zahlenbereich durchlaufen: random_bits = 0 for i in range(64): if randint(0, 1): random_bits |= 1 << i Wenn Sie eine Datenstruktur wie eine Liste mit Strings durchlaufen möchten, ist dies unmittelbar möglich: flavor_list = ['Vanille', 'Schoko', 'Nuss', 'Erdbeer'] for flavor in flavor_list: print('%s ist lecker' % flavor) Es kommt häufiger vor, dass man eine Liste durchlaufen möchte und der Index des aktuellen Elements dabei bekannt ist. Wenn Sie beispielsweise die Reihenfolge Ihrer Lieblingseissorten ausgeben möchten, können Sie dazu range verwenden: for i in range(len(flavor_list)): flavor = flavor_list[i] print('%d: %s' % (i + 1, flavor)) Im Vergleich zu den anderen Beispielen, die über flavor_list oder range iterieren, sieht das ein wenig unbeholfen aus. Sie müssen erst die Länge der Liste ermitteln und über den Index auf die Elemente des Arrays zugreifen – es liest sich einfach nicht gut. 38 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.11 Punkt 11: Gleichzeitige Verarbeitung von Iteratoren mit zip Für solche Aufgaben gibt es in Python die integrierte enumerate-Funktion, die dem Iterator einen Generator bereitstellt, der Paare aus Schleifenindex und dem zugehörigen Iterator liefert. Der Code ist viel klarer: for i, flavor in enumerate(flavor_list): print('%d: %s' % (i + 1, flavor)) >>> 1: Vanille 2: Schoko 3: Nuss 4: Erdbeer Das lässt sich weiter verkürzen, indem Sie angeben, bei welchem Wert (in diesem Fall 1) enumerate mit der Zählung beginnen soll: for i, flavor in enumerate(flavor_list, 1): print('%d: %s' % (i, flavor)) 1.10.1 Kompakt 쐽 enumerate bietet eine kompakte Syntax zum Durchlaufen eines Iterators und stellt den Index des jeweiligen Elements zur Verfügung. 쐽 Verwenden Sie statt range oder Indizes lieber enumerate. 쐽 Sie können enumerate einen zweiten Parameter übergeben, der festlegt, bei welchem Wert die Zählung beginnt (Standardwert ist 0). 1.11 Punkt 11: Gleichzeitige Verarbeitung von Iteratoren mit zip In Python hat man es häufig mit mehreren Listen ähnlicher Objekte zu tun. Mittels Listen-Abstraktionen können Sie durch Anwendung eines Ausdrucks auf die Elemente einer Liste leicht eine weitere Liste erzeugen (siehe Punkt 7: ListenAbstraktionen statt map und filter). names = ['Cecilia', 'Lise', 'Marie'] letters = [len(n) for n in names] Die Elemente der abgeleiteten Liste sind mit den ursprünglichen Elementen durch ihren Index miteinander verknüpft. Um beide Listen gleichzeitig zu verarbeiten, können Sie über die Länge der Liste names iterieren: 39 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge longest_name = None max_letters = 0 for i in range(len(names)): count = letters[i] if count > max_letters: longest_name = names[i] max_letters = count print(longest_name) >>> Cecilia Das Problem ist hier die unschöne Schleife. Die Zugriffe auf die names- und letters-Arrays über die Indizes machen den Code schwer lesbar. Der Schleifenindex i wird gleich zwei Mal benutzt. Das lässt sich durch den Einsatz von enumerate etwas verbessern (siehe Punkt 10: enumerate statt range), aber ideal ist das noch lange nicht. for i, name in enumerate(names): count = letters[i] if count > max_letters: longest_name = name max_letters = count Code wie dieser lässt sich mittels der in Python integrierten zip-Funktion vereinfachen. In Python 3 stellt sie für zwei oder mehr Iteratoren einen Generator bereit. Dieser zip-Generator liefert Tupel mit den jeweils nächsten Werten der Iteratoren. Der resultierende Code ist erheblich besser verständlich als die Verwendung von Indizes für mehrere Listen. for name, count in zip(names, letters): if count > max_letters: longest_name = name max_letters = count Es gibt hier jedoch zwei Schwierigkeiten. Zum einen ist zip in Python 2 kein Generator, sondern wertet die übergebenen Iteratoren vollständig aus und liefert eine Liste aller erzeugten Tupel zurück. Das kann potenziell zu großem Speicherbedarf führen und das Programm zum Absturz bringen. Falls Sie umfangreiche Iteratoren in Python 2 verwenden möch- 40 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.11 Punkt 11: Gleichzeitige Verarbeitung von Iteratoren mit zip ten, sollten Sie besser die izip-Funktion des integrierten itertools-Moduls verwenden (siehe Punkt 46: Verwendung von Algorithmen und Datenstrukturen der Standardbibliothek). Zum anderen verhält sich die zip-Funktion eigentümlich, wenn die zu den Iteratoren zugehörigen Listen unterschiedlich lang sind. Wenn Sie beispielsweise der obigen Liste einen weiteren Namen hinzufügen, dabei aber übersehen, auch die Liste der Buchstabenanzahl zu aktualisieren, liefert die Ausführung der zip-Funktion ein unerwartetes Ergebnis: names.append('Rosalinde') for name, count in zip(names, letters): print(name) >>> Cecilia Lise Marie Der neue Name »Rosalinde« fehlt. Das liegt an der Funktionsweise der zip-Funktion: Sie liefert Tupel zurück, bis irgendeiner der übergebenen Iteratoren abgearbeitet ist. Dieser Ansatz funktioniert gut, sofern feststeht, dass die Listen gleich lang sind – was bei durch Listen-Abstraktionen erzeugten Listen meist zutrifft. In vielen anderen Fällen ist dieses Verhalten der zip-Funktion jedoch überraschend und ärgerlich. Falls Sie sich nicht sicher sind, dass die von der zip-Funktion zu verarbeitenden Listen von gleicher Länge sind, sollten Sie in Betracht ziehen, die zip_longest-Funktion des integrierten itertools-Moduls zu verwenden (die in Python 2 ebenfalls die Bezeichnung zip_longest trägt). 1.11.1 Kompakt 쐽 Zur gleichzeitigen Verarbeitung mehrerer Iteratoren kann die integrierte zipFunktion verwendet werden. 쐽 In Python 3 ist die zip-Funktion ein Generator, der Tupel erzeugt. In Python 2 liefert zip eine Liste sämtlicher Tupel zurück. 쐽 Die zip-Funktion bricht die Ausgabe stillschweigend ab, falls Sie ihr Iteratoren für Listen unterschiedlicher Länge übergeben. 쐽 Die zip_longest-Funktion des integrierten itertools-Moduls ermöglicht die gleichzeitige Verarbeitung mehrerer Iteratoren unabhängig von der Länge der zugehörigen Listen (siehe Punkt 46: Verwendung von Algorithmen und Datenstrukturen der Standardbibliothek). 41 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge 1.12 Punkt 12: Verzicht auf else-Blöcke nach for- und while-Schleifen Die in Python verfügbaren Schleifen besitzen eine Funktionalität, die in den meisten anderen Programmiersprachen nicht vorhanden ist: Sie können unmittelbar am Ende des Schleifenkörpers einen else-Block platzieren: for i in range(3): print('Schleife %d' % i) else: print('Else-Block!') >>> Schleife 0 Schleife 1 Schleife 2 Else-Block! Überraschenderweise wird er nach Beendigung der Schleife sofort ausgeführt. Warum nur lautet die Bezeichnung für diesen Block »else«? Bei einer if/elseAnweisung bedeutet »else«: »Führe diesen Block aus, sofern der vorhergehende nicht ausgeführt wurde.« Und auch bei einer try/except-Anweisung bedeutet »except«: »Führe diesen Block aus, sofern die Ausführung des try-Blocks fehlschlägt.« Das »else« in einer try/except/else-Anweisung verhält sich ebenfalls nach diesem Muster: (siehe Punkt 13: Alle Blöcke einer try/except/else/finally-Anweisung nutzen), denn es bedeutet: »Führe diesen Block aus, sofern der vorhergehende nicht fehlgeschlagen ist.« Und schließlich ist auch eine try/finally-Anweisung intuitiv, da sie besagt: »Führe stets diesen abschließenden Block aus, nachdem die Ausführung des vorhergehenden Blocks versucht wurde.« Angesichts all dieser Verhaltensweisen könnte ein Programmierneuling auf den Gedanken kommen, dass der else-Block einer for/else-Anweisung die Bedeutung hat: »Führe diesen Block aus, wenn die Schleife nicht vollständig durchlaufen wurde.« Tatsächlich bewirkt sie das Gegenteil. Eine break-Anweisung in der Schleife sorgt dafür, dass der else-Block übersprungen wird: for i in range(3): print('Schleife %d' % i) if i == 1: break else: print('Else-Block!') 42 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.12 Punkt 12: Verzicht auf else-Blöcke nach for- und while-Schleifen >>> Schleife 0 Schleife 1 Hier gibt es noch weitere Überraschungen: Wenn man eine leere Liste »durchläuft«, wird der else-Block ebenfalls ausgeführt: for x in []: print('Wird nie ausgeführt.') else: print('Else-Block der for-Schleife!') >>> Else-Block der for-Schleife! Der else-Block wird auch ausgeführt, wenn die Bedingung einer while-Schleife eingangs den Wert False besitzt: while False: print('Wird nie ausgeführt.') else: print('Else-Block der while-Schleife!') >>> Else-Block der while-Schleife! Der Grund für dieses Verhalten ist, dass else-Blöcke nach Schleifen nützlich sind, wenn in der Schleife etwas gesucht wird, beispielsweise wenn Sie feststellen möchten, ob zwei Zahlen teilerfremd sind (der einzige gemeinsame Teiler ist 1). Sie durchlaufen dann alle möglichen Teiler und überprüfen die Zahlen. Nachdem sämtliche Möglichkeiten durchprobiert wurden, endet die Schleife. Der elseBlock wird nur ausgeführt, wenn beide Zahlen tatsächlich teilerfremd sind, da in diesem Fall keine break-Anweisung in der Schleife ausgeführt wurde. a=4 b=9 for i in range(2, min(a, b) + 1): print('Teste ', i) if a % i == 0 and b % i == 0: print('Nicht teilerfremd') break else: print('Teilerfremd') 43 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge >>> Teste 2 Teste 3 Teste 4 Teilerfremd In der Praxis würden Sie das sicher nicht so programmieren, sondern eine Hilfsfunktion für die Berechnung verwenden. Solche Hilfsfunktionen folgen üblicherweise den beiden folgenden Mustern. Beim ersten Ansatz wird die Funktion sofort beendet, wenn die gesuchte Bedingung erfüllt ist. Anderenfalls wird ein Standardwert zurückgeliefert: def coprime(a, b): for i in range(2, min(a, b) + 1): if a % i == 0 and b % i == 0: return False return True Das zweite Verfahren verwendet eine Variable, die anzeigt, ob das Gesuchte gefunden wurde. Wenn dieser Fall eintritt, wird die Schleife mittels break beendet: def coprime2(a, b): is_coprime = True for i in range(2, min(a, b) + 1): if a % i == 0 and b % i == 0: is_coprime = False break return is_coprime Beide Vorgehensweisen sind für Betrachter, denen der Code nicht vertraut ist, erheblich besser verständlich. Die durch den else-Block gewonnene Ausdrucksfähigkeit wird von der Mühe, die Sie sich selbst und anderen beim Betrachten des Codes auferlegen, in den Schatten gestellt. Was einfache Konstrukte wie Schleifen bewirken, sollte in Python offensichtlich sein. Sie sollten daher auf else-Anweisungen nach Schleifen am besten komplett verzichten. 1.12.1 Kompakt 쐽 Python verfügt über eine spezielle Syntax, die es erlaubt, else-Blöcke unmittelbar hinter for- und while-Schleifen zu platzieren. 44 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.13 Punkt 13: Alle Blöcke einer try/except/else/finally-Anweisung nutzen 쐽 Dieser else-Block wird nur ausgeführt, wenn in der Schleife keine breakAnweisung stattfindet. 쐽 Verzichten Sie lieber auf else-Blöcke nach Schleifen, da ihr Verhalten nicht intuitiv ist und verwirrend sein kann. 1.13 Punkt 13: Alle Blöcke einer try/except/ else/finally-Anweisung nutzen Bei der Ausnahmebehandlung in Python können Sie zu vier verschiedenen Zeitpunkten auf einen Fehler reagieren. Diese Funktionalität findet sich in den try-, except-, else- und finally-Blöcken wieder. Jeder dieser Blöcke in einer daraus zusammengesetzten Anweisung dient einem eigenen Zweck, und die unterschiedlichen Kombinationen sind äußerst nützlich (siehe Punkt 51: Einrichten einer Root-Exception zur Abschottung von Aufrufern und APIs). 1.13.1 finally-Blöcke Verwenden Sie try/finally-Blöcke, um Exceptions dem aufrufenden Code zu übergeben, aber trotz der Exception Code zum Aufräumen auszuführen. Ein häufiger Anwendungsfall für einen try/finally-Block ist das verlässliche Schließen von Dateien. (Einen weiteren Ansatz finden Sie unter Punkt 43: contextlib und with-Anweisung statt try/finally-Konstrukt.) handle = open('/tmp/zufalls_daten.txt') # Könnte einen IOError verursachen try: data = handle.read() # Könnte einen UnicodeDecodeError verursachen finally: handle.close() # Nach try: immer ausführen Eine von der read-Methode ausgelöste Exception wird dem aufrufenden Code übergeben, dennoch wird die close-Methode im finally-Block auf jeden Fall ausgeführt. open muss vor dem try-Block aufgerufen werden, da beim Öffnen der Datei auftretende Exceptions (wie ein IOError, falls die Datei nicht existiert) den finally-Block überspringen sollen. 1.13.2 else-Blöcke Verwenden Sie try/except/else-Blöcke, um deutlich zu machen, welche Exceptions von Ihrem Code gehandhabt werden und welche dem aufrufenden Code übergeben werden. Falls der try-Block keine Exception auslöst, wird der else- 45 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Kapitel 1 Pythons Sicht der Dinge Block ausgeführt. Mittels des else-Blocks können Sie den Umfang des Codes im try-Block minimieren und dadurch die Verständlichkeit verbessern. Nehmen wir z.B. an, dass Sie JSON-Daten aus einem String in ein Dictionary einlesen und den zu einem Schlüssel gehörigen Wert zurückgeben möchten, den es enthält: def load_json_key(data, key): try: result_dict = json.loads(data) # Könnte einen ValueError verursachen except ValueError as e: raise KeyError from e else: return result_dict[key] # Könnte einen KeyError verursachen Falls die JSON-Daten nicht wohlgeformt sind, wird die Dekodierung durch json.loads einen ValueError verursachen. Diese Exception wird durch den except-Block abgefangen und gehandhabt. Wenn die Dekodierung gelingt, wird im else-Block der Schlüssel nachgeschlagen. Sollte dies eine weitere Exception auslösen, wird sie an den aufrufenden Code übergeben, da sie außerhalb des tryBlocks aufgetreten ist. Der else-Block gewährleistet, dass sich der dem try/ except folgende Code optisch vom except-Block abhebt. Dadurch wird der Ablauf der Exception-Weiterleitung verdeutlicht. 1.13.3 Alles zusammen Verwenden Sie try/except/else/finally-Blöcke, um all dies zusammenfassen. Nehmen Sie z.B. an, dass Sie eine Aufgabenliste aus einer Datei einlesen, diese verarbeiten und dann die Datei an Ort und Stelle aktualisieren möchten. Hier liest und verarbeitet der try-Block die Datei. Der except-Block handhabt die Exceptions des try-Blocks, die auftreten könnten. Der else-Block aktualisiert die Datei an Ort und Stelle. Dabei auftretende Exceptions werden dem aufrufenden Code übergeben. Zu guter Letzt schließt der finally-Block dann die Datei. UNDEFINED = object() def divide_json(path): handle = open(path, 'r+') # Könnte IOError verursachen try: data = handle.read() # Könnte UnicodeDecodeError verursachen op = json.loads(data) # Könnte ValueError verursachen 46 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 1.13 Punkt 13: Alle Blöcke einer try/except/else/finally-Anweisung nutzen value = (op['numerator'] / op['denominator']) # Könnte ZeroDivisionError verursachen except ZeroDivisionError as e: return UNDEFINED else: op['result'] = value result = json.dumps(op) handle.seek(0) handle.write(result) # Könnte IOError verursachen return value finally: handle.close() # Immer ausführen Dieser Aufbau ist besonders nützlich, weil alle Blöcke auf intuitive Art und Weise zusammenarbeiten. Wenn z.B. im else-Block beim Schreiben der Datei eine Exception auftritt, wird dennoch der finally-Block ausgeführt, der die Datei schließt. 1.13.4 Kompakt 쐽 Die try/finally-Anweisungen ermöglichen es, Code zum Aufräumen auszuführen – unabhängig davon, ob im try-Block eine Exception ausgelöst wurde. 쐽 Der else-Block ermöglicht es, den Umfang des Codes im try-Block zu minimieren und den Erfolgsfall optisch von den try/except-Blöcken abzuheben. 쐽 Im else-Block können Sie nach erfolgreicher Ausführung des try-Blocks, jedoch vor Ausführung des finally-Blocks, weitere Aufgaben erledigen. 47 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Stichwortverzeichnis Symbole Numerisch _ (Unterstrich) 50, 82, 107 __ (doppelter Unterstrich) 105 __all__-Attribut 220 __call__-Methode 88 __dict__ 100, 130, 144, 247 __doc__ 213 __enter__ 189 __exit__ 189 __get__-Methode 126 __getattr__-Methode 130 __getattribute__ 132 __init__.py 218, 221 __init__-Konstruktor 100 __init__-Methode 93, 96, 136 __iter__-Methode 62 __len__-Methode 113 __main__-Modul 242 __metaclass__ 137 __new__-Methode 136 __repr__-Funktion 246 __set__-Methode 126 __setattr__ 134 . (Operator) 105 @ (Decorator) 186 @classmethod 93 @contextmanager 189 @property 116, 120, 125 @-Symbol 186 * (Operator) 65, 75 *args 66 % (Operator) 244 %r-Platzhalter 247 %s (Formatstring) 23 %s-Platzhalter 247 + (Operator) 23 += (Operator) 159 2to3 18 8-Bit-Werte 21 A Abhängigkeit Injektion von Abhängigkeiten 231 reproduzieren 237 transitive 233 zirkuläre 227 Abstraktionsschicht 82 Algorithmus 203 and (Operator) 35 API 220 append-Methode 57 Argument optionales 64 as-Klausel 220 as-Klausel (import-Anweisung) 219 assert 28 assert-Anweisung 249 AssertionError-Exception 249 asyncio-Modul 156 Attribut Bezeichnung 109 dynamisches 115 einfaches 116 öffentliches 105 privates 105, 107 Standardwert 194 Attribut-Dictionary 106 AttributeError-Exception 105, 132, 229 Attributname 106 Ausdruck bedingter 33 boolescher 25 komplizierter 24 langer 19 yield from 177 263 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Stichwortverzeichnis Ausgabeumlenkung 149 Ausnahmebehandlung 45 B Beenden-Signal 168 Besucherzahlen analysieren 60 Bezeichner 19 Binärbaum 101 bisect-Modul 206, 255 Bisektion 255 break-Anweisung 42 Bytecode 152 Bytecode-Instruktionen 153 bytes 21 C Cache 254 callable-Funktion 88 C-Erweiterung 179 class-Anweisung 115, 136, 138, 142 class-Block 105 close-Methode 45 Closure 52, 87 für zustandsbehaftete Hooks 87 cls (class) 20 cls() 93 Code testen 248 collections.abc-Modul 113 collections-Modul 83, 203 communicate-Methode 148–149 concurrent.futures-Modul 180 configparser-Modul 243 configure-Funktion 230 Container-Klasse 84, 110 contextlib-Modul 188 copyreg 197 copyreg-Modul 194 Coroutine 169 Arbeitsspeicherbedarf 169 Generator 169 Python 2 177 send-Methode 172 count-Methode 113 cProfile-Modul 254 CPython 17, 152 CPython-Interpreter 154 Cython 179 D Datei einlesen 90 schließen 45 Datenfeld privates 105 Datenklassen 83, 103 Datenstrom Verarbeitung 37 Datenstruktur 203 Datenübermittlung 54 datetime-Funktion 71 datetime-Modul 200 Debuggen 251 Debugger 251 schrittweise Ausführung 252 Variablen anzeigen 252 decimal-Modul 209 decode-Methode 21 Decorator 185 defaultdict 86 defaultdict-Klasse 205 Dependency Injection 231 Deployment-Umgebung 241 Deque 203 Deserialisierung 104, 140, 182, 192 Deskriptor 119, 126 Deskriptor-Protokoll 127 Diamond Inheritance 96 Dictionary 33, 72, 79 Standardwert 205 Digitalkamera 161 Division 50 Division durch null 49, 73 Docstring 71, 213–215 doctest-Modul 217 Dokumentation 214 Dokumentenerzeugung 214 E Eingabeumlenkung 149 Einrückung 19 else-Block 42, 46 encode-Methode 21 End-Index 30 Entwicklungsumgebung 241 enumerate-Funktion 39 Erzeuger 165 264 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Stichwortverzeichnis eval-Funktion 245 except-Block 46, 225 Exception 45, 49–50 Typ 226 F Faktorisierungsalgorithmus 153 Fibonacci 186 Field-Deskriptor 144 filter-Funktion 33 finally-Block 45 First-Class-Objekt 52, 86 Flag 53, 74 Fließkommazahlen-Division 74 Flussrate 68 for-Anweisung 35, 42, 62 Formatstring 23, 244 fractions-Modul 210 functools-Modul 187 Funktion 49 als Argument 89 Argumente mehrfach durchlaufen 64 Standardwerte 68 variable Parameterzahl 66 zustandslose 86 G Game of Life 171 Garbage Collection 258 gc-Modul 258 Generator 57 externe Eingabe 170 Iterator 57 Speicherbedarf 58 Generator-Ausdruck 36 Iterator 36 Generator-Funktion 170 Geschwindigkeitsmessung 256 getattr-Funktion 133 get-Methode 24, 164 Getter-Methode 115 GIL Siehe Global Interpreter Lock Gitternetz 175 Gleiter 176 Global Interpreter Lock 152, 179 global-Anweisung 55 Größter gemeinsamer Teiler 180 Gültigkeitsbereich 53 Gültigkeitsbereiche in Python 2 56 H hasattr-Funktion 100, 133 hashlib-Modul 151 heapq-Modul 205 Helikoptersteuerung 156 Helligkeitsmessung 157 help-Funktion 187 Hilfsfunktion 24, 26, 36, 44 Hook 85 I IEEE 754 208 if/else-Anweisung 26 Import 20 dynamischer 231 import * 223 import this 17 import-Anweisung 20, 222 Importpfad 197 Importreihenfolge 229 Index negativer 29 IndexError-Exception 162 index-Methode 113, 206 Injektion von Abhängigkeiten 231 Insertion-Sort 253 Instanz-Dictionary 130 Instanzerzeugung 93 Instanzreferenz 129 Integrationstest 250 int-Funktion 26 IOError 45 isinstance 100 islice-Methode 32 Istwert 158 Iterator 37 ausgeschöpfter 60 gleichzeitige Verarbeitung 39 Iterator-Protokoll 62 iter-Funktion 62 Iterieren 59 itertools-Modul 32, 41, 207 izip-Funktion 41 J join-Methode 166 JSON 46, 103, 139 JSON (JavaScript Object Notation) 192 json-Modul 192 JSON-Serialisierung 103 265 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Stichwortverzeichnis K Karo-Vererbung 96 Kindprozess 148–149 Klasse 79 mit Metaklasse registrieren 138 Versionierung 196 Klassenattribut 126, 137 Zugriff mit Metaklasse 143 Klassenhierarchie 82 Umstrukturierung 99 Klassenmethode 93 Konfigurationsparameter 93 Konstruktor 92 Kontextmanager 189 L lambda-Ausdruck 61 lambda-Funktion 33 Laufzeitfehler 142 Leaky-Bucket-Algorithmus 120 Leerer Wert 20 Leerraum 19 Leerzeichen 19 List Comprehension 32 Liste Abstand 31 als Parameter 66 Elemente 52 Elemente zählen 110 Index 28 Zugriff auf 27 Zuweisung 29 Listen-Abstraktion 32 Listenelement gruppieren 30 list-Funktion 58 list-Klasse 85, 111 locals-Funktion 252 localtime-Funktion 199 Lock 157 Lock-Klasse 159, 188 logging-Modul 244 Log-Level 191 M map-Funktion 33 MapReduce 90 Matrix 34 md5-Funktion 150 md5-Hash 151 Messungen vornehmen 253 Meta.__new__-Methode 145 Metaklasse 115, 136–137, 145 Method Resolution Order (MRO) 97, 99 Mix-in 100 Mix-ins kombinieren 103 mktime-Funktion 199 Modul __main__ 242 asyncio 156 bisect 206, 255 collections 83, 203 collections.abc 113 concurrent.futures 180 configparser 243 contextlib 188 copyreg 194 cProfile 254 datetime 200 decimal 209 doctest 217 fractions 210 functools 187 gc 258 Gültigkeitsbereich 55 hashlib 151 heapq 205 itertools 32, 41, 207 json 192 laden 72 logging 244 multiprocessing 180 pdb 251 pickle 192 profile 254 pstats 254 pytz 201 queue 164 select 152 subprocess 148 sys 18, 243 threading 159 time 199 tracemalloc 259 unittest 248 weakref 129 MRO (Method Resolution Order) 97, 99 mro-Methode 98 266 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Stichwortverzeichnis multiprocessing-Modul 180 Multithreading 153 Mutex-Sperre 152, 157 N namedtuple 83 Nachteile 83 NameErrorException 53 Namenskonflikt 109, 220 Namenskonvention 107 Namensraum 219 Navier-Stokes-Gleichungen 180 Nebenläufigkeit 147 Negativer Index 29 Negierung 20 next-Funktion 37, 62 None 49, 71, 73 nonlocal-Anweisung 54 Normierungsfunktion 59 nose 250 Numba 179 O Objekt generische Erzeugung 89 Interna 107 Öffentliches Attribut 105 open-Befehl 23 open-Methode 190 openssl 149 Operator ternärer 27 Optimierung 253 Optionales Argument 64 or (Operator) 25 OrderedDict-Klasse 204 OverflowError-Exception 73 P Package 218 Paket 218 Parallele Ausführung 147, 179 Parameterzahl konstante 64 variable 65 pdb-Modul 251 PEP 19 Pfadbezeichnung 20 pickle-Modul 192 pip 211 Pipe 149 Pipeline 161 Point2D 139 Polygon 137 Polymorphismus 90 Popen 148 Portierung 179 Portierung nach C 180 Prädikat-Funktion 207 print-Funktion 244 Prinzip der geringsten Überraschung 115 Privates Attribut 105, 107 Privates Datenfeld 105 ProcessPoolExecutor-Klasse 181 Produktivumgebung 241 profile-Modul 254 Profiler 253 Programmierstil 17 Programmlogik wiederholte Verwendung 27 Protokollmeldung 70 pstats-Modul 254 Punkt-Operator 105 put-Methode 164 pyenv 234 pyenv-Befehl 235 Pylint 20 pytest 250 Python Dokumentation 214 Interpreter 53 Laufzeitumgebung 17 Portierung 179 Programmierstil 17 Programmstruktur 64 Standardbibliothek 185, 203 Standardinstallation 185 Standardmodule 185 Stärken 11 Syntax 27 Version 17 Zen of 20 Zusammenarbeit 213 Python Package Index 211 pythonic 11, 17 pytz-Modul 201 267 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Q Quadratzahlen 33 quantize-Methode 210 Queue 160 queue-Modul 164 R range-Funktion 38 Read the Docs 214 read-Methode 45, 90 Refactoring 82 Reference Counting 258 Rekursion endlose 131 repr-Funktion 245 requirements.txt 237 Root-Exception 224 Rückgabewert 51, 57 Runden 210 S Schleife for 42, 62 verschachtelte 34 while 43 Schleifenindex 39 Schlüsselwort-Argument 67 Python 2 75 Schlüsselwort-Argumente 66 Schlüsselwort-Argumente in Python 2 75 Schreibmodus 23 Scoping Bug 54 select-Modul 152 self 20 send-Funktion 170 Serialisierung 100, 104, 139, 182, 192 Serieller Port 155 Set 33 set_trace-Funktion 251 Setter-Methode 115 setUp-Methode 249 Sichtbarkeitsbereich 106 six 18 Sollwert 158 sort-Methode 52, 85 Speicherbedarf 40 Speicherbereinigung 258 Speicherleck 129, 258 Sphinx 214 Spiel des Lebens 171 Spiellogik 174 Stack-Verlauf 260 Standardbibliothek 185, 203 Standardmodule 185 Standardwert dynamischer 71 für Funktionen 68 star args 64 Start-Index 30 Stats-Klasse 254 Stilregel 19 StopIteration-Exception 60, 172 str 21 strptime-Funktion 199 subprocess-Modul 148 super().__getattribute__-Methode 135 super-Funktion 97, 99 SyntaxError-Exception 232 sys-Modul 18, 243 Systemaufruf parallel 156 Systemaufrufe 155 T Tabulator 19 task_done-Methode 166 tearDown-Methode 249 teilerfremd 43 Ternäroperator 26 TestCase-Klasse 249 Textkodierung 21 Thread 91, 154 Arbeitsspeicherbedarf 169 Probleme 169 Zusammenspiel 159 threading-Modul 159 ThreadPoolExecutor-Klasse 181 time-Modul 199 timeout-Parameter 152 tracemalloc-Modul 259 Transaktion 132 Transitive Abhängigkeit 233 try/except/else/finally-Anweisung 45 try/finally-Konstrukt 188 try-Block 45 Tupel 40, 65, 81–82 Tupel-Zuweisung 29 type 136 TypeError-Exception 63 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181 Stichwortverzeichnis U Übergabewarteschlangen 168 Umgebung virtuelle 234 Umgebungsvariable 243 Umkehren Zeichenreihenfolge 31 Umrechnungsfaktor 69 unicode 21 unittest-Modul 248 UNIX-Epoche 198 UNIX-Pipe 150 Unterklasse mit Metaklasse überprüfen 136 UTF-8 21 V ValueError 46, 51 Variable Definition 54 Gültigkeitsbereich 51 ungenutzte 50, 82 Zuweisung 19, 54 Verbraucher 161 Vererbung 79 mehrfache 95, 100 Versionierung 196 Virtuelle Umgebung 233–234 W Warteschlange 164, 205 WeakKeyDictionary 129 weakref-Modul 129 Wettlaufsituation 157 while-Anweisung 43 Whitespace 19 Wiederverwendung von Code 100 with-Anweisung 188 with-Block 189 with-Target 190 Wortanfang 57 wraps-Funktion 187 Y yield from 174 yield-Ausdruck 57, 170 Z Zahlen herausfiltern 35 Zahlenliste sortieren 52 Zeichen Konvertierung 31 pro Zeile 36 störende 26 Zeichenreihenfolge umkehren 31 Zeilenlänge 19 Zeilenumbrüche zählen 91 Zeitstempel 70, 201 Zeitüberschreitung 152 Zeitzone 199 Zen of Python 20 ZeroDivisionError 51 ZeroDivision-Exception 73 zip_longest-Funktion 41 zip-Funktion 40 Zirkelbezug 101 Zirkuläre Abhängigkeit 227 Zusammenarbeit 213 Zuweisung verzögerte 130 zur Laufzeit 146 Zuweisung (Variable) 54 Zweier-Tupel 50 269 © des Titels »Effektiv Python programmieren« (ISBN 9783958451810) 2015 by mitp Verlags GmbH & Co. KG, Frechen. Nähere Informationen unter: http://www.mitp.de/181