22. Module

Programme greifen auf Sammlungen von Funktionen zurück. Python erlaubt, diese in Module zu gruppieren. Python wird mit Modulen für viele Zwecke geliefert, etwa für Stringverarbeitung, grafische Benutzerschnittstelle, Verschlüsselung, Mathematik, Web-Programmierung usf. (vgl. Modul-Index).

Um ein Modul zu verwenden, muss man es importieren:

>>> import math
>>> math.sin(0.3)
0.29552020666133955

Funktionen aus einem Modul werden also als modulname.funktionsname gerufen.

Eigene Module sind einfach: Man schreibt alles, was ins Modul gehört, in eine Datei mit der Endung py. Unsere compute-Funktion als Modul:

examples> cat compute.py
"""
A toy module to demonstrate modules and
dictionaries.  The main function is compute()
that takes a string with the expression and a
language specific dictionary.  Some dictionaries
are provided by this module
"""

germanWords = {"eins": '1', "zwei": '2',
  "plus": '+', "mal": '*'}
englishWords = {"one": '1', "two": '2',
  "plus": '+', "times": '*'}

def compute(expr, langDict):
  """
  computes a pseudo-natural-language...

Ein Vorteil von Modulen ist, dass wir nicht jedes Mal, wenn wir eine Funktion brauchen, ihren Quelltext in das neue Programm kopieren müssen. Außerdem können wir dem Modul gleich noch Daten mitgeben, hier etwa die Wörterbücher für die verschiedenen Sprachen.

Danach:

>>> import compute
>>> compute.compute("one plus one",
...   compute.englishWords)
2

Man ahnt schon, dass es etliche Parallelen zwischen Modulen und Objekten gibt – in der Tat sind Module auch Objekte, aber eben sehr spezielle.

Objekte verfügen wie Funktionen über eigene Namespaces, die in diesem Fall so lange existieren sind wie das Objekt lebt – und diese Namespaces fragt man nach Werten, wenn man obj.name sagt.

Innerhalb eines Namespaces müssen natürlich alle Namen verschieden sein, in verschiedenen Namespaces dürfen aber durchaus gleiche Namen vorkommen. So könnte etwa ein Objekt secureHash eine Methode compute haben, die rein gar nichts mit der im Modul compute zu tun hat. Auch in verschiedenen Modulen dürfen Attribute gleichen Namens vorkommen. Das ist schon deshalb nützlich, weil verschiedene Module üblicherweise von verschiedenen Leuten geschrieben werden und diese natürlich nicht wissen können, was die jeweils anderen so an Namen vergeben.

Die Trennung der Namespaces ist gut und wünschenswert. Deshalb rate ich davon ab, Konstrukte wie from module import * zu verwenden, das alle Einträge des Namespace von module in den aktuellen Namespace übernimmt – einerseits weiß nachher niemand mehr, welche Namen aus welchen Modulen kommen, andererseits ist es nur eine Frage der Zeit, bis es zu namespace clashes kommt, also ein Name den anderen überschreibt. So könnte ein Modul den Namen open definieren (das tun nicht wenige), und nachher wundert man sich, warum ein Aufruf von open (das wir später als unseren Schlüssel zu Dateien kennenlernen werden) absolut nicht das tut, was es soll. Noch lustiger wirds, wenn zwei Module jeweils ein eigenes open definieren und das Verhalten des Programms plötzlich davon abhängt, in welcher Reihenfolge die Module importiert werden. Kurz: from module import * nur interaktiv und in äußersten Notfällen verwenden.

Die Variante from module import name, die lediglich name in den aktuellen Namespace übernimmt, ist dagegen in Einzelfällen nützlich und immerhin kontrollierbar. Übertreiben sollte man es damit aber auch nicht, zumal sowas subtile Probleme beim Neuladen von Modulen provoziert.

Module in der Programmentwicklung

Die dynamische Natur von Python bringt es mit sich, dass wir Funktion um Funktion entwickeln und in der Regel sofort testen können. Damit das relativ schnell geht, kann man in einem Fenster den Interpreter laufen lassen und in einem anderen den Editor mit dem Quelltext.

Wenn man das so macht, stellt sich allerdings das Problem, wie man dem Interpreter sagt, dass man den Quelltext geändert hat und er diesen neu laden und compilieren soll. Dafür gibt es die eingebaute Funktion reload, der man einfach den Namen des neu zu ladenden Moduls übergibt. Wenn ihr nochmal das compute-Skript von oben betrachtet, könntet ihr im Interpreter z.B. folgendes tun:

>>> import compute
>>> compute.compute("eins plus eins", compute.germanWords)
2
>>> compute.compute("Eins plus eins", compute.germanWords)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "compute.py", line 18, in compute
    pythonExpression.append(langDict[w])
KeyError: 'Eins'

Ups – vielleicht sollten wir Groß-/Kleinschreibung ignorieren? Wir verlassen den Interpreter nicht, sondern verändern das expr.split() aus der compute-Funktion in einem anderen Fenster zu expr.lower().split(). Zurück im Interpreter kann man es nochmal probieren (in der Regel bekommt man im Interpreter die alten Eingaben mit der Pfeil-hoch-Taste):

>>> compute.compute("Eins plus eins", compute.germanWords)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "compute.py", line 18, in compute
    pythonExpression.append(langDict[w])
KeyError: 'Eins'

– natürlich hat sich nichts geändert, der Interpreter weiß ja nicht, dass wir etwas am Quelltext geändert haben. Aber

>>> reload(compute)
<module 'compute' from 'compute.py'>
>>> compute.compute("Eins plus eins", compute.germanWords)
2

From module import name spielt übrigens nicht mit reload – das geht nicht, weil reload nur im Namespace des neugeladenen Moduls hantieren kann, nicht aber in dem aller anderen Module, die vielleicht Referenzen auf Objekte aus dem fraglichen Modul halten, schon, weil bei diesen Objekten gar nicht mehr zwingend klar ist, wem sie denn eigentlich “gehören”. Siehe auch die Aufgabe unten.

Besser als diese Sorte inkrementeller Entwicklung ist es aber, immer gleich Doctests zu den Funktionen zu schreiben. Näheres dazu gleich.

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Um einzusehen, was bei reload in Kombination mit from module import schief geht, probiert Folgendes:

>>> import compute
>>> from compute import germanWords
>>> id(germanWords)
>>> id(compute.germanWords)
>>> reload(compute)
>>> id(compute.germanWords)
>>> id(germanWords)

Die Funktion id ist uns oben schon mal begegnet – sie gibt so etwas wie einen Fingerabdruck zurück und erlaubt es uns, Objekte zu unterscheiden, selbst wenn sie zufällig den gleichen Wert haben.


Markus Demleitner

Copyright Notice