11. Ein kurzer Überblick über die Standardbibliothek – Teil II¶
Dieser zweite Durchgang behandelt fortgeschrittenere Module, die den Anforderungen professioneller Programmierer gerecht werden. Diese Module kommen in kleinen Skripten nur selten vor.
11.1. Ausgabeformatierung¶
Das Modul reprlib stellt eine für die verkürzte Darstellung großer oder tief verschachtelter Container angepasste Version von repr() bereit:
>>> import reprlib
>>> reprlib.repr(set('supercalifragilisticexpialidocious'))
"{'a', 'c', 'd', 'e', 'f', 'g', ...}"
Das Modul pprint bietet eine ausgefeiltere Steuerung beim Ausgeben sowohl integrierter als auch benutzerdefinierter Objekte in einer für den Interpreter lesbaren Form. Wenn das Ergebnis länger als eine Zeile ist, fügt der „Pretty Printer“ Zeilenumbrüche und Einrückungen hinzu, um die Datenstruktur übersichtlicher darzustellen:
>>> import pprint
>>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
... 'yellow'], 'blue']]]
...
>>> pprint.pprint(t, width=30)
[[[['black', 'cyan'],
'white',
['green', 'red']],
[['magenta', 'yellow'],
'blue']]]
Das Modul textwrap passt Textabsätze an eine bestimmte Bildschirmbreite an:
>>> import textwrap
>>> doc = """The wrap() method is just like fill() except that it returns
... a list of strings instead of one big string with newlines to separate
... the wrapped lines."""
...
>>> print(textwrap.fill(doc, width=40))
The wrap() method is just like fill()
except that it returns a list of strings
instead of one big string with newlines
to separate the wrapped lines.
Das Modul locale greift auf eine Datenbank mit kulturspezifischen Datenformaten zu. Das Gruppierungsattribut der Formatierungsfunktion von „locale“ bietet eine direkte Möglichkeit, Zahlen mit Gruppentrennzeichen zu formatieren:
>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
'English_United States.1252'
>>> conv = locale.localeconv() # get a mapping of conventions
>>> x = 1234567.8
>>> locale.format_string("%d", x, grouping=True)
'1,234,567'
>>> locale.format_string("%s%.*f", (conv['currency_symbol'],
... conv['frac_digits'], x), grouping=True)
'$1,234,567.80'
11.2. Vorlagen¶
Das Modul string enthält eine vielseitige Klasse Template mit einer vereinfachten Syntax, die sich für die Bearbeitung durch Endbenutzer eignet. Dadurch können Benutzer ihre Anwendungen anpassen, ohne den Anwendungscode ändern zu müssen.
Das Format verwendet Platzhalternamen, die aus $ in Kombination mit gültigen Python-Bezeichnern (alphanumerische Zeichen und Unterstriche) gebildet werden. Durch die Einfassung des Platzhalters in geschweifte Klammern können weitere alphanumerische Zeichen ohne dazwischenliegende Leerzeichen folgen. Durch die Schreibweise $$ wird ein einzelner Escape-Platzhalter $ erzeugt
>>> from string import Template
>>> t = Template('${village}folk send $$10 to $cause.')
>>> t.substitute(village='Nottingham', cause='the ditch fund')
'Nottinghamfolk send $10 to the ditch fund.'
Die Methode substitute() löst einen KeyError aus, wenn in einem Wörterbuch oder einem Schlüsselwortargument kein Platzhalter angegeben wurde. Bei Anwendungen im Stil einer Seriendruckfunktion können die vom Benutzer bereitgestellten Daten unvollständig sein, sodass die Methode safe_substitute() möglicherweise besser geeignet ist – sie lässt Platzhalter unverändert, wenn Daten fehlen:
>>> t = Template('Return the $item to $owner.')
>>> d = dict(item='unladen swallow')
>>> t.substitute(d)
Traceback (most recent call last):
...
KeyError: 'owner'
>>> t.safe_substitute(d)
'Return the unladen swallow to $owner.'
Unterklassen von Vorlagen können ein benutzerdefiniertes Trennzeichen festlegen. Beispielsweise könnte ein Dienstprogramm zur Stapelumbenennung für einen Foto-Browser Prozentzeichen für Platzhalter wie das aktuelle Datum, die Bildnummer oder das Dateiformat verwenden:
>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
... delimiter = '%'
...
>>> fmt = input('Enter rename style (%d-date %n-seqnum %f-format): ')
Enter rename style (%d-date %n-seqnum %f-format): Ashley_%n%f
>>> t = BatchRename(fmt)
>>> date = time.strftime('%d%b%y')
>>> for i, filename in enumerate(photofiles):
... base, ext = os.path.splitext(filename)
... newname = t.substitute(d=date, n=i, f=ext)
... print('{0} --> {1}'.format(filename, newname))
img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg
Eine weitere Anwendungsmöglichkeit für Templates ist die Trennung der Programmlogik von den Details verschiedener Ausgabeformate. Dadurch lassen sich XML-Dateien, Berichte im Klartext und HTML-Webberichte durch benutzerdefinierte Templates ersetzen.
11.3. Arbeiten mit Layouts für binäre Datensätze¶
Das Modul struct stellt die Funktionen pack() und unpack() für die Arbeit mit binären Datensatzformaten variabler Länge bereit. Das folgende Beispiel zeigt, wie man die Header-Informationen einer ZIP-Datei durchlaufen kann, ohne das Modul zipfile zu verwenden. Die Pack-Codes "H" und "I" stehen für vorzeichenlose Zahlen mit zwei bzw. vier Bytes. Der "<" gibt an, dass es sich um Standardgrößen in Little-Endian-Byte-Reihenfolge handelt:
import struct
with open('myfile.zip', 'rb') as f:
data = f.read()
start = 0
for i in range(3): # show the first 3 file headers
start += 14
fields = struct.unpack('<IIIHH', data[start:start+16])
crc32, comp_size, uncomp_size, filenamesize, extra_size = fields
start += 16
filename = data[start:start+filenamesize]
start += filenamesize
extra = data[start:start+extra_size]
print(filename, hex(crc32), comp_size, uncomp_size)
start += extra_size + comp_size # skip to the next header
11.4. Multithreading¶
Threading ist eine Technik zur Entkopplung von Aufgaben, die nicht sequenziell voneinander abhängig sind. Threads können eingesetzt werden, um die Reaktionsfähigkeit von Anwendungen zu verbessern, die Benutzereingaben entgegennehmen, während andere Aufgaben im Hintergrund ausgeführt werden. Ein damit verbundener Anwendungsfall ist die parallele Ausführung von E/A-Vorgängen und Berechnungen in einem anderen Thread.
Der folgende Code zeigt, wie das High-Level- threading -Modul Aufgaben im Hintergrund ausführen kann, während das Hauptprogramm weiterläuft:
import threading, zipfile
class AsyncZip(threading.Thread):
def __init__(self, infile, outfile):
super().__init__()
self.infile = infile
self.outfile = outfile
def run(self):
with zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED) as f:
f.write(self.infile)
print('Finished background zip of:', self.infile)
background = AsyncZip('mydata.txt', 'myarchive.zip')
background.start()
print('The main program continues to run in foreground.')
background.join() # Wait for the background task to finish
print('Main program waited until background was done.')
Die größte Herausforderung bei Multithread-Anwendungen besteht darin, Threads zu koordinieren, die Daten oder andere Ressourcen gemeinsam nutzen. Zu diesem Zweck stellt das Threading-Modul eine Reihe von Synchronisationsprimitiven bereit, darunter Sperren, Ereignisse, Bedingungsvariablen und Semaphoren.
Obwohl diese Werkzeuge leistungsstark sind, können bereits kleine Designfehler zu Problemen führen, die sich nur schwer reproduzieren lassen. Daher besteht der bevorzugte Ansatz zur Aufgabenkoordination darin, den gesamten Zugriff auf eine Ressource in einem einzigen Thread zu bündeln und diesen Thread dann mithilfe des Moduls queue mit Anfragen aus anderen Threads zu versorgen. Anwendungen, die Queue “-Objekte für die Kommunikation und Koordination zwischen Threads nutzen, sind einfacher zu entwerfen, besser lesbar und zuverlässiger.
11.5. Protokollierung¶
Das Modul logging bietet ein funktionsreiches und flexibles Protokollierungssystem. Im einfachsten Fall werden Protokollmeldungen an eine Datei oder an sys.stderr gesendet
import logging
logging.debug('Informationen zur Fehlersuche')
logging.info('Informative Meldung')
logging.warning('Warnung: Konfigurationsdatei %s nicht gefunden', 'server.conf')
logging.error('Fehler aufgetreten')
logging.critical('Kritischer Fehler -- Programm wird beendet')
Dies führt zu folgender Ausgabe:
WARNUNG:root:Warnung: Konfigurationsdatei „server.conf“ nicht gefunden
FEHLER:root:Es ist ein Fehler aufgetreten
KRITISCH:root:Kritischer Fehler – System wird heruntergefahren
Standardmäßig werden Informations- und Debugging-Meldungen unterdrückt und die Ausgabe an die Standardfehlerausgabe weitergeleitet. Zu den weiteren Ausgabeoptionen gehört die Weiterleitung von Meldungen per E-Mail, über Datagramme, Sockets oder an einen HTTP-Server. Mit neuen Filtern lassen sich je nach Priorität der Meldungen unterschiedliche Weiterleitungswege auswählen: DEBUG, INFO, WARNING, ERROR und CRITICAL.
Das Protokollierungssystem kann direkt über Python konfiguriert oder aus einer vom Benutzer bearbeitbaren Konfigurationsdatei geladen werden, um eine individuelle Protokollierung zu ermöglichen, ohne die Anwendung zu verändern.
11.6. Schwache Referenzen¶
Python verfügt über eine automatische Speicherverwaltung (Referenzzählung für die meisten Objekte und Garbage Collection zur Beseitigung von Zyklen). Der Speicher wird kurz nach dem Wegfall der letzten Referenz darauf freigegeben.
Dieser Ansatz funktioniert bei den meisten Anwendungen gut, doch gelegentlich besteht die Notwendigkeit, Objekte nur so lange zu verfolgen, wie sie von etwas anderem verwendet werden. Leider wird allein durch das Verfolgen einer Referenz erstellt, die das Objekt dauerhaft macht. Das Modul weakref bietet Werkzeuge, um Objekte zu verfolgen, ohne eine Referenz zu erstellen. Wenn das Objekt nicht mehr benötigt wird, wird es automatisch aus einer „weakref“-Tabelle entfernt und ein Callback für „weakref“-Objekte ausgelöst. Zu den typischen Anwendungsfällen gehört das Zwischenspeichern von Objekten, deren Erstellung ressourcenintensiv ist:
>>> import weakref, gc
>>> class A:
... def __init__(self, value):
... self.value = value
... def __repr__(self):
... return str(self.value)
...
>>> a = A(10) # create a reference
>>> d = weakref.WeakValueDictionary()
>>> d['primary'] = a # does not create a reference
>>> d['primary'] # fetch the object if it is still alive
10
>>> del a # remove the one reference
>>> gc.collect() # run garbage collection right away
0
>>> d['primary'] # entry was automatically removed
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
d['primary'] # entry was automatically removed
File "C:/python313/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'
11.7. Werkzeuge zur Arbeit mit Listen¶
Viele Anforderungen an Datenstrukturen lassen sich mit dem integrierten Listentyp erfüllen. Manchmal besteht jedoch Bedarf an alternativen Implementierungen mit unterschiedlichen Kompromissen hinsichtlich der Leistung.
Das Modul array stellt ein array-Objekt bereit, das einer Liste ähnelt, jedoch ausschließlich homogene Daten enthält und diese kompakter speichert. Das folgende Beispiel zeigt ein Array von Zahlen, die als vorzeichenlose Binärzahlen mit zwei Bytes (Typcode "H") gespeichert sind, anstatt der üblichen 16 Bytes pro Eintrag bei regulären Listen von Python-int-Objekten:
>>> from array import array
>>> a = array('H', [4000, 10, 700, 22222])
>>> sum(a)
26932
>>> a[1:3]
array('H', [10, 700])
Das Modul collections stellt ein deque “-Objekt bereit, das einer Liste ähnelt: Das Hinzufügen und Entfernen von Elementen auf der linken Seite erfolgt schneller, das Suchen in der Mitte hingegen langsamer. Diese Objekte eignen sich gut für die Implementierung von Warteschlangen und für die Breitensuche in Bäumen:
>>> from collections import deque
>>> d = deque(["task1", "task2", "task3"])
>>> d.append("task4")
>>> print("Handling", d.popleft())
Handling task1
unsearched = deque([starting_node])
def breadth_first_search(unsearched):
node = unsearched.popleft()
for m in gen_moves(node):
if is_goal(m):
return m
unsearched.append(m)
Neben alternativen Listenimplementierungen bietet die Bibliothek auch weitere Werkzeuge wie das Modul bisect mit Funktionen zur Bearbeitung sortierter Listen:
>>> import bisect
>>> scores = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
>>> bisect.insort(scores, (300, 'ruby'))
>>> scores
[(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]
Das Modul heapq bietet Funktionen zur Implementierung von Heaps auf der Grundlage regulärer Listen. Der Eintrag mit dem niedrigsten Wert wird stets an Position Null gespeichert. Dies ist nützlich für Anwendungen, die wiederholt auf das kleinste Element zugreifen, aber keine vollständige Sortierung der Liste durchführen möchten:
>>> from heapq import heapify, heappop, heappush
>>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
>>> heapify(data) # rearrange the list into heap order
>>> heappush(data, -5) # add a new entry
>>> [heappop(data) for i in range(3)] # fetch the three smallest entries
[-5, 0, 1]
11.8. Dezimale Gleitkommaarithmetik¶
Das Modul decimal bietet den Datentyp Decimal für die dezimale Gleitkommaarithmetik. Im Vergleich zur integrierten float “-Implementierung der binären Gleitkommaarithmetik ist diese Klasse besonders hilfreich für
Finanzanwendungen und andere Anwendungsbereiche, die eine exakte Dezimaldarstellung erfordern,
Kontrolle über die Präzision,
Kontrolle über die Rundung zur Erfüllung gesetzlicher oder behördlicher Anforderungen,
Verfolgung der signifikanten Dezimalstellen oder
Anwendungen, bei denen der Benutzer erwartet, dass die Ergebnisse mit den von Hand durchgeführten Berechnungen übereinstimmen.
Beispielsweise führt die Berechnung einer Steuer von 5 % auf eine Telefongebühr von 70 Cent bei der dezimalen Gleitkommadarstellung und der binären Gleitkommadarstellung zu unterschiedlichen Ergebnissen. Der Unterschied wird erheblich, wenn die Ergebnisse auf den nächsten Cent gerundet werden:
>>> from decimal import *
>>> round(Decimal('0.70') * Decimal('1.05'), 2)
Decimal('0.74')
>>> round(.70 * 1.05, 2)
0.73
Das Ergebnis von Decimal behält eine nachgestellte Null bei und leitet daraus automatisch eine Signifikanz von vier Stellen aus Multiplikanden mit einer Signifikanz von zwei Stellen ab. „Decimal“ bildet mathematische Berechnungen so ab, wie sie von Hand durchgeführt werden, und vermeidet Probleme, die auftreten können, wenn binäre Gleitkommazahlen dezimale Größen nicht exakt darstellen können.
Dank der exakten Darstellung kann die Klasse Decimal Modulo-Berechnungen und Gleichheitsprüfungen durchführen, die für binäre Gleitkommazahlen ungeeignet sind:
>>> Decimal('1.00') % Decimal('.10')
Decimal('0.00')
>>> 1.00 % 0.10
0.09999999999999995
>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
False
Das Modul decimal ermöglicht arithmetische Berechnungen mit beliebig hoher Genauigkeit:
>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')