NB_01_Datenstrukturen_Werkzeuge

(c) 2019/2020 Hochschule Augsburg - Fakultät für Informatik - Prof.Dr.Nik Klever

Formatierung

Textausgabe Formatierung

reprlib

Das reprlib-Modul stellt eine Version von repr() zur Verfügung, die für eine abgekürzte Anzeige mittels ... von großen oder tief verschachtelten Containern angepasst ist:

In [2]:
import reprlib
reprlib.repr(set('supercalifragilisticexpialidocious'))
Out[2]:
"{'a', 'c', 'd', 'e', 'f', 'g', ...}"

pprint

The pprint module offers more sophisticated control over printing both built-in and user defined objects in a way that is readable by the interpreter. When the result is longer than one line, the “pretty printer” adds line breaks and indentation to more clearly reveal data structure:

Das pprint-Modul bietet eine ausgefeiltere Kontrolle über das Drucken von eingebauten und benutzerdefinierten Objekten, die vom Interpreter lesbar ist. Wenn das Ergebnis länger als eine Zeile ist, fügt der "hübsche Drucker" Zeilenumbrüche und Einrückungen hinzu, um die Datenstruktur deutlicher zu zeigen:

In [3]:
import pprint
t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta','yellow'], 'blue']]]
pprint.pprint(t, width=30)
[[[['black', 'cyan'],
   'white',
   ['green', 'red']],
  [['magenta', 'yellow'],
   'blue']]]

textwrap

Das textwrap-Modul formatiert Textabschnitte, um diese an eine bestimmte Bildschirmbreite anzupassen:

In [4]:
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.

locale

Das Modul locale greift auf eine Datenbank mit länderspezifischen Datenformaten zu. Das Gruppierungsattribut der Formatfunktion des Moduls locale bietet z.B. eine direkte Möglichkeit, Zahlen gruppiert mit entsprechenden länderspezifischen Separatoren zu formatieren:

In [5]:
import locale
locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')

conv = locale.localeconv()          # get a mapping of conventions
x = 1234567.8
locale.format("%d", x, grouping=True)
Out[5]:
'1.234.567'
In [6]:
locale.format_string("%s%.*f", (conv['currency_symbol'],conv['frac_digits'], x), grouping=True)
Out[6]:
'€1.234.567,80'
In [7]:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')

conv = locale.localeconv()          # get a mapping of conventions
x = 1234567.8
locale.format("%d", x, grouping=True)
Out[7]:
'1,234,567'
In [8]:
locale.format_string("%s%.*f", (conv['currency_symbol'],conv['frac_digits'], x), grouping=True)
Out[8]:
'$1,234,567.80'

Templating

Das Modul string enthält eine umfangreiche Klasse Template mit einer einfachen Syntax, die für eine Bearbeitung durch Endbenutzer geeignet ist. Dies ermöglicht es Endbenutzern, ihre Anwendungen anzupassen, ohne die Anwendung selbst ändern zu müssen.

Das Format verwendet Platzhalter, die mit einem vorangestellten \$-Zeichen und anschliessenden gültigen Python-Identifikatoren (alphanumerische Zeichen und Unterstriche) gebildet werden. Werden die Platzhalter mit geschweiften Klammern umgeben, können weitere alphanumerische Zeichen ohne zusätzliche Zwischenräume folgen. Ein doppeltes \$ \$ erzeugt ein einziges Escape-Zeichen \$:

In [9]:
from string import Template
t = Template('${village}folk send $$10 to $cause.')
t.substitute(village='Nottingham', cause='the ditch fund')
Out[9]:
'Nottinghamfolk send $10 to the ditch fund.'

Die Methode substitute() wirft einen KeyError, wenn ein Platzhalter nicht in einem Dictionary oder einem Keyword-Argument bereitgestellt wird. Bei Anwendungen, bei denen die vom Benutzer bereitgestellten Daten unvollständig sein können (wie z.B. bei Serienbrief-Anwendungen) kann die Methode safe_substitute() geeigneter sein - diese Methode lässt Platzhalter unverändert, wenn Daten fehlen:

In [10]:
t = Template('Return the $item to $owner.')
d = dict(item='unladen swallow')
t.substitute(d)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-10-aa21a55b2069> in <module>()
      1 t = Template('Return the $item to $owner.')
      2 d = dict(item='unladen swallow')
----> 3 t.substitute(d)

/home/root/anaconda3/lib/python3.6/string.py in substitute(*args, **kws)
    124             raise ValueError('Unrecognized named group in pattern',
    125                              self.pattern)
--> 126         return self.pattern.sub(convert, self.template)
    127 
    128     def safe_substitute(*args, **kws):

/home/root/anaconda3/lib/python3.6/string.py in convert(mo)
    117             named = mo.group('named') or mo.group('braced')
    118             if named is not None:
--> 119                 return str(mapping[named])
    120             if mo.group('escaped') is not None:
    121                 return self.delimiter

KeyError: 'owner'
In [11]:
t.safe_substitute(d)
Out[11]:
'Return the unladen swallow to $owner.'

