Effektiv Python programmieren - mitp

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