4 - Module & Exceptions
Module
Nachdem wir in der letzten Woche gelernt haben, wie man den selbstgeschriebenen Code besser organisieren kann durch Einsatz von Funktionen und Klassen, wird heute gezeigt, wie man nun Dateien mit Code als sogenannte Module speichern kann.
Importieren aus anderer Python-Datei
Um aus einer weiteren Pythondatei Klassen, Funktionen, oder Variablen zu laden benutzt man
import <Dateiname ohne .py>.<Klassen/Funktions/Variablenname>
Wichtig: damit das funktioniert, muss der Python-Interpreter im selben Ordner gestartet worden sein wie die Datei aus der importiert wurde!
Importieren aus Ordnerstruktur
Um aus einer komplexeren Ordnerstruktur importieren zu können, geht man wie folgt vor:
import <Ordner>.<Unterordner>.<Unterunterordner>.<...>.<Dateiname>.<Klassen/Funktions/Variablennamen>
from ... import ...
Möchten wir etwas direkt in den aktuellen Namespace importieren, benutzen wir from ... import
import ... as ...
Mittels import ... as ... können wir dem importierten Modul einen anderen (oft gekürzten) Namen geben.
Beispiel
Die aus einer Übung bekannten Funktionen write_file und read_file haben wir in einer Datei input_output_file.py gespeichert, und sind so von ihnen überzeugt, dass wir sie in Zukunft öfters benutzen möchten.
Sind wir im selben Ordner wie die Datei, ist es ganz einfach, von einer anderen Python-Datei aus auf den schon vorhandenen Code zuzugreifen: wir schreiben einfach
import input_output_file # Achtung, die Endung .py wird weggelassen!
und können dann folgendermaßen auf die Funktionen (sowie Klassen und Variablen), die in der Datei input_output_file.py geschrieben wurden, zugreifen:
data = input_output_file.read_file("dateiname")
Wem es zu lästig ist, bei jedem Funktionsaufruf den Modulnamen zu schreiben, kann am Anfang folgendes tun:
from input_output_file import read_file, write_file # , ... data = read_file("dateiname")
Auf diese Weise werden aber auch nur die Funktionen importiert, die wir explizit nach import auflisten. Wir können sie dann aber benutzen, ohne input_output_file. voranzustellen.
Es geht aber noch eine Stufe höher: Angenommen, wir haben nicht nur eine Datei für Input und Output, sondern noch eine weitere (z.B. für das Einlesen von komprimierten Daten wie Zip-Files zuständig sein). In diesem Fall bietet es sich an einen eigenen Ordner input_output anzulegen, und in diesen die beiden Python-Dateien zu legen.
Ganz ähnlich wie zuvor wollen wir nun die Funktionen read_file und write_file importieren. Um das tun zu können, müssen wir es Python jedoch möglich machen, den Ordner input_output als Modul zu erkennen. Das tun wir, indem wir eine leere Datei mit dem Namen __init__.py im Ordner input_output anlegen. Wir können unseren Code in beliebig tief geschachtelten Ordnerstrukturen vergraben, solange wir nur daran denken, in jedem Ordner eine __init__.py Datei anzulegen.
Danach können wir, wenn wir uns außerhalb des Ordners befinden, per
from input_output.input_output_file import read_file, write_file
die Funktionen read_file und write_file wieder importieren.
Alternativ:
import input_output.input_output_file
Oft ist es ratsam, die Funktionen/Klassen/Variablen nicht direkt in den globalen Namespace zu laden, da es dann zu Konflikten mit schon bestehenden Namen kommen kann.
Um in diesem Fall zu viel Schreibarbeit aufgrund langer Modulnamen zu vermeiden, lässt sich folgender Trick anwenden:
import input_output.input_output_file as iof # 'as' keyword gefolgt von Aliasnamen
Im weiteren Verlauf lässt sich dann per
iof.read_file("bla")
auf die Funktionen zugreifen.
Selbstgeschriebene Module für den Python-Interpreter sichtbar machen
Haben wir nun ein selbsterstelltes Modul irgendwo auf unserer Festplatte liegen, müssen wir nun Python noch sagen, wo es sich befindet. Das machen wir, indem wir den Speicherort zum Pythonpath hinzufügen.
Die einfachste Variante ist vermutlich, den Pythonpfad direkt im Code zu setzen:
import sys sys.path.append("/Pfad/zum/Ordner/in/dem/das/Modul/liegt") import mein_modul # ersetze mein_modul durch gewünschten Modulnamen
Möchte man den Pfad dauerhaft hinzufügen, tun Linux-User folgendes im Terminal:
cd ~ echo 'export PYTHONPATH="/Pfad/zu/meinem/Modul:$PYTHONPATH"' >> .bashrc
Windows-User können folgendes ausprobieren:
In die Datei autoexec.bat wird folgende Zeile geschrieben:
set PYTHONPATH=%PYTHONPATH%;C:\Pfad/zu/meinem/Modul
Die Python Standard Library
Netterweise kommt Python schon mit einer Menge eigener Module daher. Die wichtigsten (persönliche Meinung des Dozenten) werden hier kurz vorgestellt:
sys
Das Modul sys enthält vor allem Variablen, die vom Python-Interpreter benutzt werden oder mit ihm interagieren.
Hilfreich sind vor allem
sys.argv
Diese Variable speichert die Kommandozeilenargumente, mit denen ein Programm aufgerufen wurde.
Folgendes Programm sei unter dem Namen main.py gespeichert:
import sys print("ich wurde aufgerufen mit den Argumenten:", sys.argv)
Starten wir dieses Skript folgendermaßen:
python main.py hier ganz viele kommandozeilen argumente 1 2 3 4 5
so ist die Ausgabe:
ich wurde aufgerufen mit den Argumenten: ['test.py', 'hier', 'ganz', 'viele', 'kommandozeilen', 'argumente', '1', '2', '3', '4', '5']
Alle Argumente werden also als Strings in einer Liste gespeichert.
sys.path
In dieser Variablen werden alle Pfade gespeichert, in denen der Interpreter nach nutzbaren Python-Modulen sucht. Möchte man einen weiteren Pfad hinzufügen, tut man das einfach per
sys.path.append("/mein/neuer/pfad")
Der neu hinzugefügte Pfad verschwindet allerdings wieder aus sys.path wenn das Skript endet!
sys.exit()
Diese Funktion beendet das Skript sofort.
pickle
Erlaubt es, Python-Objekte zu serialisieren, d.h. sie als Datei zu speichern.
Beispiel:
import pickle class MeineKlasse: def __init__(self): self.x = 1 self.y = 2 mk = MeineKlasse() with open("meine_klasse.pickle", "w") as f: pickle.dump(mk, f)
Das gespeicherte Objekt kann anschließend folgendermaßen geladen werden:
with open("meine_klasse.pickle", "r") as f: pickle.load(f)
collections
In diesem Modul sind diverse nützliche Container-Datentypen, die teilweise Erweiterungen der Standard-Container dict, list und tuple sind.
Counter
Damit lassen sich Elemente sehr einfach zählen. Beispiel mit dem Text aus der Übung word_counter:
from collections import Counter c = Counter(text.split()) print(c.most_common(10)) # gibt die 10 häufigsten Wörter in text aus
deque
Eine Art Liste, bei der sich effizient an beiden Enden Elemente hinzufügen oder entfernen lassen.
defaultdict
Ähnlich wie dict, aber legt für einen Key, der bisher nicht gespeichert war, automatisch einen entsprechenden Default-Value an.
Beispiel:
from collections import defaultdict int_dict = defaultdict(int) print(int_dict["blub"]) # gibt den Defaultwert 0 aus list_dict = defaultdict(list): print(list_dict["irgendwas"]) # gibt eine leere Liste aus
OrderedDict
Ein Dictionary, das sich die Reihenfolge der eingefügten Elemente merkt
types
Hilfreiches Modul, falls man doch mal gezwungen ist, Datentypen zu prüfen.
Beispiel:
import types x = 3 y = 1.23 z = True def f(): print("hello world") print(type(x) is types.IntType # True) print(type(y) is types.FloatType ) print(type(z) is types.BooleanType) print(type(f) is types.FunctionType)
math
Dieses Modul enthält die wichtigsten mathematischen Operationen Für Berechnungen komplexer Zahlen, benutze man cmath
from math import sin, cos, exp, log, pi sin(pi)**2 + cos(pi)**2 exp(0) log(1) # usw.
random
Modul zur Erzeugung von Zufallszahlen
Die wichtigsten Funktionen im Überblick:
from random import random, uniform, randint, shuffle, choice random() # erzeugt eine gleichverteilte Pseudozufallszahl zwischen 0 und 1 (ohne die 1). uniform(3.4, 7.8) # erzeugt eine gleichverteilte Pseudozufallszahl (float) zwischen den gegebenen Grenzen def wuerfel(): return randint(1, 6) # erzeugt zufällig (und gleichverteilt) einen Integer zwischen 1 und 6 (einschließlich 6) l = [1, 2, 3, 4, 5] shuffle(l) # vertauscht die Elemente von l zufällig choice(l) # zieht zufällig ein Element aus l
time
Dieses Modul beinhaltet diverse Funktionen, die für Zeitmessungen benutzt werden können.
Beispiel:
import time print(time.time()) # Gibt die Sekunden an, die seit dem 1. Januar 1970 vergangen sind time.sleep(5) # Das Programm stoppt für 5 Sekunden
Für Datumsberechnungen ist darüberhinaus das Modul datetime hilfreich.
argparse
Möchte man ein Programm mit mehreren Parametern von der Kommandozeile aufrufen, wird es recht bald komplex, die Eingaben des Users auf Korrektheit zu prüfen und richtig zu parsen.
Hier hilft das Modul argparse.
Beispiel:
import argparse parser = argparse.ArgumentParser() parser.add_argument("argument") # hier wird festgelegt, dass es genau ein notwendiges Argument gibt args = parser.parse_args()
Im obigen Beispiel haben wir festgelegt, dass das Programm mit genau einem Parameter aufgerufen werden muss. Nicht mer und nicht weniger ist möglich.
Manchmal ist es aber auch hilfreich, optionale Parameter festzulegen. Das geht so:
import argparse parser = argparse.ArgumentParser() parser.add_argument("--argument") # hier wird festgelegt, dass es genau ein notwendiges Argument gibt args = parser.parse_args() if args.argument: print(args.argument) else: print("Kein Argument angegeben")
Dieses Mal können wir das Programm entweder ohne jeglichen Parameter aufrufen, oder wir rufen es folgendermaßen auf (unter der Annahme, dass die Datei main.py heißt):
python main.py --argument irgendwas
pdb
pdb ist der Python Debugger. Er ist ein sehr hilfreiches Tool, um Fehler im eigenen Python-Code zu finden.
Typischerweise ruft man den Debugger auf, indem man an der Stelle seines Codes, an der man etwas inspizieren will, folgende Zeile schreibt:
pdb.set_trace()
An dieser Stelle hält der Debugger das Programm an und wechselt in den interaktiven Modus, in dem man sich die Werte aller Variablen anschauen kann, aber auch neue Variablen definieren oder Funktionen ausführen kann.
Mit folgenden Befehlen kann man weiter durch den Code navigieren:
-
next führt die nächste Zeile des Programms aus und pausiert dann wieder. Enthält die nächste Zeile einen Funktionsaufruf, wird die komplette Funktion ausgeführt, ohne dass in ihr gestoppt wird
-
step ist wie next, "betritt" aber auch Funktionen wenn sie aufgerufen werden
-
Befindet man sich in einer Funktion, sorgt return dafür, dass das Programm erst wieder angehalten wird, wenn das return statement der Funktion erreicht wird.
-
until führt den Code weiter aus, bis die angegebene Zeilennummer erreicht wird
-
jump erlaubt schließlich noch das direkte Springen zu einer beliebigen Zeilennummer
Weitere Module installieren
Es gibt noch unzählige weitere Module für Python, die sich leicht nachinstallieren lassen.
Ubuntu- und Debian-User mit Administratorrechten finden viele Pakete in ihrem Paketmanager, z.B. python-numpy, python-scipy, ...
Alternativ ist es möglich eine sogenannte virtualenv zu benutzen. Diese kopiert den Python-Interpreter, der oftmals in einem geschützten Pfad liegt, in das Home-Verzeichnis des Benutzers. Das erleichtert die Modul-Installation deutlich.
Alle mit Linux können folgendes tun:
cd ~ mkdir virtualenvs virtualenv virtualenvs/pythonkurs source virtualenvs/pythonkurs/bin/activate
Nach diesen vier Schritten wird der Python-Interpreter benutzt der im Verzeichnis ~/virtualenvs/pythonkurs/bin/ liegt, benutzt.
Nun kann man Module mittels pip installieren:
pip install <Modulname>
Exceptions
Bisher gingen wir immer davon aus, dass unsere Programme fehlerfrei laufen, oder haben Fehlermeldungen erstmal ignoriert.. Hier wird nun gezeigt, wie man seinen Code so schreiben kann, dass er auf Fehler direkt reagiert.
Beispiel:
zahl = float(input("Bitte Zahl eingeben: "))
Führen wir dieses Zahl aus und geben brav eine Zahl ein, kommt es zu keinen Problemen. Hat der Anwender jedoch nicht aufgepasst und gibt z.B. "Hallo" ein, wird er von folgender Meldung überrascht:
ValueError: could not convert string to float: Hallo
Ganz logisch, float() kann den String "Hallo" nicht in eine Zahl umwandeln, daher erscheint ein sogenannter ValueError.
Nun möchten wir aber gerne ein anderes Verhalten unseres Codes. Wir möchten, dass er den User so lange nach einer Zahl fragt, bis er sie bekommt. Die Lösung könnte so aussehen:
while True: try: zahl = float(input("Bitte Zahl eingeben: ")) print(5 / zahl) break except ValueError: print("Hey, das war gar keine Zahl!")
Damit hätten wir schonmal das Problem der falschen Benutzereingabe gelöst. Allerdings gibt es immer noch eine Möglichkeit, das Programm zum Absturz zu bringen: Gibt der User 0 ein, grüßt ihn der ZeroDivisionError!
Also fangen wir auch den ab:
while True: try: zahl = float(input("Bitte Zahl eingeben: ")) print(5 / zahl) break except ValueError: print("Hey, das war gar keine Zahl!") except ZeroDivisionError: print("Null ist keine gute Idee...")
Damit haben wir uns erstmal gegen alle Gefahren abgesichert. Eine Liste der möglichen Exceptions gibt es hier.
Doch damit nicht genug, wir können auch selbst Exceptions "raisen", d.h. Code auf Probleme mit einer Exception reagieren lassen:
import types def add_integers(a, b): if type(a) != types.IntType or type(b) != types.IntType: raise ValueError("Bitte nur Integers!") else: return a + b
Diese Funktion beschwert sich beispielsweise lautstark mit einem ValueError, sobald wir ihr ein Argument liefern, das kein Integer ist. Die Wahl der Exception ist dabei uns überlassen