Auf dieser Seite werden die neueren Python-Features behandelt, die selten richtig dokumentiert sind. Mit jeder Python-Version kommen neue Funktionen, Sprachkonstrukte und Ähnliches hinzu, die eine Aufgabenstellung vereinfachen. Damit man sich ein Bild dieser Veränderungen machen kann, gibt es diese Seite. Sie ist eher für fortgeschrittenere Python-Programmierer gedacht, da man schon Einiges an Python-Wissen benötigt.
Inhalt
Die Sprache
with Statement
Dieser Abschnitt ist noch nicht fertig, derweil siehe Tutorial/with.
List Comprehensions
List Comprehensions (LC) ermöglichen es, einfache Listenoperationen auszuführen. Sie sind seit Python 2.0 Teil der Sprache. Ein einfaches Beispiel dafür ist die Notwendigkeit alle int-Elemente in einer Liste zu verdoppeln.
1 >>> l = [1, 2, 3, 4]
2 >>>
3 >>> # ohne LCs
4 >>> l_nolc = []
5 >>> for x in l:
6 >>> l_nolc.append(x * 2)
7 >>> print l_nolc
8 [2, 4, 6, 8]
9 >>>
10 >>> # mit LCs
11 >>> l_lc = [x*2 for x in l]
12 >>> print l_lc
13 [2, 4, 6, 8]
Was demonstriert das? Dieses zeigt, dass man in LCs for-Schleifen einbaut. Man kann beliebige for-Schleifen nutzen, ob mit range()/xrange() oder ohne, das ist egal. Dann kann man mit der Variablen tun was man will; sie wird am Schluss automatisch in die neu entstandene Liste hinzugefügt. Es gibt auch die Möglichkeit if-Blöcke in List Comprehensions einzubauen, wie ein Beispiel zeigt:
1 >>> sprachen = ['Python', 'Ruby', 'Lisp', 'Haskell']
2 >>> # erst ohne LCs
3 >>> p_sprachen = []
4 >>> for sprache in sprachen:
5 >>> if 'p' in sprache.lower():
6 >>> p_sprachen.append(sprache)
7 >>> print p_sprachen
8 ['Python', 'Lisp']
9 >>>
10 >>> # jetzt mit LCs
11 >>> sprachen_mit_p = [sprache for sprache in sprachen if 'p' in sprache.lower()]
12 >>> print sprachen_mit_p
13 ['Python', 'Lisp']
In diesem Code haben wir erstmal eine Liste mit Programmiersprachen und wollen eine neue Liste erstellen, die nur die Sprachen enthält, die ein "P" im Namen haben (sicherlich gibt es noch bessere Problemstellungen, aber das ist auch schon mal gut). Also müssten wir normalerweise erst eine neue, leere Variable erstellen, dann eine for-Schleife machen, die durch die Liste der Sprachen iteriert, und dann noch eine if-Abfrage, die erstmal den Namen in Kleinbuchstaben konvertiert und schaut, ob dort ein "p" vorhanden ist und gegebenfalls diese Sprache in die neue Liste aufnimmt. Aber durch die List Comprehensions wird es einfacher. Man kann einfach auch Bedingungen abgeben, in denen das Element der Liste hinzugefügt wird, und die ganze LC ist nicht länger als eine Zeile.
Boolean-Datentyp
Ab Python 2.2.1 wurden zwei neue Konstanten, True und False eingeführt, die jeweils für 1 und 0 standen. In 2.3 bildeten diese beiden Konstanten einen neuen Datentyp, den bool. Die meisten Module im Lieferumfang wurden so umgeschrieben, dass sie True und False statt 1 und 0 ausgeben, damit der Code klarer ist. Dieses ist eigentlich eher eine kosmetische Änderung, da sich neue Dinge damit nicht machen lassen.
1 >>> True + True + True
2 3
Slice-Syntax
Seit Python 1.4 unterstützt die Slice Syntax auch ein drittes Argument, den Schritt. Aber bis Python 2.3 haben weder die Listen-, Tupel- noch Stringdatentypen das dritte Argument unterstützt. Dies wurde geändert.
1 >>> liste = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 >>> tupel = (0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9)
3 >>> string = '0123456789'
4 >>>
5 >>> liste[::2]
6 [0, 2, 4, 6, 8]
7 >>> tupel[::-1]
8 (9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
9 >>> string[2:6:2]
10 '24'
Future-Imports
In Python 2.1 wurde ein Mechanismus eingeführt, um ein bestimmtes neues Verhalten des Interpreters zu kontrollieren. Wenn also Features eingeführt werden, die nicht besonders vorwärtskompatibel sind, wird ein Major Python Release vorher ein Feature-Import zur Verfügung gestellt, der das neue Feature aktiviert, obwohl es schon implementiert, aber nicht standardmäßig aktiviert ist. Dies soll dazu dienen, den Programmierern Zeit zu geben, ihre Programme langsam an die neuen Möglichkeiten anzupassen.
1 >>> from __future__ import nested_scopes
2 >>> from __future__ import division
3 >>> from __future__ import generators
__future__ ist eigentlich kein normales Modul, sondern eher eine Art Einschalter. Einmal aktiviert, kann man während der Laufzeit von Python dieses neue Verhalten nicht mehr rückgängig machen, und zudem müssen die __future__-Imports ganz am Anfang des Moduls stehen. In folgenden Python-Versionen wird dieses Verhalten als Standard eingesetzt und der __future__-Import wird unnötig, trotzdem wird er keine Fehler ergeben, da Features nie aus dem __future__-Pseudomodul verschwinden. Somit kann man diese Imports in den eigenen Programmen auch stehen lassen.
Mehrzeilige Imports
In Python 2.4 wurde der simplere Teil des PEP 328 implementiert, der die Import-Syntax so erweitert hat, dass die importierten Namen in Klammern und auf mehreren Zeilen stehen dürfen.
1 >>> from sys import (argv, byteorder,
2 ... copyright)
Dieser kurze Quellcodeabschnitt importiert die Namen sys.argv, sys.byteorder und sys.copyright in den aktuellen Namensraum als argv, byteorder und entsprechend copyright.
Absolute und relative Imports
Python 2.5 implementiert auch den komplexeren Teil des PEP 328, welcher erstmals absolute und relative Importe zulässt. Somit werden Modifikationen am PYTHONPATH unnötig, um verstreute Module zu importieren.
Erweiterte Zuweisung
Vor Python 2.0 war es nicht möglich, eine Zuweisung wie zahl++ bekannt aus Sprachen wie C zu machen. Dieses hat sich in Python 2.0 etwas geändert, da eingesehen wurde, dass zahl = zahl + 1 etwas umständlich zu schreiben und dadurch fehlerträchtig war. Man konnte aber sowas wie den Pre- und Postincrement-Operator nicht einfach einführen, weil in Python Zahlen und Strings unveränderlich sind; diese lassen sich nur durch Neuzuweisung ändern. Genau das machen die neuen Zuweisungsoperatoren +=, -=, *=, /=, %=, **=, &=, |=, ^=, >>= und <<=. Diese Neuerung ist nicht sehr bewegend, aber trotzdem recht praktisch, da man nicht mehr so viel tippen muss und somit der Code allgemein eleganter und übersichtlicher wird. Noch dazu ist der Code ein klein wenig schneller, da der selbe Name nicht zwei mal durch den Interpreter ausgewertet werden muss.
1 >>> zahl = 3
2 >>> zahl = zahl + 1
3 4
4 >>> zahl += 1
5 5
Zu beachten bleibt, dass etwas wie zahl =+ 1 die Variable zahl auf 1 setzt. Deswegen darf man die neuen Operatoren nicht einfach so umdrehen; sie machen dann nämlich nicht das, was eigentlich erwartet wird. Genauso ist zu beachten dass zahl += 1 im Endeffekt nichts anderes als zahl = zahl + 1 ist, es kann also zu seltsam anmutenden Fehlern mit folgenden Code kommen:
1 >>> x = ([],)
2 >>> x[0] += ["hello"]
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in
5 TypeError: object does not support item assignment
6 >>> x
7 (['hello'],)
8 >>>
Der Fehler resultiert daraus, dass nach der eigentlichen Addierung über __iadd__ in Zeile 2 probiert wird, das neue Listenobjekt (welches bei __iadd__ noch das selbe ist) x[0] zuzuweisen, was natürlich nicht gehen kann da x eine Tupel ist. Da aber das Listenobjekt bereits vorher in-place geändert wurde (da es ein mutable-Typ), taucht die Änderung nachher trotzdem in x auf.
Verbessertes in
Da es lange Zeit per in möglich war zu prüfen, ob ein bestimmer Buchstabe in einem String vorkommt, wurde diese Möglichkeit in Python 2.3 so erweitert, dass man mit in herausbekommen kann, ob ganze Teilstrings vorhanden sind. in sagt aber nicht, wo sich dieses Vorkommnis befindet.
1 >>> 'bcd' in 'abcde'
2 True
Set-Datentyp
Seit Python 2.3 gibt es einen neuen Datentyp: das Set ("die Menge") aus dem Modul sets. Dieser wurde in Python 2.4 nochmal in C geschrieben, in die Builtins verschoben und heißt jetzt set. Das Set verhält sich wie ein Dictionary ohne Werte: Elemente können nicht doppelt vorkommen und sind ungeordnet. Ein in-Test ist schneller als bei Listen. Es gibt auch das frozenset, das ein immutables Set darstellt. Sets werden verwendet um in Python Mengen darzustellen, dazu bieten sie Funktionen die sagen, ob eine Menge die Ober- bzw Untermenge der anderen ist, Funktionen die Mengen vereinigen Schnittmengen bilden können, sowie Funktionen die die Menge kopieren oder Differenzen von Mengen bilden.
1 >>> # eine einfache Liste
2 >>> liste = ['Lisp', 'Python', 'Ruby', 'Haskell', 'Python']
3 >>> print liste
4 ['Lisp', 'Python', 'Ruby', 'Haskell', 'Python']
5 >>> # man sieht: die Liste ist sortiert
6 >>>
7 >>> # und nun machen wir daraus ein Set
8 >>> menge = set(liste)
9 >>> # also einfach die set()-Funktion aufgerufen, die aus Iteratoren Mengen machen kann
10 >>>
11 >>> print menge
12 set(['Lisp', 'Python', 'Haskell', 'Ruby'])
13 >>> # aha, das doppelte 'Python' ist weg!
14 >>> # die Reihenfolge ist auch vertauscht, Mengen sind wohl ungeordnet
15 >>>
16 >>> # dann fügen wir mal Elemente hinzu
17 >>> menge.add('Scheme')
18 >>> menge.add('Lisp')
19 >>> print menge
20 set(['Lisp', 'Python', 'Haskell', 'Ruby', 'Scheme'])
21 >>> # ahh, nun ist 'Scheme' drin ebenso wie 'Lisp'. Nur 'Lisp' war vorher auch schon drin
Bedingte Ausdrücke
In Python 2.5 wurde eine etwas neue Syntax eingeführt um sogenannte bedingte Ausdrücke zu ermöglichen. Dabei geht es um ein Konstrukt, welches hauptsächlich ermöglicht Code abzukürzen, dass statt einer längeren if-else-Abfrage nun eine einzeilige Anweisung reicht. Um das zu illustrieren ist hier ein adaptiertes Beispiel aus der Dokumentation:
1 >>> wenn_wahr = 'Die Bedingung ist wahr'
2 >>> wenn_unwahr = 'Die Bedingung ist unwahr'
3 >>>
4 >>> # unwahre Bedingung
5 >>> bedingung = False
6 >>>
7 >>> # vorher hat man es so getestet
8 >>> if bedingung:
9 >>> x = wenn_wahr
10 >>> else:
11 >>> x = wenn_unwahr
12 >>>
13 >>> print x
14 Die Bedingung ist unwahr
15 >>>
16 >>> # ab Python 2.5 mit Conditional Expressions
17 >>> x = wenn_wahr if bedingung else wenn_unwahr
18 >>> print x
19 Die Bedingung ist unwahr
20 >>>
21 >>> # und nun mit einer wahren Bedingung
22 >>> bedingung = True
23 >>> x = wenn_wahr if bedingung else wenn_unwahr
24 >>> print x
25 Die Bedingung ist wahr
Standardwerte für Dictionaries
Wenn man auf Schlüssel in Dictionaries zugreift die nicht existieren, bekommt man einen KeyError. Jedoch ist es durchaus praktisch manchmal unbekannte Schlüssel einen Wert zurückgeben zu lassen. Daher hat der dict-Datentyp die Funktion dict.get('Key', 'Default) wobei der erste Parameter der Schlüssel ist und der zweite ist der Wert der zurückgegeben wird, wenn der Schlüssel im dict nicht existiert. Sowas ist in der Regel für Zähler benötigt, wo man sich eine try/except KeyError-Abfrage sparen will:
1 >>> # Zeichenhäufigkeit herausbekommen
2 >>> wort = 'Mississippi'
3 >>>
4 >>> # der try/except-Weg
5 >>> freq = dict()
6 >>> for zeichen in wort:
7 ... try:
8 ... freq[zeichen] += 1
9 ... except KeyError:
10 ... freq[zeichen] = 1
11 ...
12 >>> print freq
13 {'i': 4, 'p': 2, 's': 4, 'M': 1}
14 >>>
15 >>> # eleganterer Weg mit `dict.get()`
16 >>> freq = dict()
17 >>> for zeichen in wort:
18 ... freq[zeichen] = freq.get(zeichen, 0) + 1
19 ...
20 >>> print freq
21 {'i': 4, 'p': 2, 's': 4, 'M': 1}
Nun bietet Python 2.5 eine Möglichkeit in einem Dict auf unbekannte Schlüssel zu reagieren:
1 >>> class zerodict (dict):
2 ... def __missing__ (self, key):
3 ... return 0
Also ist die magische Methode __missing__ dazugekommen, die eben den Schlüssel mit dem zurückgegebenen Wert ausgibt. Dazu kam im collections-Modul der defaultdict-Typ, der uns die Sache noch weiter vereinfacht:
1 >>> from collections import defaultdict
2 >>> freq = defaultdict(int)
3 >>> for zeichen in wort:
4 ... freq[zeichen] += 1
5 ...
6 >>> print freq
7 defaultdict(<type 'int'>, {'i': 4, 'p': 2, 's': 4, 'M': 1})
Der erste Parameter für defaultdict ist die Factory-Funktion: sie wird in __missing__ aufgerufen, um einen Standardwert zu ermitteln. In diesem Beispiel ist int() die Factory-Funktion und ein einfaches int() gibt null zurück, womit sich also der Zähler sehr einfach und elegant implementieren lässt.
Die Builtins
In jeder neuen Python-Version kommen neue Builtins mit oftmals sehr praktischen Funktionen hinzu. Diese sind aber auch nachprogrammierbar, so gibt es Backports von bool(), sum(), enumerate(), reversed(), sorted() und den Sets.
sorted
sorted() ist neu in Python 2.4 und gibt die sortierte Version einer Sequenz zurück, ohne die ursprüngliche Sequenz zu verändern.
enumerate
Manchmal ist es nötig, dass man in einer for-Schleife die Elemente einer Liste durchgeht, wobei man gleichzeitig ihren Platz in der Liste wissen will. Genau zu diesem Zweck wurde enumerate() in Python 2.3 eingeführt
1 >>> liste = ['Python', 'Ruby', 'Lisp', 'Haskell']
2 >>>
3 >>> # so ging es, und geht es immer noch
4 >>> for e in range(len(liste)):
5 ... print e, liste[e]
6 ...
7 0 Python
8 1 Ruby
9 2 Lisp
10 3 Haskell
11 >>>
12 >>> # und nun moderner
13 >>> for e, item in enumerate(liste):
14 ... print e, item
15 ...
16 0 Python
17 1 Ruby
18 2 Lisp
19 3 Haskell
sum
Wie der Name der in Python 2.3 hinzugekommen Funktion besagt, bildet sum() die Summe der Werte in einer Sequence. So eine Sequence ist jedes iterable Objekt, also Listen, Tupel, Generatoren etc.
1 >>> # die Zahlen die wir addieren wollen
2 >>> zahlen = [2, 3, 5, 7, 11, 13, 17, 19, 23]
3 >>>
4 >>> # vorher: phantasievolle Anwendung von Funktionen
5 >>> reduce(lambda a, b: a + b, zahlen)
6 100
7 >>> # nun: sum()
8 >>> sum(zahlen)
9 100
Es ist fast schon zu simpel für ein Beispiel, jedoch lohnt es sich auch auf den zweiten Parameter von sum() zu achten, den Startwert. Dieser kann einem in die Quere kommen, wenn man Objekte zusammenzählt, die sich nicht mit Null addieren lassen.
1 >>> # in diesem Beispiel arbeiten wir mal mit timedeltas
2 >>> from datetime import timedelta
3 >>> # einige deltas generieren
4 >>> deltas = [timedelta(i) for i in range(1, 9)]
5 >>> sum(deltas)
6 Traceback (most recent call last):
7 File "<stdin>", line 1, in <module>
8 TypeError: unsupported operand type(s) for +: 'int' and 'datetime.timedelta'
9 >>> # liegt daran, dass man den startwert 0 nicht mit timedeltas addieren kann
10 >>> # also müssen wir den Startwert anders belegen, in dem Fall mit einem timedelta dass Null entspricht
11 >>> sum(deltas, timedelta(0))
12 datetime.timedelta(36)
Die Module
In jeder Python Version kommen neue Module hinzu, die neue Möglichkeiten eröffnen oder weitere Vereinfachungen bieten. Einige wichtige sollten mal beschrieben werden.
Erstmal eine Liste von Modulen die beschreiben werden sollen, es jedoch noch keine Zeit gab über sie etwas zu schreiben:
- locale, gettext - ab Python 2.0: i18n und l10n
- imputil - ab Python 2.0: import hooks
- weakref - ab Python 2.1
- doctest, unittest - ab Python 2.1/2.2: Code testen
- bsddb aus PyBSDDB, alte bsddb ist bsddb185 - ab Python 2.3: Interface zu BSDDB
- sqlite3 - ab Python 2.5: PySQLite in Python aufgenommen
wsgiref - ab Python 2.5: simpler WSGI-Server
subprocess
Das neue subprocess Modul, das in Python 2.4 zum ersten mal auftaucht, soll die ganzen Möglichkeiten von Python aus neue Prozesse zu starten vereinfachen. Dadurch sollen os.system(), os.popen() und die Funktionen des popen2-Moduls vereinheitlicht werden, so dass man in Zukunft nur noch das subprocess-Modul nutzt. Dazu stellt dieses Modul eine Klasse zur Verfügung: Popen.
Praktischerweise kann man subprocess auch für Python 2.2 und 2.3 nachrüsten. Dazu kann man einfach die Datei subprocess.py von der aktuellen Python Version zu seinem Projekt kopieren und importieren, oder man nutzt den passenden Installer.
1 >>> import os, popen2, subprocess
2 >>>
3 >>> # Vorher: Programm einfach nur starten, ohne subprocess
4 >>> os.system('touch stamp')
5 >>> # Neu:
6 >>> subprocess.Popen(['touch', 'stamp'])
7 >>>
8 >>> # Befehl ausführen und Ausgabe ausgeben:
9 >>> process = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE)
10 >>> process.wait()
11 >>> print process.stdout.read()
12 >>>
13 >>> # stderr auch lesen:
14 >>> process = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
15 >>> # warten, bis der gestartete Prozess zu Ende ist
16 >>> process.wait()
17 >>>
18 >>> # die Standardausgabe des Prozesses ausgeben
19 >>> print process.stdout.read()
20 >>> # die Fehlerausgabe (sofern vorhanden) anzeigen
21 >>> print process.stderr.read()
22 >>> # den Rückgabewert des Prozesses ausgeben, meist 0 wenn das Programm problemlos durchgelaufen ist
23 >>> print process.returncode
24 >>>
Wichtig ist, nach dem Starten des subprocess mit .wait() zu warten, bis das gestartete Programm komplett ausgeführt wurde. Ansonsten kann es sein, dass man nur Teilergebnisse erhält. Natürlich ist das .wait() nur optional. Wenn man nicht will, dass das Python-Programm bis zum Beenden des aufgerufenen Programms blockt, sollte man das .wait() natürlich weglassen.
Wie man sieht, übergibt man dem Popen()-Konstruktor eine Liste mit Programmname und Argumenten. Leerzeichen und andere Shell-Metazeichen ($, > usw.) müssen nicht mit einem Backslash escaped werden, da die Kommandozeile nicht durch eine Shell interpretiert wird.
Will man das Verhalten von os.system, also Interpretierung durch eine Shell, was z.B. Ausgabeumleitung oder Pipelines möglich macht, übergibt man als erstes Argument einen String und das Keyword-Argument shell=True:
1 >>> subprocess.Popen("ls -l | head", shell=True, ...)
Eine Liste und shell=True zu übergeben ist unbrauchbar (die restlichen Argumente werden als Parameter von /bin/sh verwendet), ebenso einen String ohne shell=True zu übergeben (in diesem Fall wird nach dem ganzen String als Name des Programms gesucht).
Leider gibt es noch keinen eingebauten Timeout, allerdings existiert auch hierfür eine Lösung.
Mit dem Python Cookbook-Eintrag kann man plattformunabhängig mit der Shell kommunizieren. Für POSIX-Systeme (Linux) eignet sich Pexpect.
atexit
Das Modul atexit ermöglicht es, bestimmte Funktionen noch vor dem Beenden des Interpreters auszuführen ("at exit" = beim Verlassen). Dies war zwar schon vor Python 2.0 möglich, wo es noch kein Modul atexit gab, jedoch waren die Möglichkeiten weniger "sauber". So konnte man zwar eigene Funktionen an sys.exitfunc binden (was seit Python 2.4 deprecated ist) oder die Ausnahme SystemExit bzw. KeyboardInterrupt abfangen, jedoch hat man nun mit atexit ein Modul das für diesen Zweck geschaffen ist. Dadurch kann man auch mehrere Funktionen vor dem Beenden ausführen, sie werden nacheinander ausgeführt.
Genug der Erklärung, nun mal zu den Beispielen. Zunächst eines, dass atexit nicht nutzt:
1 >>> import time, sys
2 >>> def finish():
3 ... print 'Finishing stuff'
4 ...
5 >>> sys.exitfunc = finish
6 >>> time.sleep(5)
7 ^C
Man muss zugeben, dass dies schon sehr einfach aussieht. Nun das Beispiel mit atexit:
1 >>> import time, atexit
2 >>> def finish():
3 ... print 'Finishing stuff'
4 ...
5 >>> atexit.register(finish)
6 >>> time.sleep(5)
7 ^C
Durch atexit wird diese Lösung zwar nicht kürzer, jedoch kann sie mehr. Man kann jetzt nämlich problemlos auch meherere Funktionen beim Beenden automatisch aufrufen lassen, in dem man sie mit atexit.register() registriert. Dies wäre über sys.exitfunc nur machbar gewesen, wenn man eine zusätzliche Funktion erstellt hätte, die von sich aus mehrere Unterfunktionen aufruft. Zudem ist es über atexit auch möglich, der aufzurufenden Funktion Argumente mitzugeben.
bz2
Es ist ja öfters mal nötig Datenmengen zu komprimieren, oder mit komprimierten Datenmengen umzugehen. Dazu hat Python schon lange die entsprechenden Module, sowohl für ZIP-Archive zipfile als auch für gzip-Archive gzip (und das seperat verwendbare zlib-Modul, welches Strings komprimieren kann). Aber auch Kompressionsverfahren entwickeln sich weiter und ein neueres, freies Verfahren gewinnt an bedeutung: bzip2. Ab Python 2.3 können sich auch Python-Programmierer über das Modul bz2 freuen.
1 >>> import bz2
2 >>>
3 >>> # der unkomprimierte String
4 >>> uncomp = 'abcdef' * 10
5 >>> len(uncomp)
6 60
7 >>> # komprimieren mit bz2
8 >>> comp = bz2.compress(uncomp)
9 >>>
10 >>> # kleiner geworden?
11 >>> len(comp)
12 46
13 >>>
14 >>> # wunderbar, jetzt stesten wir das entpacken
15 >>> decomp = bz2.decompress(comp)
16 >>> len(decomp)
17 60
18 >>>
19 >>> # es ist gleich lang, aber ist auch das gleiche drin?
20 >>> uncomp == decomp
21 True
Der Umgang mit bz2-Dateien wird am einfachsten durch die Klasse BZ2File, die ein file-Objekt emuliert. Für das Komprimieren von kleineren Daten ist jedoch zlib zu bevorzugen, da es in diesen Fällen oft besser komprimiert, bz2 ist eher für größere Datenmengen ausgelegt.
decimal
Normalerweise führt die Natur von Fliesskommazahlen im Computer zu Ungenauigkeiten bei der berechnung, zu Rundungsfehlern, wie David Goldberg in seinem Text What Every Computer Scientist Should Know about Floating-Point Arithmetic beschreibt. Jedoch hat Python seit Version 2.4 das Modul decimal im Lieferumfang, mit dem man auch mit float-Werten exakte Rechnungen durchführen kann.
1 >>> # erstmal ganz normal mit float
2 >>> f = 1.1
3 >>> f * 2
4 2.2000000000000002
5 >>> # sieht unganau aus
6 >>> print f * 2
7 2.2
8 >>> # sieht genau aus, ist es aber nicht, da der Interpreter die Ungenauigkeit einfach nur versteckt
9 >>>
10 >>> import decimal
11 >>> # der Zahlenwert wird als String übergeben - da floats ja eigentlich ungenau sind.
12 >>> d = decimal.Decimal('1.1')
13 >>> d * 2
14 Decimal("2.2")
15 >>> # das wäre nun exakt
16 >>>
Manchmal braucht man so eine Präzision, wenn man ernsthaft mit Geldbeträgen rechnet oder Simulationen schreibt. Vor allem da die Ungenauigkeiten dazu neigen sich bei allen Operationen vergrößern. Auch wenn man mit sehr kleinen Zahlen rechnet ist es praktisch, wenn man nicht möchte, dass die kleinen Werte unter den Tisch fallen, wie bei der Normalisierung bei Floats. Das Problem mit den Dezimalzahlen hat natürlich noch eine einfachere Lösung zu bieten, es hängt eben ab, welche Präzision benötigt wird.
Wenn man dieses Modul benötigt, gibt es allerdings die Möglichkeit, sich dieses Modul auch für Python 2.3 zu besorgen. Wie das zu bewerkstelligen ist, wird auf dieser Seite beschrieben. Das Modul kann man sich entweder seperat runterladen oder direkt aus dem Python CVS ziehen.
Weitere Informationen zu Thema float-Ungenauigkeit bietet Validlab.
warnings
Python hat einen Mechanismus der Warnungen bei bestimmten Vorgängen ausgeben kann. Dieser Mechanismus wird durch das Modul warnings, welches seit Version 2.1 dabei ist, kontrolliert. Damit ist es auch möglich eigene Warnungen auszulösen.
1 >>> import warnings
2 >>>
3 >>> # eine Warnung auslösen
4 >>> warnings.warn('Eine Warnung')
5 __main__:1: UserWarning: Eine Warnung
6 >>>
7 >>> # alle Warnungen vom Typ Userwarning ignorieren
8 >>> warnings.filterwarnings('ignore', '', UserWarning)
9 >>> warnings.warn('Noch eine Warnung')
10 >>>
functools
Die functools sind ein in Python 2.5 eingeführtes neues Modul, das einige Funktionen mitbringt, welche für funktionale Programmierung nützlich sein können.
timeit
Oft will man wissen, wie kleine Codeschnippsel sich von der Geschwindigkeit her verhalten. Im Regelfall hat man sich dann immer einen kleinen Benchmark geschrieben, der den Code getestet hat. Jedoch bietet Python ≥ 2.3 inzwischen das timeit-Modul, welches genau für diesen Zweck entworfen wurde.
Was ist schneller für kleine Listen? Das Builtin zip() oder itertools.izip()?
1 >>> import timeit
2 >>> # anzahl der wiederholungen
3 >>> loop = 1000
4 >>> # zu testender code
5 >>> statement_zip = "zip([1, 2, 3], [4, 5, 6])"
6 >>> statement_izip = "izip([1, 2, 3], [4, 5, 6])"
7 >>> # timer initialisieren
8 >>> timer_zip = timeit.Timer(statement_zip)
9 >>> timer_izip = timeit.Timer(statement_izip, "from itertools import izip")
10 >>>
11 >>> # benchmark
12 >>> print "%.2f usec/pass" % (loop * timer_zip.timeit(number=loop) / loop)
13 0.11 usec/pass
14 >>> print "%.2f usec/pass" % (loop * timer_izip.timeit(number=loop) / loop)
15 0.10 usec/pass
16 >>> # ok, alles klar, so ziemlich gleich
Der erste an timeit.Timer() zu übergebende Parameter ist der Code dessen Geschwindigkeit geprüft werden soll. Der zweite Timer im Beispiel hat noch einen zweiten parameter und zwar den Setup-Code. Dieser Code wird einmal ausgeführt, damit der Name izip überhaupt bekannt wird. Der zu prüfende Code wird number mal wiederholt, um möglichst viele Proben zu erhalten, deren Mittelwert am Ende in Microsekunden ausgegeben wird. Generell ist es so, es besser ist, je mehr Durchläufe man laufen lässt um einen möglichst ausgeglichenen Mittelwert zu haben, aber je mehr Wiederholungen man einbaut, desto länger dauert natürlich auch der gesammte Benchmark. Man muss selbst einen guten Wert für die Anzahl Weiderholungen finden.
timeit wählt automatisch eine Zeitmessungsmethode aus, die für die jeweilige Platform am geeignetsten ist. So wird unter Windows time.clock() verwendet, unter anderen Systemen time.time(). Für den Benutzer des Moduls timeit ist es irrelevant, da das Modul sich darum kümmert und somit gleich eine Fehlerquelle ausschließt.
itertools
Die in Python eingeführten Generatoren haben einen niedrigeren Speicherverbrauch als normale Funktionen die Iterables ausgeben, weil die Objekte erst dann erstellt werden wenn sie benötigt werden. Jedoch geben noch viele Builtins normale Listen aus was sich aus Kompatibilitätsgründen in Python 2.x nicht ändern wird. Jedoch findet man im Modul itertools die Generator-Implementationen von einigen Builtins. Das Modul selbst gibt es seit Python 2.3, in Python 2.4 sind jedoch noch einige Funktionen hinzugekommen.
1 >>> from itertools import izip, islice
2 >>> # früher
3 >>> zip([1, 2, 3], ['a', 'b', 'c'])
4 [(1, 'a'), (2, 'b'), (3, 'c')]
5 >>> # generator
6 >>> generator = izip([1, 2, 3], ['a', 'b', 'c'])
7 >>> generator
8 <itertools.izip object at 0x5089deb8>
9 >>> # also dann mal nachschauen was in dem Generator steckt
10 >>> list(generator)
11 [(1, 'a'), (2, 'b'), (3, 'c')]
12 >>>
13 >>> # ähnliches gibt es auch mit slices
14 >>> generator = islice('abcdefghi', 1, 5, 1)
15 >>> generator
16 <itertools.islice object at 0x5091b720>
17 >>> list(generator)
18 ['b', 'c', 'd', 'e']
19 >>>
Es gibt dazu auch noch die Funktionen ifilter(), welches wie das filter()-Builtin funktioniert, ifilterfalse(), welches das selbe tut, nur das Gegenteil filtert und imap() welches analog zum das klassischen map() ist.
Jedoch sind im itertools-Modul nicht nur abgewandelte Builtins drin, sondern auch ganz neue Funktionen. Einige davon sind sehr simpel und nützlich wie itertools.count(), andere wie itertools.groupby() sind komplizierter, jedoch lassen sich mit etwas Können schöne Strukturen herstellen. Dieses Modul ist perfekt für Leute, die ML oder Haskell mögen, da ihnen vieles bekannt vorkommen dürfte.
1 >>> import itertools
2 >>> counter = itertools.count()
3 >>> counter.next()
4 0
5 >>> counter.next()
6 1
7 >>> counter.next()
8 2
9 >>> counter.next()
10 3
11 >>>
Zuletzt bietet itertools auch Möglichkeiten Iteratoren (also eben unter anderem auch Generatoren) zu bearbeiten. itertools.chain() kann Itaratoren verketten, itertools.cycle() lässt den Iterator unendlich lange laufen, indem er in eine Dauerschleife gebracht wird, itertools.repeat() wiederholt bestimmte Elemente des Iterators, itertools.tee kopiert Iteratoren. Die Funktionen von itertools sind gut dokumentiert, ein blick in dessen Dokumentation kann durchaus praktische und interessante Dinge zu Tage fördern.
logging
Es kommt manchmal vor, dass man eine Log-Funktion braucht um Logdateien zu schreiben. Besonders für Serverprogramme ist so etwas oft praktisch. Bis Python 2.3 musste man sich entweder ein eigenes System schrieben oder etwas fertigs nehmen. In Python 2.3 ist im Zuge des PEP 282 ein logging-Modul implementiert worden. Es ist stark an den Java-äquivalenten java.util.logging und log4j angelehnt. Der Vorteil ist, dass man das Modul auch für Python-Versionen bis hin zu 1.5.x nachrüsten kann. Somit spricht der Verwendung des Moduls nichts entgegen.
Simple Dinge sind auch mit dem logging-Modul simpel:
1 >>> import logging
2 >>> logging.debug('Eine Debug-Nachricht')
3 >>> logging.info('Kurze Information')
4 >>> logging.warning('Du seist gewarnt!')
5 WARNING:root:Du seist gewarnt
Man kann erkennen, dass es sogenannte Log-Level gibt, die die Priorität einer ausgabe angeben. Standardmäßig gibt es die Level DEBUG, INFO, WARNING, ERROR und CRITICAL, der Standardlevel ist WARNING. Das heißt, dass Meldungen mit einer geringeren Priorität nicht angezeigt werden. Die Ausgabe wie die Logmeldungen aussehen ist natürlich ebenso konfigurierbar.
Zum kofigurieren wird die Funktion logging.baseConfig verwendet:
1 >>> import logging
2 >>> logging.basicConfig(level=logging.DEBUG,
3 format='%(asctime)s %(levelname)-8s %(message)s',
4 datefmt='%a, %d %b %Y %H:%M:%S')
5 >>> logging.info('Eine Information')
6 Sat, 05 May 2007 14:47:56 INFO Eine Information
7 >>>
Mithilfe der Handler ist es möglich nicht nur in nach sys.stderr zu loggen sondern auch in Dateien, Mails, HTTP-Server oder den Syslog sowie auch noch andere. Auch das nutzen mehrerer Handler mit verschiedenen Prioritätsstufen ist vorgesehen. Das Modul beitet auch noch viele zusätzliche Funktionen, daher lohnt es sich die Dokumentation und die Beispiele genauer unter die Lupe zu nehmen.
textwrap
Manchmal möchte man normalen Text nach einer bestimmten Anzahl von Zeichen umbrechen, damit er leichter lesbar wird. Für solche Zwecke liefert Python ab Version 2.3 das textwrap-Modul mit.
1 >>> import textwrap
2 >>> # das ist ein langer Beispieltext ohne Umbrüche
3 >>> text = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Mauris mollis nibh sit amet purus. Proin nulla.
4 Nulla fermentum leo ut leo. Nunc luctus. Cras orci. Sed egestas tortor congue odio. In luctus, sem ut sagittis viverra,
5 ligula eros luctus neque, in tincidunt dolor odio id nibh. Mauris felis eros, tristique vel, faucibus vel, rutrum ut,
6 mauris. Pellentesque gravida. Sed erat velit, accumsan pellentesque, sollicitudin a, fringilla at, nisi.""".replace('\n', '')
7 >>>
8 >>> print textwrap.fill(text, 30)
9 Lorem ipsum dolor sit amet,
10 consectetuer adipiscing elit.
11 Mauris mollis nibh sit amet
12 purus. Proin nulla. Nulla
13 fermentum leo ut leo. Nunc
14 luctus. Cras orci. Sed egestas
15 tortor congue odio. In luctus,
16 sem ut sagittis viverra,
17 ligula eros luctus neque, in
18 tincidunt dolor odio id nibh.
19 Mauris felis eros, tristique
20 vel, faucibus vel, rutrum
21 ut,mauris. Pellentesque
22 gravida. Sed erat velit,
23 accumsan pellentesque,
24 sollicitudin a, fringilla at,
25 nisi.
26 >>>
Hier sieht man wie der Text umgebrochen wird. Wenn man den Umgebrochenen Text lieber in Listenform haben will, muss man statt textwrap.fill() einfach nur textwrap.wrap() verwenden.
Doch das ist nicht alles was textwrap bietet. Mit textwrap.dedent() kann man überflüssige Leerzeichen an mehrzeiligen Strings loswerden, was bei Docstrings nützlich sein kann.
1 >>> def beispielfunktion()
2 ... """
3 ... Das ist ein mehrzeiliger Docstring,
4 ... in dem ganz viele wichtige Informationen stehen
5 ... """
6 ...
7 >>>
8 >>> print beispielfunktion.__doc__
9 Das ist ein
10 Docstring
11 >>> # überflüssige Einrückung entfernen
12 >>> print textwrap.dedent(beispielfunktion.__doc__)
13 Das ist ein
14 Docstring
15 >>>
textwrap ist ein kleines, überschaubares Modul. Die dort enthaltenen Funktionen sind zwar nicht sehr raffiniert aber können einem durchaus Arbeit abnehmen.
ctypes
Das bekannte ctypes-Paket wurde in Python 2.5 in die Standardbibliothek aufgenommen. Damit kann man dynamische C-Libraries (Shared Objects .so unter Unices, Dynamic Link Libraries .dll unter Windows) direkt aus Python-Code aufrufen. Damit ist es möglich, Code der in C-Bibliotheken direkt aus Python heraus aufzurufen. Somit kann man nun in purem python Wrapper für C-Libraries machen. Das Modul ctypes wird für Pygame-ctypes, ein SDL-Binding benutzt, ebenso wie für http://pyopengl.sourceforge.net/ctypes/, ein OpenGL-Binding.
Hier ein Beispiel für Unix-Systeme. Für Windows ist das Vorgehen sehr ähnlich.
1 >>> import ctypes
2 >>> # glibc laden
3 >>> libc = ctypes.LoadLibrary('libc.so.6')
4 >>> libc.printf('Hallo C\n')
5 Hallo C
6 2
7 >>>
Die ctypes-Dokumentation ist umfassend jedoch sind C-Kenntnisse beim Erstellen von Wrappern immer nützlich. Bei Problemen steht die ctypes-Mailingliste aber jederzeit bereit um zu Lösungen zu verhelfen.
hashlib
Oft kommt es vor, dass man Hash-Funktionen braucht, Beispielsweise bei der Verwaltung von gespeicherten Passwörten oder um die Integrität von Dateien zu überprüfen. Die bekanntesten Hash-Funktionen sind MD5 und SHA1 für die Python seit langer Zeit die Module md5 und sha bereitgestellt hat. Ab Version 2.5 wurden beide Module missbilligt und ein neues Modul, hashlib wurde hinzugefügt um die Hash-Funktionalitäten in einem Modul zu vereinigen.
1 >>> # der alte Weg
2 >>> import md5, sha
3 >>>; Hash-Object erzeugen
4 >>> hash = md5.new('Readability counts.')
5 >>> # die new()-Funktion ist in sehr alten Versionen auch nicht vorhanden:
6 >>> hash = md5.md5('Readability counts.')
7 >>>
8 >>> # hashwert anzeigen
9 >>> hash.hexdigest()
10 '9aa5696200b4d79275a44c9a1b26699d'
11 >>>
12 >>> # das gleiche gilt für das Modul sha
13 >>> # nun der neue Weg
14 >>> import hashlib
15 >>> hash = hashlib.md5('Readability counts.').hexdigest()
16 '9aa5696200b4d79275a44c9a1b26699d'
17 >>> # die MD5-Hashes stimmen überein
18 >>>
Neben hashlib.md5() und hashlib.sha1() bietet hashlib ebenso die die Algorithmen hashlib.sha224(), hashlib.sha256(), hashlib.sha384() und hashlib.sha512().
collections
collections ist ein Modul, das weitere Container-Datentypen bereitstellt, die nützlich sein können. Es ist mit Python 2.4 erschienen und unterstützt den deque-Typen der sich Listenähnlich verhält, aber speziell auf append() und pop() optimiert ist - dessen Haupteinsatzzweck sind Stacks und Queues also da, wo es besonders auf das Anhängen und Wegschneiden von Elementen ankommt. Seit Python 2.5 gibt es auch das defaultdict, welches ein spezieller dict-Datentyp ist und weiter oben erklärt wird.
xml.etree
xml.etree ist ein Modul, das in Python 2.5 in die Standard-Library aufgenommen wurde. Es ist eine weitere Library, die neben xml.sax und xml.minidom verwendet werden kann, um XML zu parsen und zu bearbeiten. xml.etree bietet zwei Klassen: xml.etree.ElementTree und xml.etree.cElementTree, die ursprünglich von Fredrik Lundh als ElementTree-Modul geschrieben worden waren, wobei cElementTree logischerweise die in C implementierte Version ist und dementsprechend schneller. Eine häufige und oft in Code verwendete Abkürzung ist ET, welche sich auf beide Varianten beziehen kann.
Damit hat nun Python die dritte XML-API in der Standard-Library. Der Vorteil gegenüber SAX ist, dass mit ElementTree XML verarbeitet werden kann, wohingegen SAX nur zum einlesen verwendet wird. Der Vorteil gegenüber DOM ist, dass ElementTree eine Python-API ist und Minidom die eine Java-API implementiert, die komplizierter zu verwenden ist. Zusätzlich ist ET auch schneller als Minidom. Das bedeutet, dass ab Python 2.5 eigentlich kein Grund mehr besteht, noch Minidom in neuen Programmen zu verwenden - in diesem Fall ist es oftmals klüger, direkt auf ElementTree zu setzen.
1 >>> # früher, mit minidom
2 >>> # TODO: hier beizeiten einfügen
3 >>> # nun, mit ElementTree
4 >>> from xml.etree import ElementTree as ET
5 >>> tree = ET.fromstring("""<root>
6 <element attribut="irgendwas">Element hier</element>
7 <sometag>Some Tag</sometag>
8 <text>This is XML!</text>
9 </root>""")
10 >>> sometag = tree.find("sometext")
11 >>> print sometag.text
12 'Some Tag'
13 >>>
Es gibt noch eine weitere brauchbare XML-Library für Python namens lxml, die nicht Teil der Standard-Library ist. Sie ist dennoch erwähnenswert, da sie auf die C-Libraries libxml2 und libxslt1 zugreif und daher sehr schnell ist und viele Features bietet - zudem ist ihre API zu ElementTree kompatibel, somit kann man es auch als Drop-In-Ersatz für ElementTree nutzen.