Unterklassen der Klasse Template können ein benutzerdefiniertes Trennzeichen angeben. Beispielsweise kann ein Skript zur Umbenennung von Bildern für einen Fotobrowser das Prozentzeichen als Trennzeichen hernehmen um Platzhalter wie das aktuelle Datum, die Bildsequenznummer oder das Dateiformat damit verwenden zu können:

In [12]:
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):  ')

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))
Enter rename style (%d-date %n-seqnum %f-format):  Test_%n.jpg
img_1074.jpg --> Test_0.jpg
img_1076.jpg --> Test_1.jpg
img_1077.jpg --> Test_2.jpg

Eine weitere Anwendung für das Templating ist die Trennung der Programmlogik von den Details mehrerer und unterschiedlicher Ausgabeformate. Damit werden benutzerdefinierte Vorlagen für XML-Dateien, Klartextberichte und HTML-Webberichte möglich.

In [13]:
%%Mooc MoocStringAssessment
Out[13]:

Templating

Verändern sie den Code in Zeile 11 in der obigen Zelle so, dass zusätzlich noch der ursprüngliche Name der Datei über den folgenden Input verwendet werden kann:


fmt = input('Enter rename style (%b-basename %d-date %n-seqnum %f-format):  ')

Geben sie die veränderte Zeile 11 (ohne Einrückung) als Ergebnis ein:



Binärdaten

Das Modul struct bietet die pack()- und unpack()-Funktionen für die Bearbeitung von binären Daten mit variabler Länge. Das folgende Beispiel zeigt, wie man Header-Informationen in einer ZIP-Datei durchlaufen kann, ohne das Modul zipfile zu verwenden.

Eine ZIP-Datei hat für jede komprimierte Datei den folgenden Aufbau als Fileheader (s. ZIP-Format bzw. auch Wikipedia)

  local file header signature     4 bytes  (0x04034b50)
  version needed to extract       2 bytes
  general purpose bit flag        2 bytes
  compression method              2 bytes
  last mod file time              2 bytes
  last mod file date              2 bytes
  crc-32                          4 bytes
  compressed size                 4 bytes
  uncompressed size               4 bytes
  file name length                2 bytes
  extra field length              2 bytes

  file name (variable size)
  extra field (variable size)

In dem Modul struct werden für die binären Strukturen Formatcharacter verwendet, die in der Modul-Dokumentation im Kapitel 7.1.2.2 aufgelistet sind. Dabei repräsentieren die im folgenden Code (Zeile 9) verwendeten "H" und "I" Kodierungen jeweils zwei und vier Byte unsignierte Zahlen und das "<"-Zeichen bedeutet, dass die Standardgröße verwendet und die Daten in der Reihenfolge Little-Endian-Byte sind:

In [14]:
import struct

with open('Bsp.zip', 'rb') as f:
    data = f.read()

start = 0
for i in range(5):
    # Beginn des 30 Byte Fileheaders
    fields = struct.unpack('<IHHHHHIIIHH', data[start:start+30])
    header = dict(zip(["signature", "version", "flag", "comp_method", "last_mod_time", "last_mod_date",
            "crc32", "comp_size", "uncomp_size", "filenamesize", "extra_size"],fields))
    # Beginn des variablen Filenamens
    start += 30
    filename = str(data[start:start+header["filenamesize"]],encoding='UTF-8')
    # Beginn des variablen Extra Feldes
    start += header["filenamesize"]
    extra = data[start:start+header["extra_size"]]
    # Ausgabe
    print("{filename:32} - {comp_method:02x}, {last_mod_time:6}, {last_mod_date:6}, {crc32:08x}, {comp_size:6}, {uncomp_size:8}".format(filename=filename,**header))
    # Beginn des nächsten Fileheaders
    start += header["extra_size"] + header["comp_size"]
Aufgabe-Lösungsvorschlag.ipynb   - 08,  45181,  19121, b9c3410d,  13513,   262708
Funktionale Programmierung.ipynb - 08,  40975,  19107, 492ab107,   1379,     7848
Generatoren.ipynb                - 08,   9314,  19113, d4b49e2e,  23113,   141781
Iteratoren.ipynb                 - 08,  40973,  19107, ddd01fe2,   1872,    15234
Beispiele zu Generatoren.ipynb   - 08,  16387,  19115, f01a27e4,   5915,    41640

Zum Vergleich: Ausgabe von unzip -v

In [15]:
!unzip -v Bsp.zip
Archive:  Bsp.zip
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
  262708  Defl:N    13513  95% 2017-05-17 22:03 b9c3410d  Aufgabe-Lösungsvorschlag.ipynb
    7848  Defl:N     1379  82% 2017-05-03 20:00 492ab107  Funktionale Programmierung.ipynb
  141781  Defl:N    23113  84% 2017-05-09 04:35 d4b49e2e  Generatoren.ipynb
   15234  Defl:N     1872  88% 2017-05-03 20:00 ddd01fe2  Iteratoren.ipynb
   41640  Defl:N     5915  86% 2017-05-11 08:00 f01a27e4  Beispiele zu Generatoren.ipynb
--------          -------  ---                            -------
  469211            45792  90%                            5 files

Frage:

