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')
