Wichtige Bibliotheken

Inhalt

Im siebten Kapitel gehen wir wichtige Module von Python durch und decken die folgenden Konzepte ab:

  • Systemfunktionen und Zugriff auf das Dateisystem

  • Datums und Zeitformate

  • Reguläre Ausdrücke

  • Kopieren von Objekten

  • Zeitmessungen

  • Pretty Printing von built-in Datentypen

  • Logging

  • Mathematische Funktionen

  • Zufallsgeneratoren

  • Serialisieren von Objekten

  • Lesen von CSV Dateien

Python Standardbibliothek

Python wird nach dem Motto batteries included entwickelt, das heißt es sollte möglichst viel von der Standardbibliothek unterstützt werden, ohne das man zusätzliche Packages benötigt. Die Standardbibliothek wird direkt zusammen mit Python installiert und ist in der Regel immer verfügbar. Im folgenden gucken wir uns einen Teil der Standardbibliothek an, wobei wir ganze Bereiche (zum Beispiel Netzwerke, HTTP, JSON, etc.) ignorieren.

Systemfunktionen und Zugriff auf das Dateisystem

1. Modul "os"

Das Modul os haben wir schon kurz in Kapitel 6 kennengelernt. Durch dieses Modul kann man Betriebssystem spezifische Operationen ausführen, zum Beipiel auf dem Dateisystem.

betriebssystem.py
=================
import os

# prints the current working folder
working_folder = os.getcwd()  
print(working_folder)
try:
    # creates a new folder, use os.makedirs in case nested folders must be created
    os.mkdir("new_folder")    
except FileExistsError:
    # do nothing if folder already exists
    pass                      

# fetches a list of the contents of the current folder (ls)
for file in os.listdir():     
    print(file)
# switches working folder to our new folder
os.chdir("new_folder")        
# switches back to old working folder
os.chdir(working_folder)      
# deletes the created folder
os.rmdir("new_folder")        
print("---------")
for file in os.listdir():
    print(file)
Ausführung vom Programm
>>> python3 betriebssystem.py
/home/deameni/python_examples/my_fonctions/
dictionaries.py
sqrt-copy.py
duckTyping.py
indexZugriffe.py
my_sqrt.py
new_folder
---------
dictionaries.py
sqrt-copy.py
duckTyping.py
indexZugriffe.py
my_sqrt.py

Mit os.system() kann man Befehle auf dem Betriebssystem ausführen, zum Beispiel, um Programme auszuführen. Diese Funktion sollte jedoch nicht mehr verwendet werde. Stattdessen sollte man die Funktion run aus dem subprocess Modul verwenden.

