Wissensdatenbank von GCSoft

>> Wissensdatenbank / Python 3

Modularisierung

Unter Modularisierung versteht man die Aufteilung des Quelltextes in sogenannte Module. Module können in einem
Programm eingebunden werden und stellen dann die enthaltene Funktionalität zur Verfügung.

Es gibt zwei Arten von Modulen:

Selbst geschriebene globale Module sollten in das Unterverzeichnis /usr/lib/python3.8/site-packages oder
in das Unterverzeichnis /home/gerhard/.local/lib/python3.8/site-packages gespeichert werden.

Einbinden globaler Module

Ein globales Modul kann mithilfe der import-Anweisung eingebunden werden.
Beispiel: import math, random

Nachdem ein Modul eingebunden wurde, wird ein neuer Namensraum mit seinem Namen erstellt (z.b.: math, random).
Es ist möglich, den Namen des Namensraums durch eine import/as-Anweisung festzulegen:
import math as mathematik
x = mathematik.sin(mathematik.pi)

Darüber hinaus kann die import-Anweisung so verwendet werden, dass kein eigener Namensraum für das eingebundene
Modul erzeugt wird, sondern alle Elemente des Moduls in den globalen Namensraum des Programms eingebunden werden:
from math import *
x = sin(pi)

Es ist möglich, anstelle des Sterns eine Liste von zu importierenden Elementen des Moduls anzugeben:
from math import sin, pi
Auch hier ist es möglich, durch ein dem Namen nachgestelltes as einen eigenen Namen festzulegen:
from math import sin as hallo, pi as welt
x = hallo(welt)

Lokale Module

Es wird eine Python-Datei erstellt, die sich im selben Verzeichnis befinden muss wie die Python-Datei, die das
lokale Modul einbindet. Der Modulname entspricht dem Dateinamen der zugehörigen Programmdatei ohne Dateiendung.

Namenskonflikte

Durch das Erstellen eigener Module kann es leicht zu Namenskonflikten mit globalen Modulen kommen. Dem Interpreter
ist eine Reihenfolge vorgegeben, nach der er zu verfahren hat, wenn ein Modul importiert werden soll:

  1. Im lokalen Programmordner
  2. Suche globales Modul
  3. ModulNotFoundError

Modulinterne Referenzen

In jedem Modul existieren Referenzen, die Informationen über das Modul selbst enthalten.

Referenz Beschreibung
__builtins__ Ein Dictionary, das die Namen aller eingebauten Typen und Funktionen als Schlüssel
und die mit den Namen verknüpften Instanzen als Werte enthält.
__file__ Ein String, der den Namen der Programmdatei des Moduls inklusive Pfad enthält. Ist nicht
bei Modulen der Standardbibliothek vefügbar.
__name__ Ein String, der den Namen des Moduls enthält.

Module ausführen

Wird ein Modul über eine import-Anweisung eingebunden, wird der in ihm enthaltende Code ausgeführt.
Ob eine Programmdatei als Programm oder als Modul ausgeführt wird, kann anhand des Wertes von __name__
erkannt werden. Bei einem eingebundenen Modul referenziert __name__ den Modulnamen, bei einem ausgeführten
Programm den String "__main__".

Pakete

Mehrere Module können in einem Paket gekapselt werden. Ein Paket kann im Gegensatz zu einem einzelnen
Modul beliebig viele weitere Pakete enthalten, die ihrerseits wieder Module bzw. Pakete enthalten können.

Um ein Paket zu erstellen, muss ein Unterordner im Programmverzeichnis erzeugt werden. Der Name des Ordners
entspricht dem Namen des Pakets. Zusätzlich kann in diesem Ordner eine Programmdatei namens __init__.py
vorhanden sein. Diese Datei enthält Initialisierungscode, der beim Einbinden des Pakets einmalig ausgeführt wird.

Importieren aller Module eines Pakets

Es gibt zwei Möglichkeiten, beide müssem durch den Autor des Pakets implementiert werden:

Namespace Packages

Fehlt in einem Paket die Datei __init__.py, so handelt es sich um ein Namespace Package.
Es können Ordner und somit Packages selben Namens an verschiedenen Orten im Dateisystem, an denen Python nach
Modulen und Paketen sucht, existieren. Beide Ordner haben den gleichen Namen, enthalten aber verschiedene Module.
import package.modul1
import package.modul2

Relative Import-Anweisung

Große Bibliotheken definieren eine beliebig komplexe Paketstruktur. Innerhalb einer solchen Paketstruktur
wird eine relative Variante der import-Anweisun benötigt, die ein Unterpaket anhand einer relativen
Pfadangabe einbindet.
Beispiele:
from . import xyz
from ..bmp import read
from ..bmp import read as read_bmp

Ein Punkt steht dabei für das aktuelle Verzeichnis. Jeder weitere Punkt symbolisiert das ein Level höher
gelegene Verzeichnis.

Eine relative import-Anweisung schlägt fehl, wenn sie außerhalb einer Paketstruktur ausgeführt wird
(SystmError) oder wenn eines der Pakete im relativen Pfad zwischen from und
import nicht eingebunden ist (NameError).

Das Paket importlib

Das Paket importlib der Standardbibliothek implementiert das Importverhalten, das der import-Anweisung
zugrunde liegt.

Es gibt drei grundlegende Einsatzzwecke:

Einbinden von Modulen und Paketen

Die Funktion import_module bekommt den Namen des einzubindenden Moduls bzw. Paket als String übergeben:
import importlib
mah = importlib.import_module("math")

Wenn ein relativer Import durchgeführt werden soll, muss für den optionalen zweiten Parameter ein String
übrgeben werden, der den Pfad zum Basispaket enthält, auf den sich der relative Import bezieht:
read = importlib.import_module("..bmp.read", "formats.png")

Verändern des Import-Verhaltens

Es wird die Möglichkeit geboten, in den Importprozess des Interpreters einzugreifen. Es ist mit diesen
Methoden möglich, Module aus komprimierten Archiven oder aus dem Internet zu laden.

Das Importverhalten wird festgelegt durch eine Kombination sogenannter Finder und Loader.
Ein Finder lokalisiert das einzubindende Modul anhand seines Namens und instanziiert einen passenden Loader,
der es einbindet. In der Liste sys.path_hooks sind die systemweit registrierten Finder eingetragen.

Der Finder

Der Finder ist eine Klasse, die neben ihrem Kontruktor eine Methode find_module enthält.

Der Loader

Ein Loader implementiert im Wesentlichen die Methode load_module, die den Modulnamen übergeben
bekommt. Sie ist dafür zuständig, das Modul einzubinden und die entstandene Modulinstanz zurückzugebn.