Wie verhält sich jetzt die Ausgabe des Datums (z.B. 19121) und der Uhrzeit (z.B. 45181) in der vorhergehenden Zelle zu der Ausgabe in dieser Zelle (Datum 2017-05-17 und Uhrzeit 22:03) ?

Exkurs und Wiederholung zu Bits und Bytes

siehe Bits und Bytes im Unterkapitel Zahlen und Zeichendarstellung sowie Bit-Operatoren im Unterkapitel Operatoren

Datum wird in 2 Bytes abgespeichert

Um das Datum in 2 Bytes zu speichern, wird der Tag (1-31 oder 0-30) in 5 Bits, der Monat (0-11 oder 1-12) in 4 Bits und das Jahr in 7 Bits abgespeichert, somit erhält man folgende Formel:

datum = (jahr-1980) << 9 | monat << 5 | tag

Hierbei wird normiert auf das Jahr 1980 (Epoche genannt), sodass im Zeitraum zwischen 1980 und 2127 keine Probleme entstehen.

aus der obigen Beschreibung kann man mit den entsprechenden Masken die Umkehrung folgern:

tag   = datum & 0b00011111                         # 0x1f = 0b00011111 (5 Bits)
monat = (datum >> 5) & 0b00001111                  # 0x0f = 0b00001111 (4 Bits)
jahr  = ((datum >> 9) & 0b01111111) + 1980         # 0x7f = 0b01111111 (7 Bits)

Uhrzeit wird in 2 Bytes abgespeichert

Um die Uhrzeit in 2 Bytes zu speichern, werden 2 Sekunden durch 5 Bits, die Minuten durch 6 Bits und die Stunden wieder durch 5 Bits abgespeichert, somit erhält man analog

uhrzeit = stunden << 11 | minuten << 5 | sekunden // 2

woraus sich dann auch die Umkehrung ergibt:

sekunden = (uhrzeit & 0b00011111) * 2              # 0x1f = 0b00011111 (5 Bits)
minuten  = (uhrzeit >> 5) & 0b00111111             # 0x3f = 0b00111111 (6 Bits)
stunden  = (uhrzeit >> 11) & 0b00011111            # 0x1f = 0b00011111 (5 Bits)
In [16]:
import struct

with open('Bsp.zip', 'rb') as f:
    data = f.read()

start = 0
for i in range(5):
    # Beginn des 30 Byte Fileheaders
    fields = struct.unpack('<IHHHHHIIIHH', data[start:start+30])
    header = dict(zip(["signature", "version", "flag", "comp_method", "last_mod_time", "last_mod_date",
            "crc32", "comp_size", "uncomp_size", "filenamesize", "extra_size"],fields))
    # Beginn des variablen Filenamens
    start += 30
    filename = str(data[start:start+header["filenamesize"]],encoding='UTF-8')
    # Beginn des variablen Extra Feldes
    start += header["filenamesize"]
    extra = data[start:start+header["extra_size"]]
    # Berechnung des Datums
    tag = header["last_mod_date"] & 0x1f
    monat = (header["last_mod_date"] >> 5) & 0x0f
    jahr = ((header["last_mod_date"] >> 9) & 0x7f) + 1980
    # Berechnung der Uhrzeit
    sekunden = (header["last_mod_time"] & 0x1f) * 2
    minuten = (header["last_mod_time"] >> 5) & 0x3f
    stunden = (header["last_mod_time"] >> 11) & 0x1f
    # Ausgabe
    print("{filename:32} - {comp_method:02x}, {last_mod_time:6}, {last_mod_date:6}, {crc32:08x}, {comp_size:6}, {uncomp_size:8}, {jahr}-{monat:02}-{tag:02}, {stunden:02}:{minuten:02}".format(filename=filename,tag=tag,monat=monat,jahr=jahr,stunden=stunden,minuten=minuten,**header))
    # Beginn des nächsten Fileheaders
    start += header["extra_size"] + header["comp_size"]
Aufgabe-Lösungsvorschlag.ipynb   - 08,  45181,  19121, b9c3410d,  13513,   262708, 2017-05-17, 22:03
Funktionale Programmierung.ipynb - 08,  40975,  19107, 492ab107,   1379,     7848, 2017-05-03, 20:00
Generatoren.ipynb                - 08,   9314,  19113, d4b49e2e,  23113,   141781, 2017-05-09, 04:35
Iteratoren.ipynb                 - 08,  40973,  19107, ddd01fe2,   1872,    15234, 2017-05-03, 20:00
Beispiele zu Generatoren.ipynb   - 08,  16387,  19115, f01a27e4,   5915,    41640, 2017-05-11, 08:00
In [17]:
%%Mooc Video
Out[17]:

Weitere Literatur

In [51]:
%%Mooc WebReference

Brief Tour of the Standard Library - Part II

https://docs.python.org/3/tutorial/stdlib2.html

Hinweis: Kurze Auflistung einiger weiterer Module aus der Standardbibliothek

In [52]:
%%Mooc WebReference

struct — Interpret bytes as packed binary data

https://docs.python.org/3/library/struct.html

Hinweis: struct — Interpretation von Bytes als gepackte Binärdaten