13. Apr 2020
Lesedauer 14 Min.
Bibliotheken nutzen
Programmieren mit Python, Teil 4
Nach der Behandlung weiterer Daten-Container unternimmt dieser Teil des Python-Umsteiger-Kurses einen Abstecher in die Welt der Python-Bibliotheken.

Dieser Artikel zeigt mithilfe weiterer einfacher Beispiele, wie die Programmiersprache Python funktioniert und eingesetzt werden kann. Neben weiteren Daten-Containern wird es in erster Linie um die Benutzung fertiger Standard-Bibliotheken gehen. Das in der Python-Community am häufigsten verwendete Modul ist sicherlich numpy, das unter anderem auch eine sehr leistungsfähige Array-Klasse enthält.
Dictionaries
Ein Dictionary funktioniert ähnlich wie eine Python-Liste [1], ist allerdings generischer. Während bei einer Liste der Zugriffsindex immer eine ganze Zahl (Integer) sein muss, können Sie mit einem Dictionary fast jeden Datentyp als Index verwenden.Solche Dictionarieskennen wir zum Beispiel vom Dateisystem der Festplatte: Der Index eines Eintrags ist der Dateiname in Form eines Strings. Dieser „zeigt“ dann eindeutig auf den Dateiinhalt. Man spricht dann auch von Mapping zwischen einer Reihe von Indizes (Schlüsseln) und einem dazugehörigen Wert (Schlüssel-Wert-Paare).Man kann sich ein Dictionary auch als Wörterbuch vorstellen, wie hier im ersten Beispiel gezeigt wird. Zuerst wird ein leeres Dictionary erstellt:de2en = dict()
print(<span class="hljs-name">de2en</span>)
Dictionaries werden bei der Ausgabe mit geschweiften Klammern versehen, ein leeres Dictionary wird also durch {} dargestellt. Nun können Elemente in das Dictionary eingefügt werden. Hier wird die bereits bekannte Index-Schreibweise benutzt:
de2en[<span class="hljs-string">"eins"</span>] = <span class="hljs-string">"one"</span>
de2en[<span class="hljs-string">"zwei"</span>] = <span class="hljs-string">"two"</span>
de2en[<span class="hljs-string">"drei"</span>] = <span class="hljs-string">"three"</span>
Der Index des Dictionarys ist, ebenso wie der Wert, vom Typ String. Die Ausgabe liefert folgendes Ergebnis:
{<span class="hljs-string">'eins'</span>: <span class="hljs-string">'one'</span>, <span class="hljs-string">'zwei'</span>: <span class="hljs-string">'two'</span>, <span class="hljs-string">'drei'</span>: <span class="hljs-string">'three'</span>}
Hier kann man die Zuordnung von Schlüssel und Wert genau erkennen. Wenn nun ein deutsches Wort ins Englische übersetzt werden soll, wird das ganz einfach mit folgendem Python-Befehl realisiert:
<span class="hljs-function"><span class="hljs-title">print</span><span class="hljs-params">(de2en[<span class="hljs-string">"zwei"</span>])</span></span>
Das Ausgabeergebnis ist in diesem Fall natürlich two.Wenn man einen Schlüssel benutzt, der nicht im Dictionary enthalten ist, gibt der Python-Interpreter die Fehlermeldung KeyError aus. Probieren Sie es einmal aus. Die Eingabe des Befehls
<span class="hljs-function"><span class="hljs-title">print</span><span class="hljs-params">(de2en[<span class="hljs-string">"vier"</span>])</span></span>
liefert <span class="hljs-keyword">die </span>entsprechende Fehlermeldung.
In der Praxis müssen häufig sogenannte Histogramme erstellt werden. Diese stellen die Anzahl bestimmter Werte in einem Balken mit einer entsprechenden Höhe dar. Man kann sehr gut ein Dictionary benutzen, um zum Beispiel die Anzahl der Buchstaben in einem Satz zu zählen. Buchstaben, die nicht vorkommen, benötigen auch keinen Speicherplatz im Dictionary. Die Vorgehensweise zeigt Listing 1.
Listing 1: Ein Histogramm
# Histogramm-Funktion<br/>def histogramm(data):<br/> balken = dict()<br/> data = data.upper()<br/> <br/> for c <span class="hljs-keyword">in</span> data:<br/> <span class="hljs-keyword">if</span> c not <span class="hljs-keyword">in</span> balken:<br/> # Erste Eintragung<br/> balken[c] = <span class="hljs-number">1</span><br/> else:<br/> # Weitere Buchstaben<br/> balken[c] += <span class="hljs-number">1</span><br/> <br/> return balken<br/><br/>### Main-Methode ###############<br/>satz = <span class="hljs-string">"Das ist ein schoenes Pythonprogramm"</span><br/>diagramm = histogramm(satz)<br/><br/># Einfache Standardausgabe<br/>print(diagramm) <br/><br/># Ende des Programms
Die Histogramm-Funktion lässt sich mithilfe eines Dictionarys sehr einfach und speicherschonend programmieren. In der Funktion wird zuerst ein leeres Dictionary erstellt. Da wir in unserer Histogramm-Funktion die Anzahl der vorkommenden Buchstaben in einem String zählen wollen, konvertieren wir diesen zunächst komplett in Großbuchstaben, um die Ergebnisdatenmenge etwas zu reduzieren.Nun kann man mit einer einfachen for-Schleife über die Buchstaben im String data iterieren. Mit einem if-Statement wird geprüft, ob der Buchstabe schon im Dictionary vorhanden ist. Wenn dies nicht der Fall ist, wird ein neues Element mit dem Buchstaben als Schlüssel im Dictionary erstellt und mit dem Integer-Wert 1 initialisiert. In allen anderen Fällen wird der Wert zum jeweiligen Schlüssel einfach um eins erhöht. Als Ergebnis gibt die Funktion das erzeugte Dictionary mit den Zählwerten zurück.Die Main-Methode des Beispiels definiert zunächst einen String und ruft dann die Histogramm-Funktion auf. Im einfachsten Fall gibt man dann das zurückgegebene Dictionary mit einem print-Befehl aus. Das Ergebnis ist allerdings nicht sonderlich gut lesbar:
{'D': 1, 'A': 2, 'S': 4, ' ': 4, 'I': 2, 'T': 2, 'E': 3,
'N': 3, 'C': 1, 'H': 2, 'O': 3, 'P': 2, 'Y': 1, 'R':
2, 'G': 1, 'M': 2}
Mit zwei weiteren Python-Zeilen kann man die Situation aber wesentlich verbessern: Es wäre doch schön, wenn die Buchstaben in alphabetischer Reihenfolge mit der jeweiligen Anzahl dahinter ausgegeben würden. Dazu muss das Dictionary nach den Schlüsselwerten sortiert werden. Im Moment ist die Reihenfolge durch das erste Auftreten eines Buchstabens bestimmt. Dazu wird die Zeile mit dem print-Befehl einfach auskommentiert und die folgenden Zeilen eingefügt:
# Einfache Standardausgabe
#print(diagramm)
# Sortierte Ausgabe
for index in sorted (diagramm.keys()):
print(index, " - ", diagramm[index])
# Ende des Programms
In der neuen for-Schleife wird der Inhalt des Dictionarys nach den Schlüsseln (Eigenschaft keys() im Dictionary diagramm) sortiert und mit dem neuen print-Befehl ausgegeben.Listen-Objekte können auch als Werte in einem Dictionary enthalten sein:
list1 = ["a", "b", "c"]
list2 = ["x", "y", "z"]
d = dict()
d[1] = list1
d[2] = list2
# Ausgabemöglichkeiten:
# Gesamtes Dictionary
print(d)
# Eine Liste aus dem Dictionary
print(d[1])
# Ein Element aus einer Liste
print(d[1][2])
Der erste print-Befehl gibt das gesamte Dictionary in der bekannten Weise aus:
{1: ['a', 'b', 'c'], 2: ['x', 'y', 'z']}
Der zweite Ausgabebefehl gibt eine komplette Liste aus. Die Liste wird durch den Schlüssel (hier: 1) festgelegt. Der letzte print-Befehl zeigt ein Element aus der gewählten Liste an. Der erste Index (hier wieder: 1) gibt den Schlüssel für die Liste im Dictionary an. Der zweite Index in eckigen Klammern (hier: 2) gibt die Position des Elements in der jeweiligen Liste (beginnend mit 0) an.
Globale Variablen
An dieser Stelle möchte ich einen kurzen Ausflug in die Welt der globalen Variablen unternehmen. Solche globalen Variablen werden außerhalb aller Funktionen deklariert und gehören zu einem besonderen Frame mit dem Namen __main__. Auf diese Variablen kann man von jeder Funktion aus zugreifen. Während alle lokalen Variablen nach dem Funktionsaufruf verschwinden, bleiben die globalen Variablen erhalten und können später aus einer anderen Funktion heraus benutzt werden.Hierbei gibt es allerdings einige Regeln, die unbedingt beachtet werden müssen.meinWert = 42 # globale Variable
def meineFunk():
print(meinWert)
### Hier beginnt das main-Frame
meineFunk()
print(meinWert)
### Ende des main-Frames
Wenn Sie dieses kleine Programm laufen lassen, wird die Zahl 42 zweimal im Konsolenfenster ausgegeben. Die Variable meinWert wird im main-Frame deklariert und initialisiert. Danach wird nur noch lesend auf die Variable zugegriffen.Das Beispiel soll nun so geändert werden, dass die aufgerufene Funktion die globale Variable meinWert ändert:
meinWert = 42 # globale Variable
def meineFunk2():
meinWert = 100
print(meinWert)
### Hier beginnt das main-Frame
print(meinWert)
meineFunk2()
print(meinWert)
### Ende des main-Frames
In meinem Beispiel habe ich eine neue Funktion meineFunk2 definiert, welche die Variable meinWert auf 100 setzt und diese dann ausgibt.In der main-Funktion wird zunächst die globale Variable ausgegeben (42), dann wird die Funktion aufgerufen und die Variable meinWert wird auf 100 gesetzt und ausgegeben. Wir verlassen nun die Funktion und befinden uns wieder in der main-Methode. Nun wird die globale Variable meinWert erneut ausgegeben. Wir erhalten nun aber überraschenderweise wieder den Wert 42.Was ist passiert? Es ist eigentlich ganz einfach: In der Funktion meineFunk2 hat der Python-Interpreter eine neue lokale Variable meinWert erstellt und mit 100 initialisiert. Am Ende der Funktion wurde diese Variable dann weggeräumt. In der Funktion selbst wurde also gar nicht auf die globale Variable zugegriffen.Um das gewünschte Verhalten für die globale Variable meinWert zu erhalten, ist nur eine kleine, aber wichtige Änderung erforderlich:
# Korrekte globale Variable
meinWert = 42 # globale Variable
def meineFunk3():
global meinWert
meinWert = 100
print(meinWert)
### Hier beginnt das main-Frame
print(meinWert)
meineFunk3()
print(meinWert)
### Ende des main-Frames
Beachten Sie in diesem Beispiel die Zeile mit der global-Deklaration in der Funktion meineFunk3. Dort wird für den Python-Interpreter festgelegt, dass in der Funktion immer die globale Variable meinWert benutzt werden soll.Als Ergebnis erhalten wir die folgende Ausgabe im Konsolenfenster:
42
100
100
Das folgende Beispiel mit einer globalen Variablen liefert übrigens beim Aufruf der Funktion eine Fehlermeldung:
count = 0
def falscheFunktion():
count = count + 1 # Fehler
Hier haben wir die Variable count, die im main-Frame angelegt wird. Auf diese Variable count wird nun in der Funktion lesend zugegriffen, sie ist aber noch gar nicht angelegt und initialisiert. Dies führt dann zur Fehlermeldung UnboundLocalError: ...Nun kommen wir wieder auf Listen und Dictionaries, also auf sogenannte veränderbare Typen, zu sprechen. Diese Container enthalten Daten, die zur Laufzeit des Programms geändert werden können. Dabei werden lediglich die Daten im Container verändert, es wird jedoch kein neuer Container erzeugt.Im folgenden Beispiel wird zunächst ein Dictionary im main-Frame erstellt und zwei Daten-Elemente werden initialisiert. Die Funktion fügt ein weiteres Element in den Container ein. Diese Operation funktioniert einwandfrei, weil nur der Inhalt des existierenden Dictionarys geändert wird.
# Ein globales Dictionary
meinDict = {"a":0, "b":1}
def meineFunkDict():
# Existierendes Dictionary ändern
meinDict["a"] = 2
### main-Frame ###
print(meinDict)
meineFunkDict()
print(meinDict)
### Ende main-Frame ###
Die Ausgabe des Beispielprogramms zeigt das ursprüngliche und das erweiterte Dictionary:
{'a': 0, 'b': 1}
{'a': 0, 'b': 1, 'c': 2}
Wenn Sie jedoch der Variablen meinDict ein neues Dictionary-Objekt zuweisen wollen, muss wieder das Schlüsselwort global verwendet werden:
# Ein globales Dictionary
meinDict = {"a":0, "b":1}
def meineFunkDict2():
global meinDict
# Neues Dictionary zuweisen
meinDict = dict()
### main-Frame ###
print(meinDict)
meineFunkDict2()
print(meinDict)
### Ende main-Frame ###
Tupel
Ein Tupel ist eine Reihenfolge (Sequenz) von Werten. Die Werte können von beliebigem Typ sein. Die einzelnen Elemente in einem Tupel werden über einen Integer-Index angesprochen. Tupel und Listen sind sich sehr ähnlich, es gibt allerdings einen entscheidenden Unterschied: Tupel sind nicht veränderbar.Ein Tupel wird durch eine kommaseparierte Liste von Werten erstellt:meinTupel = 'a', 'b', 'c'
Es ist zwar nicht erforderlich, aber wegen der besseren Lesbarkeit werden Tupel in runden Klammern geschrieben. Dies gilt auch für die Ausgabe von Tupeln im Konsolenfenster:
nochEinTupel = ( 1, 2.4, 'abc' )
print(nochEinTupel)
print(nochEinTupel[1])
Hier wird ein Tupel mit unterschiedlichen Typen erzeugt. Der erste print-Befehl gibt das gesamte Tupel-Objekt in runden Klammern aus. Der zweite print-Befehl listet dagegen nur das zweite Element des Tupels. Wie bei Listen wird auch der Index bei Tupeln nullbasierend angewendet.Bei der Initialisierung von Tupel-Objekten muss man etwas aufpassen, denn die Zeile
xxx = ( 'abc' )
liefert kein Tupel, sondern einen String. Um ein Tupel mit nur einem Element zu erstellen, müssen Sie nach dem Wert ein Komma einfügen:
xxx = ( 'abc', )
yyy = 'abc', # geht auch
Ein leeres Tupel wird durch den folgenden Python-Befehl erzeugt:
zzz = tuple()
Wenn man als Argument bei der Tupel-Initialisierung ein Sequenz-Objekt (String, Liste oder Tupel) übergibt, erhält man ein neues Tupel mit sämtlichen einzelnen Elementen dieser Sequenz:
t = tuple('abcdefghij')
print(t)
Dieses kleine Code-Snippet liefert folgende Ausgabe:
('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j')
Nun kann man mit Integer-Indizes auf die einzelnen Elemente im Tupel zugreifen.
print(t[0]) # a
print(t[-1]) # j
print(t[2:4]) # ('c', 'd')
print(t[7:]) # ('h', 'i', 'j')
print(t[:2]) # ('a', 'b')
Bei der Indizierung von Tupel-Elementen werden also die gleichen Regeln angewendet, die auch für Listen gelten.Wenn Sie jedoch versuchen, ein Element eines Tupels zu verändern, erhalten Sie die Fehlermeldung TypeError: ...:
t[1] = 'B' # Fehler: TypeError
Man kann die Elemente eines Tupels also nicht ändern, aber man kann ein bereits existierendes Tupel durch ein anderes ersetzen:
# Neues Tupel erstellen
tup = ('a', 'b', 'c')
print(tup)
# Neues Tupel hinten erweitern
tup = tup + ('Z',)
print(tup)
# Neues Tupel vorne erweitern
tup = ('A',) + tup
print(tup)
# Noch ein neues Tupel aus dem alten erzeugen
tup = tup[:2] + tup[3:]
print(tup)
Das Beispielprogramm liefert folgende Ausgaben:
('a', 'b', 'c')
('a', 'b', 'c', 'Z')
('A', 'a', 'b', 'c', 'Z')
('A', 'a', 'c', 'Z')
Im Zusammenhang mit Tupeln gibt es auch noch eine sehr interessante Lösung. Sie haben bestimmt schon einmal zwei Variablen mithilfe einer temporären Variablen ausgetauscht:
# Es gibt zwei Variablen a und b
temp = a
a = b
b = temp
Hier kann man in Python die sogenannte Tupel-Zuweisung benutzen, die wesentlich eleganter ist:
# Es gibt wieder zwei Variablen a und b
print(a, b)
a, b = b, a # Hier Tupel-Zuweisung
print(a, b)
Der Code in der Zeile zwischen den beiden print-Befehlen vertauscht tatsächlich die Inhalte der beiden Variablen. In diesem Fall ist die linke Seite vom Gleichheitszeichen ein Tupel von Variablen, während auf der rechten Seite ein Tupel von Ausdrücken zu finden ist. Jeder Wert wird dann der entsprechenden Variablen auf der linken Seite zugewiesen, wobei aber die Werte auf der rechten Seite vor der Zuweisung erst komplett ausgewertet werden. Bei dieser Operation ist jedoch zu beachten, dass auf der linken und der rechten Seite des Gleichheitszeichens immer gleich viele Elemente vorhanden sein müssen.Die Benutzung von Tupeln ist auch im Zusammenhang mit Funktionen sehr interessant. Normalerweise können Funktionen nur einen Wert mit dem return-Befehl zurückgeben. Wenn man aber ein Tupel-Objekt aus der Funktion zurückgibt, kann man damit auch mehrere Ergebniswerte (auch unterschiedlichen Typs) weitergeben.Im folgenden Beispiel soll in einer Python-Funktion der y-Wert einer Funktion an der Stelle x berechnet werden. Außerdem sollen auch noch die Werte der ersten und zweiten Ableitung ermittelt werden. Alle drei Ergebnisse sollen mit return an die aufrufende Funktion transferiert werden:
def func(x):
# Die Funktion y(x)
y = 3*x**3 + x**2 + 2*x – 2
# Die 1. Ableitung y'(x)
y1 = 9*x**2 + 2*x + 2
# Die 2. Ableitung y''(x)
y2 = 18*x + 2
# Rückgabe der drei Ergebniswerte
return y, y1, y2
### Hauptprogramm ###
x = 1.5
# Rückgabe in ein Tupel
result = func(x)
print(result)
print(type(result))
# Rückgabe in einzelne Variablen
res0, res1, res2 = func(x)
print("Funktionswert: ", res0)
print("1. Ableitung : ", res1)
print("2. Ableitung : ", res2)
# Ende Hauptprogramm
Zunächst definieren wir die Berechnungsfunktion func mit dem Parameter x. An dieser Stelle sollen die Funktion und die beiden Ableitungen berechnet werden. Das ist alles sehr unspektakulär.Erst die Rückgabe der Ergebnisse ist interessant: Es werden drei Variablen zurückgegeben, die Python zu einem Tupel zusammenfasst. Dies kann man im ersten func-Aufruf im Hauptprogramm sehr schön sehen, denn die Ausgabe des result-Objekts im print-Befehl zeigt die drei Ergebniswerte in runden Klammern – also ein Tupel-Objekt.Durch die Ausgabe in der nachfolgenden Zeile wird dies bestätigt:
(13.375, 25.25, 29.0)
<class 'tuple'>
Die einzelnen Werte im Tupel werden durch Indizierung mit eckigen Klammern angesprochen. Die folgende Zeile gibt den Wert der ersten Ableitung aus:
print(result[1])
Der zweite Aufruf von func benutzt dagegen auf der linken Seite des Gleichheitszeichens drei Variablen (res0, res1 und res2), in welche die einzelnen Werte im Rückgabe-Tupel kopiert werden. Diese können dann ganz normal weiterverwendet werden.Es gibt aber noch ein weiteres interessantes Tupel-Feature in Python. Man kann Tupel benutzen, um Funktionen mit variabler Parameteranzahl zu programmieren. Bei einer solchen Funktion werden alle Parameterwerte beim Aufruf zu einem Tupel zusammengefasst. Im Kopf der Funktion wird nur ein Parameter deklariert, dem ein * vorangestellt wird. Das folgende Beispiel zeigt eine spezielle Ausgabefunktion, die beliebig viele Parameter im Konsolenfenster ausgibt:
# Datenübergabe vieler Werte als Tupel
def superPrint(*data):
# 1: Ausgabe als Tupel
print(data)
# 2: Als einzelne Werte
for x in data:
print(x)
In der Funktion superPrint werden zwei Ausgabevarianten gezeigt: die einfache Ausgabe als Tupel und eine Auflistung der Werte in einzelnen Zeilen. Entscheidend ist hier der Parameter *data in der Kopfzeile der Funktion. Die Funktion kann nun mit einer unterschiedlichen Parameteranzahl aufgerufen werden:
# Mit konstanten Werten
superPrint(1, 2.4, 'abc', 4, -567.567)
superPrint('a', 'b', 'c')
# Mit Variablen
x = 1
y = 4.5678
superPrint(x, y)
Die relationalen Operatoren (<, >, <=, >= …) kann man ebenfalls mit Tupeln und anderen Sequenz-Containern verwenden. Python beginnt damit, das erste Element der beiden Sequenzen zu vergleichen. Falls diese gleich sind, geht es mit den nächsten Sequenz-Elementen weiter, bis unterschiedliche Elemente gefunden werden. Nun wird der jeweilige relationale Operator angewendet. Wenn die Bedingung nicht erfüllt ist, werden die nachfolgenden Elemente ignoriert. Hier einige Beispiele; das Ergebnis steht jeweils im Kommentar dahinter:
(0, 1, 2) < (0, 3, 4) # True
(0, 1, 2) > (0, 3, 4) # False
(0, 1, 2) == (0, 1, 2) # True
(1, 2, 3) == (1, 2) # False
(1, 2, 3) < (1, 3, 1) # True
(1, 2, 3) < (1, 2, 1) # False
Ein kleiner Ausflug: Bibliotheken, Arrays und numpy
Python ist eine Skriptsprache, die zur Laufzeit interpretiert wird. Das bedeutet jedoch, dass die Ausführung von Python-Code langsamer abläuft, als zum Beispiel ein gleichartiger Laufzeit-Code in C, C++ oder C# ausgeführt wird. Dafür muss der Code in Python nicht aufwendig durch einen Compiler übersetzt und gebunden werden.Ein wesentlicher Vorteil bei der Programmierung mit Python ist jedoch, dass viele Bibliotheken tatsächlich in der Programmiersprache C geschrieben wurden und dadurch sehr performant sind. Das ist im Prinzip auch der Weg, den man bei größeren Python-Projekten geht: möglichst wenig Python-Code, dafür aber möglichst viele Aufrufe in die schnellen Python-Bibliotheken.Eine wichtige und sehr schnelle Bibliothek ist numpy. An dieser Stelle wollen wir nun lernen, wie solche vorgefertigten Bibliotheken in Python aufgerufen werden.Wenn Sie bereits mit anderen Programmiersprachen zu tun hatten, haben Sie sich vielleicht gewundert, warum es in Python Listen, Dictionaries und Tupel, aber keine einfachen Arrays gibt. Dieses Rätsel wird nun aufgelöst: Es gibt auch in Python Arrays, und diese befinden sich in der Bibliothek numpy. Allerdings steht hier die Bibliotheksbenutzung im Vordergrund.Die Python-Arrays möchte ich Ihnen im Rahmen dieses Artikels nur mit einigen wenigen Beispielen erläutern. Wenn Sie sich damit intensiver beschäftigen wollen, möchte ich Ihnen die Artikelserie „Mathematik mit Python“ [2][3][4][5] nahelegen. Dort werden die Möglichkeiten der Python-Arrays ausführlich mit vielen Beispielen und anderen Bibliotheken vorgestellt.Die folgenden kleinen Beispielprogramme können Sie nur dann korrekt ausführen, wenn Sie die Bibliothek numpy installiert haben. Sollte das nicht der Fall sein, können Sie die Installation mit folgendem Befehl aus dem Kommandozeilenfenster nachholen:pip install numpy
Wenn man eine externe Bibliothek mit Python benutzen möchte, muss sie zunächst mit einem import-Befehl bereitgestellt werden. So gibt es zum Beispiel die Bibliothek math, welche einfache mathematische Standardfunktionen enthält. Dazu zählen Wurzelberechnung, Sinus, Cosinus, Logarithmen-Funktionen und viele andere mehr. Hier ein vollständiges Beispiel:
# Bibliothek math benutzen
import math
a = math.sqrt(2.0) # Wurzel
print(a)
a = math.exp(3.0) # Exponent berechnen
print(a)
a = math.sin(1.2) # Sinus
print(a)
Zunächst wird in der zweiten Zeile des Beispiels die Bibliothek math für die Benutzung importiert. In den folgenden Zeilen werden verschiedene Funktionen aus der Library aufgerufen (sqrt, sin, exp ...). In dieser Variante müssen Sie den Bibliotheksnamen (also: math) beim Aufruf einer Funktion immer mit einem Punkt voranstellen.Möchte man den Bibliotheksnamen vermeiden, gibt es noch eine andere Variante für den import-Befehl:
# Funktion sqrt aus Bibliothek math benutzen
from math import sqrt, log
a = sqrt(2.0)
print(a)
a = log(0.5)
print(a)
In diesem Beispiel werden die Funktionen sqrt und log direkt zur Verfügung gestellt und können ohne den Bibliotheksnamen angesprochen werden.Und nun kommt noch eine dritte Variante für den import-Befehl. Hierbei kann man dem Bibliotheksmodul einen neuen Namen geben. Das kann zum Beispiel dann sinnvoll sein, wenn es schon eine Variable mit dem Namen math gibt:
# Variable math – keine gute Idee
math = 15
# Bibliothek math als mlib nutzen
import math as mlib
# Wurzel berechnen
a = mlib.sqrt(math)
print(a)
Natürlich sollte man solchen Code vermeiden, da er sehr schwer zu lesen ist und bei Änderungen schnell zu Fehlern führt. Aber der Code funktioniert. Wenn die Variable math initialisiert ist und danach die Bibliothek importiert wird (importmath), kann auf die ursprüngliche Variable nicht mehr zugegriffen werden.
Arrays aus der Bibliothek numpy
Die Leser, die bereits andere Programmiersprachen kennen, werden an dieser Stelle vielleicht die normalen ein- oder mehrdimensionalen Arrays vermissen. Nun ja, die kommen jetzt dran, aber dazu benötigen wir die Bibliothek numpy.Diese Standard-Arrays möchte ich hier jedoch nur sehr kurz besprechen.Im ersten Beispiel sollen zwei Arrays angelegt und in einer Schleife mit Daten befüllt werden. In einer zweiten Schleife sollen die Daten im Konsolenfenster ausgegeben werden.import numpy as np
import math
t = np.empty(201)
s = np.empty(201)
# Schleife von 0 bis einschl. 200
for i in range(201):
t[i] = (i - 1) * 0.01
s[i] = 1.0 + math.sin(2 * math.pi * t[i])
# Ausgabe der berechneten Daten (teilweise)
for k in range(10, 20):
print(t[k], s[k])
Am Anfang des Programms werden die benötigten Bibliotheken importiert: numpy und math. Hier wird numpy unter dem Alias-Namen np bereitgestellt. Diese Vorgehensweise werden Sie in vielen Python-Programmen so vorfinden.Danach werden zwei leere Arrays t und s erstellt, die jeweils 201 Elemente groß sind. Die Arrays sind jedoch nicht wirklich leer, denn sie allokieren den Speicher für 201 Double-Precision-Zahlen (8 Byte). Dieser Speicherbereich wird aber nicht mit Nullen initialisiert. Das bedeutet, dass die folgenden Codezeilen
import numpy as np
x = np.empty(10)
print(x)
mehr oder weniger verrückte Zahlen im Konsolenfenster ausgeben. Man muss also unbedingt darauf achten, dass man nicht auf uninitialisierte Array-Elemente zugreift.Im Beispielprogramm wird nun mit dem range-Befehl eine Schleife angelegt, welche die einzelnen Datenelemente berechnet. Diese Schleife wird genau 201-mal nacheinander durchlaufen und interpretiert. In einer weiteren Schleife werden einige Datenwerte ausgegeben. Da Python eine Skriptsprache ist, werden die Aktionen in den Schleifen nicht besonders schnell sein, da sie immer wieder neu interpretiert werden.Darum möchte ich Ihnen hier noch eine zweite Variante vorstellen, mit der die Berechnung deutlich schneller abläuft:
import numpy as np
# Berechnung mit der Array-Syntax
t = np.arange(0.0, 2.0, 0.01)
s = 1.0 + np.sin(2 * np.pi * t)
# Ausgabe der berechneten Daten (teilweise)
for k in range(10, 20):
print(t[k], s[k])
Wie Sie im Beispielprogramm sehen können, ist die erste Rechenschleife komplett verschwunden. In der ersten Zeile wird das Array t mit Daten gefüllt. Hierzu wird die Funktion arange aus der numpy-Bibliothek benutzt. Der erste Parameter im arange-Aufruf ist der Startwert, es folgen der Endwert und die Schrittweite für die Befüllung der t-Arrays.Und nun kommt der Supertrick in der nächsten Zeile. Bei der Berechnung der Werte im Array s wird eine spezielle Funktionalität aus numpy benutzt (np.sin, spezielle Multiplikation und Addition), welche die Rechenoperationen wesentlich schneller ausführt.In [2] werden in der dort zu findenden Tabelle 1 die erforderlichen Rechenzeiten angegeben. Variante zwei ist etwa 15-mal so schnell wie Variante eins. Auch C#- oder C++-Programme sind nur noch unwesentlich performanter. Wenn es also möglich ist, sollte man die Rechenoperationen in Python immer mit der Funktionalität aus der numpy-Bibliothek ausführen.
Die Lösung der Übungsaufgabe
In der vorangegangenen Folge dieser Artikelserie [1] sollte ein Programm erstellt werden, welches bei ineinandergeschachtelten Ganzzahlen-Listen die Summen über die Ganzzahlwerte aller Listen berechnet. Hierzu muss über Listen in Listen in Listen ... iteriert werden. Ich habe schon angedeutet, dass hier eine Rekursion zum Einsatz kommen kann. Eine mögliche Lösung könnte wie in Listing 2 aussehen.Listing 2: Ineinandergeschachtelte Listen verarbeiten
# Summe mit verschachtelten Listen<br/>def summe(liste):<br/> teil = 0<br/> for a in liste:<br/> # Ist a wieder eine Liste?<br/> if type(a) is list:<br/> # Ja, summe rekursiv aufrufen<br/> teil += summe(a)<br/> else:<br/> # Nein: Einfach addieren<br/> teil += a<br/><br/> return teil<br/><br/># *** Hauptprogramm ***<br/>liste1 = [1, 5, 4, 8, 9]<br/>liste2 = [20, 30, 50, [-1, -2, -3]]<br/>liste = [liste1, 100, 200, liste2]<br/>print("Liste: ", liste)<br/><br/>total = summe(liste)<br/> <br/>print("Summe: ", total)<br/># *** Ende Hauptprogramm ***
Es wird eine Funktion summe erstellt, welche die Summe der Elemente einer Liste berechnen kann. Bei der Iteration über die Elemente der Liste wird jedoch jedes Mal geprüft, ob ein einfacher Wert oder wieder ein Listen-Objekt vorliegt (type(a) is list). In diesem Fall wird die summe-Funktion rekursiv für die innere Liste aufgerufen. Dieser Aufruf gibt dann natürlich die Summe der inneren Liste zurück und wird zur Hauptsumme addiert. Die Rekursionen können mehrfach ausgeführt werden, je nach Struktur der Liste.In der main-Methode wird eine komplizierte Liste erstellt, die summe-Funktion aufgerufen und anschließend das Ergebnis ausgegeben.
Und nun die nächste Aufgabe
Es soll noch ein bisschen mit Tupeln geübt werden, besonders als Rückgabe aus Funktionen.Schreiben Sie ein kleines Programm, welches eine Funktion enthält, die sowohl das Volumen als auch die Oberfläche einer Dose berechnet. Als Eingabeparameter übergeben Sie nur den Radius, als Rückgabewerte im return-Statement sollen die drei Werte für den Durchmesser, die Oberfläche und das Volumen zurückgegeben werden.Rufen Sie die Funktion aus der main-Methode auf, ordnen Sie die Rückgabewerte des Funktionsaufrufs den drei Variablen D, O und V zu und geben Sie die Ergebnisse im Konsolenfenster aus.Zusammenfassung
In vierten Teil des Kurses haben wir weitere Container-Objekte kennengelernt. Außerdem haben wir fertige Bibliotheken angewendet und dabei eine einfache Möglichkeit für die einfache und schnelle Berechnung von vielen Datenwerten in Arrays kennengelernt.Fussnoten
- Bernd Marquardt, Listenreich, Programmieren mit Python, Teil 3, dotnetpro 4/2020, Seite 128 ff., http://www.dotnetpro.de/A2004Python
- Bernd Marquardt, Arrays mit Schleife, Mathematik mit Python, Teil 1, dotnetpro 12/2017, Seite 64 ff., http://www.dotnetpro.de/A1712Rechnen
- Bernd Marquardt, Der Mathe-Turbo, Mathematik mit Python, Teil 2, dotnetpro 1/2018, Seite 78 ff., http://www.dotnetpro.de/A1801Rechnen
- Bernd Marquardt, Python kann noch mehr, Mathematik mit Python, Teil 3, dotnetpro 7/2018, Seite 70 ff., http://www.dotnetpro.de/A1807Rechnen
- Bernd Marquardt, Vielfältige Berechnungen, Mathematik mit Python, Teil 4, dotnetpro 8/2018, Seite 88 ff., http://www.dotnetpro.de/A1808Rechnen