Ausführung mit subprocess
>>> from subprocess import PIPE, run
# executes the command ls and pipes the standard output into the result variable
>>> result = run("ls", stdout=PIPE)
>>> result
CompletedProcess(args='ls', returncode=0, stdout=b'dictionaries.py\nduckTyping.py\n
indexZugriffe.py\nmy_sqrt.py\nsqrt-copy.py\n')

Außerdem gibt es noch das Modul shutil für weitere Dateioperationen, zum Beispiel für das Kopieren oder Löschen von ganzen Verzeichnissen, inklusive der Unterverzeichnisse, und dem POSIX Rechtemanagement (chmod, chown).

Beispielprogramm mit dem Modul shutil
>>> import shutil
>>> import os

>>> # copies the folder
>>> shutil.copytree("/python_examples", "examples")
'examples'

>>> # lists the the subfolder examples
>>> for file in os.listdir("examples"):
...    print(file)
...
dictionaries.py
sqrt-copy.py
duckTyping.py
indexZugriffe.py
my_sqrt.py

>>> # deletes the folder again
>>> shutil.rmtree("examples")
>>> # checks if folder still exists
>>> print("examples" in os.listdir())
False

2. Modul "sys"

Das Modul sys stellt Funktionen zur Kommunikation mit dem Interpreter bereit. Hierdurch kann man zum Beispiel Kommandozeilenparameter auslesen, die Ausführung des Interpreters beenden, Informationen über die Pythonversion auslesen, oder auch Pfade anpassen, wie wir es in Kapitel 6 getan haben.

Beispielprogramm mit dem Modul sys
>>> import sys

>>> # Display the Python version:
>>> print(sys.version)
3.10.6 (main, Aug 10 2022, 11:40:04) [GCC 11.3.0]

>>> # Display the Platform:
>>> print(sys.platform)
linux

Datums und Zeitformate

Viele Daten beinhalten Zeitstempel. Um damit vernünftig umgehen zu können, benötigt man Datumsformate. Hierfür gibt es das Modul datetime.

Beispielprogramm mit dem Modul datetime
>>> import datetime

>>> today = datetime.datetime.now()
>>> print(f"current date and time: {today}")
current date and time: 2022-11-03 10:19:48.102140
>>> print(f"can also access year, month, etc.: {today.year}")
can also access year, month, etc.: 2022
>>> # timedelta with difference between datetimes
>>> fall_of_wall = datetime.date.today()-datetime.date(1989,11,9)
>>> print(f"{fall_of_wall.days} days since the fall of the Berlin wall")
12047 days since the fall of the Berlin wall

Reguläre Ausdrücke

Reguläre Ausdrücke sind ein mächtiges Mittel um Zeichenketten zu analysieren. Anleitungen zu regulären Ausdrücken gibt es Online, zum Beispiel hier. Ein Tutorial zum Thema reguläre Ausdrücke ginge an dieser Stelle zu weit, hier gibt es nur Beispiele für die Verwendung von Regulären Ausdrücken in Python mit dem Modul re. Reguläre Ausdrücke werden nicht in normalen Zeichenketten definiert. Stattdessen gibt es, analog zu formatierten Stringliteralen auch eigene Stringliterale für reguläre Ausdrücke die mit r“…” definiert werden.

Beispielprogramm mit dem Modul re
>>> import re

>>> string1 = "Extracts 10 all 90 integers 0 From 191929123 this string"
>>> print(re.findall(r"[0-9]+", string1))
['10', '90', '0', '191929123']

>>> string2 = "Replaces 10 all 90 integers 0 in 191929123 this string with the empty string"
>>> print(re.sub(r"[0-9]+", "", string2))
Replaces  all  integers  in  this string with the empty string

Kopieren von Objekten

In Kapitel 2 haben wir bereits im Rahmen von Listen kurz über das Kopieren von Objekten gesprochen. Bei unveränderlichen Objekten ist dies in der Regel kein wichtiges Thema. Hier gilt sogar, dass es meistens besser ist, das Objekt nicht zu kopieren sondern nur mehrere Referenzen auf das gleiche Objekt im Speicher zu haben. Bei veränderbaren Objekten, ist es aber häufig wichtig Kopien zu erstellen, um sicher zu gehen, dass das Original nicht verändert wird. Man unterscheidet hier zwischen shallow copy und deep copy. Bei einer shallow copy von einem Objekt A, wird Objekt A zwar komplett dupliziert, aber alles, auf das von Objekt A verwiesen wird nicht. Bei Listen bedeutet das zum Beispiel, dass die Liste selbst kopiert wird, die Elemente, die sich in der Liste befinden jedoch nicht. Verändert man also das Element in einer Liste, ist es in beiden Listen verändert. Bei einer deep copy werden auch die referenzierten Objekte mit kopiert. Hierfür gibt es in Python das Modul copy.

Beispielprogramm mit dem Modul copy
>>> import copy

>>> my_list = [[1,2,3,4], ["a","b","c","d"]]
>>> my_shallowcopy = copy.copy(my_list)
>>> my_deepcopy = copy.deepcopy(my_list)
>>> # removes "d" second list in my_list
>>> my_list[1].pop()
'd'

>>> # removes first element from my_mylist
>>> my_list.pop(0)
[1, 2, 3, 4]

>>> print(f"my_list: {my_list}")
my_list: [['a', 'b', 'c']]

>>> print(f"my_shallowcopy: {my_shallowcopy}")
my_shallowcopy: [[1, 2, 3, 4], ['a', 'b', 'c']]

>>> print(f"my_deepcopy: {my_deepcopy}")
my_deepcopy: [[1, 2, 3, 4], ['a', 'b', 'c', 'd']]

Zeitmessungen

Wenn die Ausführung besonders lange dauert oder zeitkritisch ist, ist es manchmal wichtig zu wissen, wie lange die Ausführung einer Anweisung dauert. Hierzu gibt es in Python das Modul timeit.

Beispielprogramm mit dem Modul timeit
>>> from timeit import Timer

>>> # swaping two values with the traditional "triangle" approach
>>> print(Timer('t=a; a=b; b=t', 'a=1; b=2').timeit())
0.014197620999766514
>>> # swaping two values using packing/unpacking of tuples
>>> print(Timer('a,b = b,a', 'a=1; b=2').timeit())
0.012773645001288969

Für Zeitmessungen von längeren Programmen gibt es die Module profile und pstat, auf die hier nicht genauer eingegangen wird.

Pretty Printing von built-in Datentypen

Die Ausgabe von Dictionaries und Listen ist zwar lesbar, doch gerade bei Objekten mit vielen Einträgen wird dies schwer. Hier hilft das Modul pprint.

Beispielprogramm mit dem Modul pprint
>>> from pprint import pprint

>>> my_dict = {1:["a","b"], 2:["c","d","e"], 3:["f","g","h"], 4:{5: ["i","j","k"]}}
>>> pprint(my_dict, width=20)
{1: ['a', 'b'],
 2: ['c', 'd', 'e'],
 3: ['f', 'g', 'h'],
 4: {5: ['i',
         'j',
         'k']}}

>>> # with depth=2:
>>> pprint(my_dict, depth=2, width=20)
{1: ['a', 'b'],
 2: ['c', 'd', 'e'],
 3: ['f', 'g', 'h'],
 4: {5: [...]}}

Logging

In vielen Programmen will man zur Laufzeit Informationen über die Ausführung protokollieren. Hierbei wird üblicherweise zwischen Fehlern, Warnungen, Informationen, und Debugginginformationen für Entwickler unterschieden. Man kann dann beim Start des Programms entscheiden, wie detailiert die Logdaten sein sollen. Um dies zu Unterstützen gibt es in Python das [https:docs.python.org3library/logging.html

Beispielprogramm mit dem Modul logging
>>> import logging

>>> logging.debug('Debugging information')
>>> logging.info('Informational message')
>>> logging.warning('Warning about something -- probably nothing broken, but you should check')
WARNING:root:Warning about something -- probably nothing broken, but you should check

>>> logging.error('Error occurred -- can mean shutdown or just that some result is not produced')
ERROR:root:Error occurred -- can mean shutdown or just that some result is not produced

>>> logging.critical('Critical error -- often means program immediatly shuts down')
CRITICAL:root:Critical error -- often means program immediatly shuts down

Wie man oben sieht, werden per Default nur Warnungen, Fehler, und kritische Fehler ausgegeben. Außerdem sind die Logausgaben formatiert. Erst kommt der Typ der Lognachricht, dann der Name des loggers (Default: root), dann erst die Nachricht. Sowohl welche Lognachrichten erscheinen, als auch das Format der Nachrichten, ist konfigurierbar.

Beispielprogramm 2 mit dem Modul logging
>>> # Note: the Kernel must be restarted for this to work properly.
>>> # basicConfig only works if the logging module was not yet used
>>> import logging

>>> # sets the logging format
>>> logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
>>> # gets a logger with a name
>>> logger = logging.getLogger("my_logger")
>>> # defines the log level
>>> logger.setLevel("DEBUG")
>>> logger.debug('Debugging information')
2022-11-03 11:14:11,048 - my_logger - DEBUG - Debugging information

>>> logger.info('Informational message')
2022-11-03 11:14:11,048 - my_logger - INFO - Informational message

>>> logger.warning('Warning about something -- probably nothing broken, but you should check')
2022-11-03 11:14:11,048 - my_logger - WARNING - Warning about something -- probably nothing broken,
but you should check

>>> logger.error('Error occurred -- can mean shutdown or just that some result is not produced')
2022-11-03 11:14:11,048 - my_logger - ERROR - Error occurred -- can mean shutdown or just that some
result is not produced

>>> logger.critical('Critical error -- often means program immediatly shuts down')
2022-11-03 11:14:11,049 - my_logger - CRITICAL - Critical error -- often means program immediatly
shuts down

Man kann den Logger auch so konfigurieren, dass die Ausgabe in eine Datei geleitet wird. Dies wird jedoch hier nicht weiter behandelt.

Mathematische Funktionen

Auch wenn das laufende Beispiel bisher die Quadratwurzel war, ist es nicht nötig derartige Funktionen selbst zu Implementieren. Stattdessen kann man einfach das Modul math benutzen.

Beispielprogramm mit dem Modul math
>>> import math

>>> value = 4.2
>>> print(f"Square root of {value}: {math.sqrt(value)}")
Square root of 4.2: 2.04939015319192

>>> print(f"Square of {value}: {math.pow(value, 2)}")
Square of 4.2: 17.64

>>> print(f"Natural logarithm of {value}: {math.log(value)}")
Natural logarithm of 4.2: 1.4350845252893227

>>> print(f"Ceiling of {value}: {math.ceil(value)}")
Ceiling of 4.2: 5

>>> print(f"pi: {math.pi}")
pi: 3.141592653589793

Außerdem kann man mit dem Modul statistics einfache statistische Berechnungen von Daten machen.

Beispielprogramm mit dem Modul statistics
>>> import statistics

>>> my_list = [4, 8, 15, 16, 23, 42]
>>> print(f"mean: {statistics.mean(my_list)}")
mean: 18

>>> print(f"median: {statistics.median(my_list)}")
median: 15.5

>>> print(f"stdev: {statistics.stdev(my_list)}")
stdev: 13.490737563232042

Zufallsgeneratoren

Es gibt viele Algorithmen die randomisiert Arbeiten, vor allem im Bereich der Datenanalyse. Hierfür benötigt man Zufallsgeneratoren, die vom Modul random zur Verfügung gestellt werden.

Beispielprogramm mit dem Modul random
>>> import random

>>> print(f"random floating point number in [0,1]: {random.random()}")
random floating point number in [0,1]: 0.9039997204325259

>>> print(f"random integer between 0 and 10 [0,1]: {random.randrange(11)}")
random integer between 0 and 10 [0,1]: 2

>>> grades = [1.0, 1.3, 1.7, 2.0, 2.3, 2.7, 3.0, 3.3, 3.7, 4.0, 5.0]
>>> print(f"random element from a list: {random.choice(grades)}")
random element from a list: 1.7

>>> print(f"randomly selected subample of 3 elements from a list: {random.sample(grades, 3)}")
randomly selected subample of 3 elements from a list: [2.0, 2.3, 4.0]

Serialisieren von Objekten

Serialisierung ist ein verbreitetes Konzept um in einer Programmierumgebung verfügbare Daten zu Speichern oder diese an andere Anwendungen zu versenden. Eine Objektserialisierung schreibt vor, wie ein Objekt zur Speicherung oder zum Laden beschrieben wird. In Python gibt es das Modul pickle zur Serialisierung von beliebigen Objekten in Python. Beim pickeln wird ein Objekt in einer Binärdarstellung gespeichert, beim unpickeln wird das Objekt aus dem Speicher in den Interpreter geladen.

Beispielprogramm mit dem Modul pickle
>>> import pickle

>>> # An arbitrary collection of objects supported by pickle
>>> my_dict = {1:["a","b"], 2:["c","d","e"], 3:["f","g","h"], 4:{5: ["i","j","k"]}}
>>> with open('./data.pickle', 'wb') as file:
...     pickle.dump(my_dict, file) # dumps my_dict to the opened file
...
>>> del my_dict
>>> try:
...     my_dict
... except NameError:
...     print("my_dict does not exist")
...
my_dict does not exist

>>> with open('./data.pickle', 'rb') as file:
...     # The protocol version used is detected automatically, so we do not
...     # have to specify it.
...     my_dict = pickle.load(file)
...
>>> print(my_dict)
{1: ['a', 'b'], 2: ['c', 'd', 'e'], 3: ['f', 'g', 'h'], 4: {5: ['i', 'j', 'k']}}

Lesen von CSV Dateien

Comma Separated Value (CSV) sind ein sehr weit verbreitetes Format zum Austausch von Daten, die eine tabellenartige Struktur haben. Jede Zeile entspricht einer Datenzeile in der Tabelle. Die Einträge in einer Zeile werden durch Kommas getrennt um die Spalten zu definieren, daher auch der Name CSV. Hier können Beispiele für CSV-Dateien erzeugt werden.

Auch wenn Kommas ein übliches Trennzeichen sind, werden auch häufig Semikolons und Tabulatoren zum Trennen der Spalten verwendet. Auch der Umgang mit Anführungszeichen, zum Beispiel um Spalten mit Zeichenketten die ein Komma enthalten zu ermöglichen, ist nicht einheitlich definiert. Wie das Format ist, muss man vorher (ggf. durch angucken der Datei) bestimmen. Mit dem Modul csv kann man CSV Dateien bequem in Python laden. Dateien werden dabei Zeilenweise als Liste von Strings gelesen, bzw. geschrieben. Die CSV-Dialekte, die unterstützt werden, kann man der Dokumentation entnehmen

Beispielprogramm mit dem Modul csv
>>> import csv

>>> with open('./example.csv') as file:
...     reader = csv.reader(file, delimiter=',')
...     for row in reader:
...         print(row)
...
['id', 'firstname', 'lastname', 'email', 'email2', 'profession']
['100', 'Eadie', 'Pip', 'Eadie.Pip@yopmail.com', 'Eadie.Pip@gmail.com', 'worker']
['101', 'Fredericka', 'Hamil', 'Fredericka.Hamil@yopmail.com', 'Fredericka.Hamil@gmail.com', 'doctor']
['102', 'Maridel', 'Kellby', 'Maridel.Kellby@yopmail.com', 'Maridel.Kellby@gmail.com', 'doctor']
['103', 'Maryellen', 'Wandie', 'Maryellen.Wandie@yopmail.com', 'Maryellen.Wandie@gmail.com', 'doctor']
['104', 'Hayley', 'Mayeda', 'Hayley.Mayeda@yopmail.com', 'Hayley.Mayeda@gmail.com', 'worker']