Des Algorithmikers Werkzeug

Eine Einführung in die Programmiersprache Python

Eine Einführung in die Programmiersprache Python

Des Algorithmikers Werkzeug

Eine Einführung in die Programmiersprache Python


Als Guido van Rossum Python entwickelte, wollte er eine „einfache“ Programmiersprache erzeugen, die Schwachstellen anderer Systeme umging. Aufgrund der einfachen Syntax und durchdachter syntaktischer Kniffe entwickelte sich die Sprache als Standard für diverse wissenschaftliche Anwendungen wie Machine Learning.

Wer mit C, Java oder Perl aufgewachsen ist, betrachtet Python (auch ob der sehr eigenwilligen Community) vielleicht als Programmiersprache für eher weniger begabte Entwickler. Das ist schon deshalb unfair, weil Python im Bereich der Bibliotheken einen immensen Umfang aufweist und einige sehr interessante syntaktische Kniffe mitbringt.

Der Artikel geht davon aus, dass der Leser eine andere Programmiersprache – sei es C++ oder Java – kennt und mehr über die Besonderheiten Pythons erfahren will. Als Host soll eine unter Ubuntu 14.04 laufende AMD-8-Kern-Workstation dienen. Das bedeutet nicht, dass Python nicht auch unter macOS oder Windows existiert: Es gibt kaum ein Betriebssystem, das ohne Python auskommen muss.

Zähmung des Chaos

Der vor einigen Wochen erfolgte Rücktritt Guido van Rossums bestätigte eine schwerwiegende Schwachstelle der Python-Welt: Man hatte noch nie eine wirklich klare Roadmap – Python ist die Referenzimplementierung für das System des intelligenten Chaos. Aufgrund der vergleichsweise hohen Popularität ergibt sich eine problematische Situation. Es gibt eine Vielzahl verschiedener Python-Versionen, die miteinander im Großen und Ganzen inkompatibel sind. Zudem spannen viele Linux-Distributionen Python zur Realisierung von Systemdiensten ein – installiert man versehentlich einen inkompatiblen Interpreter, hat man es danach mit einer nicht mehr startfähigen Station zu tun.

Unter Ubuntu 14.04 ist die Lage insofern haarig, als Python und Python3 verschiedene Versionen des Interpreters aufrufen können:

tamhan@TAMHAN14:~$ python -V
Python 2.7.6
tamhan@TAMHAN14:~$ python3 -V
Python 3.4.3

Kommen Bibliotheken und andere lustige Dinge dazu, fühlt man sich an den berühmt-berüchtigten Würfel aus ballistischem Gel erinnert. Es ist nur eine Frage der Zeit, wann eine Änderung die Enchilada zum Kollaps bringt. Zur Umgehung des Problems bietet sich das System des virtuellen Environments an, dessen Funktion in Abbildung 1 schematisch dargestellt ist.

hanna_pythonml_1.tif_fmt1.jpgAbb. 1: Virtuelle Environments isolieren Python-Ausführungsumgebungen analog zu einem Container

Die Installation der virtuellen Environment Engine ist von Betriebssystem und Python-Version abhängig. Auf der Workstation des Autors erfolgt sie durch Eingabe des folgenden Kommandos, Nutzer alternativer Betriebssysteme finden online weitere Informationen:

tamhan@TAMHAN14:~$ sudo apt-get install python3.4-venv

Das Anlegen eines neuen virtuellen Environments erfolgt durch eine Befehlssequenz, die ebenfalls von Plattform zu Plattform unterschiedlich ist. Unter Ubuntu 14.04 sehen die Befehle folgendermaßen aus:

tamhan@TAMHAN14:~/suspython$ python3 -m venv workspace
tamhan@TAMHAN14:~/suspython$ cd workspace/
tamhan@TAMHAN14:~/suspython/workspace$ source bin/activate
(workspace) tamhan@TAMHAN14:~/suspython/workspace$

Der Lohn der Mühen ist die in Abbildung 2 gezeigte Arbeitsumgebung, die neben einer Konfigurationsdatei auch eine Gruppe von Verweisen enthält, über die Shell und Betriebssystem den Zugriff auf den Python-Interpreter „verbiegen“ können. Ein weiterer netter Aspekt des virtuellen Environments ist, dass sie das lokale Installieren von Bibliotheken erlauben – sitzt die Bibliothek im virtuellen Environment, ist sie vom Rest des Systems unabhängig.

hanna_pythonml_2.tif_fmt1.jpgAbb. 2: Virtuelle Environments bestehen aus Verknüpfungen und Konfigurationsskripten

Nach der Ausführung des Kommandos source bin/activate ist das Terminal parametriert. Das erkennt man unter anderem daran, dass der Name der Arbeitsumgebung in einer Klammer vor dem eigentlichen Prompt erscheint. Möchte man ein schon erstelltes virtuelles Environment nachträglich laden, muss die Source abermals eingegeben werden:

tamhan@TAMHAN14:~/suspython/workspace$ source bin/activate
(workspace) tamhan@TAMHAN14:~/suspython/workspace$

Interaktiv und eingabegesteuert

Programmiersprachen wie C folgen dem EVA-Prinzip. Der Programmierer liefert eine oder mehrere Codedateien an, die kompiliert und ausgeführt werden. In Python sieht die Lage anders aus – dazu gebe man probeweise das Kommando python ein und drücke einige Male CTRL + C, um sich am in Abbildung 3 gezeigten Verhalten zu erfreuen.

hanna_pythonml_3.tif_fmt1.jpgAbb. 3: Der Interpreter zeigt sich vom Keyboard-Interrupt nur wenig beeindruckt

Die Ursache dieses Verhaltens ist, dass der Python-Interpreter auch interaktiv betrieben werden kann. Dabei gibt man Zeile um Zeile ein und bekommt sofort eine Ausgabe – das ist als behelfsmäßiger Taschenrechner ideal geeignet. Wer diesen Betriebsmodus verlassen möchte, muss die Tastenkombination CTRL + D verwenden.

In der Praxis nutzt man Python zur Ausführung von angelieferten Dateien. Code liegt in Form von Dateien mit der Endung .py vor, die sich folgendermaßen zur Ausführung anweisen lassen:

(workspace) tamhan@TAMHAN14:~/suspython/workspace$ python test.py

Retter der Männerfreundschaft

Ist man streitlustig und jung, kommt der Moment, an dem man mit Kollegen über die beste Art der Formatierung von Code zusammendonnert. Was für einen melierten Coder harmlos klingt, kann tiefe Freundschaften auf eine harte Probe stellen.

In Python wird dieses Problem umgangen, weil die Sprache radikale Einschränkungen an den Aufbau der Codedateien anlegt. Der erste Unterschied ist, dass der Python-Interpreter Code in logische und physikalische Zeilen unterteilt. Eine logische Zeile kann dabei aus mehreren physikalischen bestehen, alternativ kann eine physikalische Zeile unter Nutzung des ;-Operators mehrere logische Zeilen aufnehmen.

Python 3 erlaubt die Nutzung von UTF-8-Zeichen im Quellcode, während Python 2 auf ASCII beschränkt ist. Beide Programmierschemata können sowohl Tabs als auch Whitespaces aufnehmen, was für Diskussionen sorgt. Im als Quasistandard geltenden Dokument PEP8 [1] schreibt Guido van Rossum, dass die Verwendung von Leerzeichen der Verwendung von Tabulatoren vorzuziehen ist. Idealerweise verwendet man pro Einrückungsebene vier Leerzeichen, ein intelligent eingestellter Editor kann diese bei Drücken der Tab-Taste automatisch einschreiben.

Python 2 brachte Logik mit, die während der Programmausführung Tabs durch Spaces zu ersetzen sucht – bei der Arbeit mit Python 3 müssen Sie in der Datei entweder nur Tabs oder nur Spaces verwenden. Die Ursache dieser pedantisch klingenden Richtlinie erschließt sich durch folgendes Beispielprogramm:

weight = float(input("Tam Air - Gewichtsrechner"))
print("Habe eine Kiste!")
if weight > 70:
  print("Strafe zahlen: 25€.")
elif weight > 50:
  print("Strafe zahlen: 5€.")
print("Die Tam Air bedankt sich")

Dieser primitive Gewichtsrechner einer wenig kulanten Airline ist interessant, weil er eine Gruppe von Python-spezifischen Elementen demonstriert. Erstens wird die Funktion print mit geschwungenen Klammern aufgerufen, stellt also eine gewöhnliche Funktion dar – in Python 2 war der Befehl ein in die Sprache eingebautes Intrinsic, das logischerweise ohne Klammern aktiviert werden musste.

C- und Visual-Basic-Programmierer reiben sich hier verwundert die Augen, da sowohl Klammern als auch EndIf-Statements fehlen. Die Codeblöcke von Selektionen, Interaktionen und ähnlichen Elementen werden in Python ausschließlich durch die Anzahl der Whitespaces bestimmt, die vor ihnen stehen.

Der Interpreter zeigt sich bei der Durchsetzung dieser Richtlinie wenig kooperativ – als Beispiel wollen wir den if-Teil ein wenig anpassen, und beim Einfügen des zweiten Print-Befehls statt vier Leerzeichen derer fünf platzieren:

if weight > 70:
  print("Strafe zahlen: 25€.")
    print("Sie packen zu viel!")

Wer das vorliegende Programm auszuführen versucht, wird mit der in Abbildung 4 gezeigten Fehlermeldung bestraft. Im Interesse der Vollständigkeit sei angemerkt, dass es sich hierbei um ein fundamentales Designkonzept von Python handelt – wer damit nicht zurechtkommt, muss sich eine andere Programmiersprache aussuchen.

hanna_pythonml_4.tif_fmt1.jpgAbb. 4: Fehler in Sachen Indentation werden vom Interpreter scharf geahndet

Mehr Datentypen als üblich

Ein alter Kalauer besagt, dass ein Programmierer, der eine objektorientierte Sprache kennt, alle kennt. Dies mag auf Python bis zu einem gewissen Grad zutreffen, da man Klassen und Co. unterstützt. Interessant ist, dass sich Python im Bereich der grundlegenden Datentypen wesentlich breiter aufstellt als P.-T.-Konkurrenz. Wo C und Co. nur primitive Variablen direkt unterstützen, bietet Python reiche Vielfalt.

Erfahrene Entwickler mögen sich nicht daran stoßen, wenn sie Listen und ähnliches von Hand realisieren. Wer Python als Werkzeug zur Implementierung eines Algorithmus sieht, freut sich darüber, wenn er zur Realisierung der eigentlichen Geschäftsaufgabe so wenige Jobs wie möglich durchführen muss.

Eine angenehme Erweiterung ist die direkte Unterstützung komplexer Zahlen: ein Darstellungsformat, das insbesondere in der Elektrotechnik weit verbreitet ist und sich aus Absolutbetrag und Winkel zusammensetzt. Komplexe Zahlen dienen beispielsweise zur Beschreibung von Spannungen und Strömen in Schaltkreisen, die bei sinusoidaler Anregung zeitverschoben auftreten.

Wo man in anderen Sprachen von Hand drauflos programmieren muss, unterstützt Python den Entwickler direkt mit einem komplexen Datentyp:

import cmath
z = complex(4,4);
print ("Realteil : ",end="")
print (z.real)
print ("Imaginärteil : ",end="")
print (z.imag)

Elektrotechniker und Rüstungselektroniker sind an dieser Stelle traurig: In Python werden komplexe Zahlen prinzipiell in Komponenten dargestellt, die Polarform ist nur als errechnete Darstellung verfügbar. Das vorliegende Programmstück illustriert den Import des cmath-Moduls, das die für die Verarbeitung von komplexen Zahlen vorgesehenen Funktionen bereitstellt. Die komplexe Aufrufweise von print nutzt einen Tupel – eine Konstruktion, die wir unds später anschauen wollen. Als alter Elektronikknochen kann sich der Autor hier die Vorstellung von rect nicht verkneifen. Sie zeigt, wie man einen in Polarkoordinaten vorliegenden Wert in eine Python-Variable überträgt:

cmath.rect(r, phi)

Elemente in Reih und Glied

Eine weitere Besonderheit der reichen Basistypvielfalt der Sprache sind Sequenztypen. Die Frage, was ein Sequenztyp genau ist, beschäftigt einen Raum voller Python-Entwickler lange. Der Klassiker „Python in a Nutshell“ bezeichnet Sequenztypen als Felder, die eine Art KV-Speicher realisieren, der aus einer Ganzzahl und einer anderen Werteart zusammengesetzt ist. Es sei angemerkt, dass diese Definition nicht zu 100 Prozent stimmt – der Named Tuple bricht mit dem Gebot des Vorhandenseins eines ints als Primärschlüssel.

Der offene Aufbau von Python erlaubt Bibliotheken und Drittentwicklern das Erzeugen eigener Sequenztypen. In der Praxis trifft man die folgenden sieben Elemente besonders häufig:

  • Buffer

  • Byte Arrays

  • Lists

  • Strings

  • Tuples

  • Unicode Strings

  • Xranges

Dass Arrays in dieser Auflistung nicht erscheinen, ist kein Zufall. Die aus C bekannten Datenstrukturen stehen in Python zwar in Form verschiedener Module zur Verfügung, die zur Basisdistribution gehören. Man setzt sie außerhalb mathematischer Prozesse allerdings selten ein und nutzt stattdessen Python-spezifischere Elemente.

Da eine komplette Besprechung all dieser Datentypen in einem einzigen Artikel illusorisch ist, wollen wir uns auf Tuples und ihre Konsorten beschränken. Im Prinzip hatten wir die Elemente weiter oben kennen gelernt – der Aufruf von print erfolgte mit einem seltsamen Parameter:

print ("Imaginärteil : ",end="")

Ein primitives Tupel ist eine Liste, die von Python allerdings als immutable deklariert wird. Hinter diesem komplex klingenden Begriff verbirgt sich der Gedanke, dass sich das Objekt zur Laufzeit nicht mehr ändert – mutable-Elemente verändern sich mitunter auch noch nach der Deklaration. Aus syntaktischer Sicht schenken sich die beiden Speicherfelder wenig – Tabelle 1 demonstriert die Unterschiede.

Beispielcode Tupel

Beispielcode Liste

thistuple = ("apple", "banana", "cherry")

print(thistuple[1])

thislist = ["apple", "banana", "cherry"]

thislist[1] = "blackcurrant"

print(thislist)

Tabelle 1: Unterschiede zwischen Liste und Tupel

Tupel und Listen unterscheiden sich vor allem bei der Erstellung. Möchte ein Entwickler ein Tupel erstellen, schließt er die anzulegenden Elemente in runde Klammern ein. Eine Liste entsteht, wenn man die aus anderen Programmiersprachen als Arraysymbol verwendeten eckigen Klammern einspannt.

Zur Laufzeit findet sich derweil kein großer Unterschied – auch beim Tupel erfolgt der Zugriff auf die individuellen Elemente mit eckigen Klammern. Wichtig ist, dass Änderungen der Tupel- bzw. Listenstruktur nur im Fall der Liste erlaubt sind. Das bedeutet allerdings nicht, dass alle in einem Tupel befindlichen Elemente nicht veränderbar sind. Speichert man beispielsweise eine Liste als Member eines Tupels, lassen sich die individuellen Werte der Liste sehr wohl verändern – konstant ist nur ihre Position im Tupel.

Die Frage, wann man Listen und wann Tupel verwenden sollte, lässt sich hervorragend diskutieren [2]. Logisch ist, dass die zur Laufzeit nicht mehr veränderliche Form Tupel für alle Situationen ideal geeignet macht, wo die gehaltenen Informationen keinen Veränderungen unterliegen. Ein weiterer exzellenter Einsatzbereich sind geschwindigkeitsrelevante Situationen – da der Speicheraufbau nicht mehr verändert werden muss, erweist sich diese Variante als performanter.

Damit ist der weiter oben verwendete Aufruf von print allerdings noch nicht erklärt. Ein Tupel, aber auch eine Liste, ordnet den enthaltenen Elementen Zahlenwerte zu. Es gibt Situationen, in denen man andere Schlüsseltypen bevorzugen würde. In diesem Fall schlägt die Stunde des Named Tuple, das mit Python 2.6 bzw. 3.0 Eingang in den Sprachstandard fand. Die Erzeugung eines Named Tuple könnte einfacher nicht sein:

from collections import namedtuple
Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0)
pt2 = Point(2.5, 1.5)
print(pt1.x)
print(pt1[0])

Die Verwendung des richtigen Ladebefehls ist wichtig. Wir nutzen from – import, um die im Paket „Collections“ liegende Funktion namedtuple direkt ansprechbar zu machen. Diese dient sodann als Generator, mit dem wir zwei Points ins Leben rufen.

Interessant ist, dass die in einem Named Tuple befindlichen Elemente nicht nur über ihre Namen angesprochen werden können. Im Interesse der Abwärtskompatibilität sorgt die collections-Klasse auch dafür, dass ein klassischer numerischer Zugriff ebenfalls problemlos funktioniert.

Theoretisch lässt sich ein derartiger Named Tuple direkt an eine Funktion übergeben. Der weiter oben gesehene seltsame Aufruf von print hat allerdings eine andere Ursache: Es handelt sich dabei um namensbehaftete Funktionsparameter. Auch sie wollen wir mit einem kleinen Beispiel näher ansehen. Die Funktion hurz realisiert dabei das gewichtete Summieren von vier Parametern, die der Bequemlichkeit halber auf die Namen a, b, c und d hören:

def hurz(a, b, c, d):
  return a*1 + b*2 + c*3 + d*4
print (hurz(1,2,3,4))
print (hurz(4,3,2,1))

Dank der Multiplikation mit verschiedenen Gewichten ist die Reihenfolge der Parameter von Bedeutung. Der erste Aufruf der Funktion liefert den Wert 30 zurück, während der zweite Aufruf, ob der anderen Anordnung der Zahlen, nur eine Summe von 20 ergibt. Die Nutzung „namensbehafteter“ Parameter erlaubt uns das Umschiffen des Problems:

def hurz(a, b, c, d):
  return a*1 + b*2 + c*3 + d*4
print (hurz(1,2,3,4))
print (hurz(d=4,c=3,b=2,a=1))

Die Ausführung des vorliegenden Programms führt dazu, dass zweimal die gleiche Zahl am Bildschirm erscheint. Der Interpreter weist die angelieferten Werte nämlich anhand der Namen in die gewünschten Parameterslots ein.

Interessanter ist in diesem Zusammenhang die Frage, was passiert, wenn man nicht alle Parameter anliefert. Als Beispiel dafür wollen wir versuchen, hurz nur mit Werten für a und d auszustatten:

def hurz(a, b, c, d):
  return a*1 + b*2 + c*3 + d*4
print (hurz(d=4,a=1))

Die Ausführung des vorliegenden Programms wird mit dem in Abbildung 5 gezeigten Fehler quittiert. Für das Anliefern optionaler Parameter ist also etwas zusätzliche Arbeit erforderlich, die im Methodenkopf erfolgen muss.

hanna_pythonml_5.tif_fmt1.jpgAbb. 5: Das Fehlen der Parameter „b“ und „c“ lässt die Programmausführung scheitern

Ein naiver Versuch zur Umgehung des Problems bestünde darin, nur den Werten b und c Standardwerte einzuschreiben:

def hurz(a, b=0, c=0, d):
  return a*1 + b*2 + c*3 + d*4
print (hurz(d=4,a=1))

Leider ist diese Vorgehensweise nicht erfolgversprechend. Der Python-Sprachstandard mag zwar sehr flexibel sein, schreibt an dieser Stelle aber vor, dass Parameter ohne Defaultwerte immer nur vor dem ersten Parameter stehen dürfen, der einen Defaultwert mitbringt. Zum Erhalten eines kompilierungsfähigen Programms müssen wir also auch dem Wert d einen Standard Value einschreiben, um uns an einer lauffähigen Variante zu erfreuen. Diese auf den ersten Blick akademisch klingende Spielerei ist in der Praxis von großer Bedeutung. Viele Python-Funktionen und -Bibliotheken – nicht nur das weiter oben verwendete print – haben einen oder zwei fixe Parameter und werden sonst über eine Art Named Tuple aus diversen Parametern mit Informationen versorgt. Das werden wir im Artikel weiter unten abermals sehen, wenn wir uns der automatisierten Realisierung von Diagrammen zuwenden.

Die Liste als Werkzeug

Das Speichern von Informationen in Listen ist nur in den seltensten Fällen Selbstzweck. Insbesondere im wissenschaftlichen Bereich handelt es sich dabei meist um Mess- oder Rechenwerte, die gewichtet oder sonstwie durch Programmlogik verändert, bewertet oder bearbeitet werden sollen. Python ist sich dieser Aufgabe bzw. Situation seit langer Zeit bewusst und trägt ihr durch umfangreiche Methoden zur Listenverarbeitung Rechnung. Ob des immensen Umfangs der Features wollen wir uns hier nur Exemplare herausgreifen, die für Umsteiger interessant und im Machine-Learning-Bereich von Bedeutung sind.

Um mit Listen und Co. zu arbeiten, brauchen wir im ersten Schritt Informationen. Da die Interaktion mit echten Dateien an dieser Stelle zu weit führen würde, nutzen wir stattdessen den range-Operator:

mylist = range(2,20)
print(mylist)
for n in mylist:
  print(n)

In Python 3 ist range eine vergleichsweise flexible Methode, die mehr oder weniger beliebige Listen aus Zahlen erstellt. Im Prinzip ist nur ein Endwert erforderlich; wer auch einen Startwert und/oder eine Schrittweite angibt, kann die Ausgabe noch genauer beeinflussen. Interessant ist in diesem Zusammenhang noch das Verhalten des Aufrufs von print, wenn er nur mit dem mylist-Objekt versorgt wird. Abbildung 6 zeigt, was Sie erwarten dürfen.

hanna_pythonml_6.tif_fmt1.jpgAbb. 6: Die Ausgabe der einzelnen Elemente erfolgt in Python nur auf explizite Anweisung

Nun, da wir im Besitz einer Folge bzw. Liste sind, können wir damit beginnen, mit ihr zu rechnen. Die mit Abstand einfachste Rechenmethode ist dabei map. Sie weisen den Interpreter damit dazu an, eine angelieferte Funktion auf alle Elemente der Liste anzuwenden und danach eine neue Liste bzw. ein Iterable zu retournieren, in dem die Ergebnisse der jeweiligen Elemente bereitliegen. Ein Beispiel wäre die Verdoppelung der Werte, die sich mit folgendem Code bewerkstelligen lässt:

def arbeiter(x):
  return x*2
mylist = range(2,20)
values = map(arbeiter, mylist)
for n in values:
  print(n)

Die map-Funktion nimmt als ersten Parameter ein Lambda entgegen. Wer bisher mit Microsoft’schen Programmiersprachen programmiert hat, weiß instinktiv, was sich dahinter verbirgt: Eine Art Funktions-Pointer, der die Struktur der anzulegenden Funktion vorgibt, Entwicklern beim Anlegen sonst aber freie Hand lässt.

Wer das vorliegende Programm ausführt, sieht in der Ausgabe eine Verdoppelung der von Range generierten Werte. Daraus folgt, dass der map-Befehl die Funktion arbeiter auch wirklich auf alle Elemente von mylist angewendet und das resultierende Feld im Wert value zurückgeliefert hat. Die Ausgabe unter Nutzung einer for-Iteration erfolgt dann nach dem von weiter oben bekannten Schema.

Möchte man die angelieferten Werte bei Nichtgefallen verwerfen, bietet sich stattdessen die Methode filter an. Zu guter Letzt gibt es auch noch reduce. Die in Python 3.0 aufgrund extremer Unbeliebtheit bei Guido van Rossum nicht mehr nativ implementierte Funktion erlaubt das Zusammenfassen von Werten. Weil es sich dabei um ein interessantes Beispiel handelt, hier noch eine kurze Implementierung:

import functools
def arbeiter(x, x2):
  return x+x2
mylist = range(2,20)
value = functools.reduce(arbeiter, mylist)
print(value)

Die an reduce übergebene Lambdafunktion nimmt anstatt eines alleinstehenden Parameters derer zwei auf. Der erste ist beim ersten Durchlauf der Funktion der des ersten Felds, während der zweite Parameter immer mit dem Wert des darauffolgenden Felds befüllt wird. Interessant wird es ab dem zweiten Durchlauf: Der erste Parameter, der an die zweite Invokation des Arbeiters übergeben wird, ist der Rückgabewert des ersten. Der zweite Parameter kommt aus dem dritten Feld et cetera. Im vorliegenden Beispiel entsteht auf diese Art und Weise eine bequem berechnete Summe aller im Feld befindlichen Zahlen.

Angemerkt sei, dass der hier verwendete dedizierte Import des Moduls nur in Python 3 erforderlich ist, in 2.X war die Funktion Teil des Hauptmoduls. Als Alternative hierzu gibt es das Konzept der Listenabstraktion, die auch unter dem englischen Namen List Comprehension bekannt ist. Es handelt sich dabei um eine aus der Mengenlehre abgeleitete Methode, um schnell und effizient mit Listen zu arbeiten. Als Beispiel hierfür dient der folgende Code, der im interaktiven Betrieb eine Sequenz von Quadratzahlen erzeugt:

>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Unter Nutzung einer Comprehension lässt sich diese Aufgabe wesentlich verkürzen:

squares = [x**2 for x in range(10)]

Über die Frage, ob man Comprehensions dem Aufrufen von reduce() vorziehen sollte, lässt sich hervorragend diskutieren.

Bibliotheken sparen Zeit

Auch wenn es über die Syntax von Python noch vieles zu berichten gäbe, müssen wir auf einen weiteren wichtigen Aspekt des Ökosystems eingehen: die Vielzahl von Bibliotheken.

Eine besondere Rolle spielt dabei das SciPy-Projekt, dessen Bibliotheksschatz in Abbildung 7 kurz zusammengefasst wird. Sowohl NumPy als auch SciPy zählen als Quasistandardbibliotheken, die auf so gut wie jeder Python-Installation leicht herunterladbar sind. Sie geben Entwicklern potente mathematische Verfahren an die Hand, deren Implementierung und/oder Lizenzierung bis vor einigen Jahren Tausende von Euros kostete.

hanna_pythonml_7.tif_fmt1.jpgAbb. 7: Das SciPy-Projekt bietet neben der namensgebenden Library auch andere Bibliotheken an

Zur praktischen Nutzung ist es empfehlenswert, eine ganze Gruppe von Utilities herunterzuladen. Bei aktiviertem Virtual Environment reicht hierzu folgende Kommandosequenz:

sudo apt-get install libatlas-base-dev gfortran
sudo apt-get build-dep python-numpy python-scipy python3-numpy python3-scipy
python -m pip install numpy scipy matplotlib

Fehler vermeiden

Bei komplexen Bibliotheken ist es manchmal sinnvoller, eine globale Installation zu befehligen. Die vom Anbieter der Distribution bereitgestellten Pakete sind zwar so gut wie immer veraltet, sind dafür aber präkompiliert.

Alternativ kann man auch eine globale Installation durchführen. Diese erweist sich allerdings als haarig, weil sowohl NumPy als auch SciPy permanenter Entwicklung unterliegen. Installiert ein anderer Nutzer oder ein anderes Programm eine aktuellere oder ältere Version, kann es vorkommen, dass das eigene Programm die Arbeit einstellt. PIP ist jedenfalls eine Art Paketmanager für Python-Bibliotheken [3].

Die Installation von Fortran und libatlas ist keine Marotte des Autors: NumPy und SciPy bringen jede Menge nativen Code mit, der die Performance des Systems optimiert und vor der Einsatzfähigkeit des Environments kompiliert werden muss. Zur Demonstration der Möglichkeiten des Pakets wollen wir ein Programm realisieren, das ein Tortendiagramm auf den Bildschirm bringt. Eine naive Implementierung sieht man in Listing 1.

Listing 1

import matplotlib.pyplot as plt
labels = 'A', 'B', 'C', 'D'
sizes = [15, 30, 45, 10]
explode = (0, 0.1, 0, 0)
fig1, ax1 = plt.subplots()
ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', shadow=True, startangle=90)
ax1.axis('equal')
plt.show()

Im Interesse der Vollständigkeit sei angemerkt, dass wir uns an dieser Stelle auf haariges Terrain begeben. Das genaue Verhalten von matplotlib ist von Workstation zu Workstation unterschiedlich. Es kann sein, dass einige der hier beschriebenen Probleme auf Ihrem PC bzw. in Ihrer Python-Distribution nicht auftreten.

Aus programmiertechnischer Sicht findet sich hier nicht viel Neues: Wir legen ein Tupel und eine Liste mit Informationen an, die das Verhalten der Diagramm-Engine beeinflussen. Im nächsten Schritt schreiben wir einige Werte ein. pie ist hierbei insofern interessant, als es nur vergleichsweise wenige fixe Parameter verlangt, die genauere Parametrierung des Verhaltens erfolgt über Dutzende optionale Parameter, die man nur bei Bedarf angeben muss. Der (laut Dokumentation) blockierende Aufruf von show sollte in der Theorie dann dafür sorgen, dass das Diagramm am Bildschirm erscheint.

Auf der Workstation des Autors ist das nicht der Fall. Das Programm läuft zwar problemlos durch, beendet sich aber, ohne dass das Diagramm am Bildschirm erscheint (Abb. 8). Die Ursache für das seltsame Verhalten ist, das matplotlib die Aufgabe, wie in Abbildung 9 gezeigt, in zwei Teile aufteilt. Diese Vorgehensweise ist übrigens kein Spezifikum, sondern findet sich auch in vielen anderen Python-Bibliotheken.

hanna_pythonml_9.tif_fmt1.jpgAbb. 8: Das Tortendiagramm erscheint am Bildschirm
hanna_pythonml_8.tif_fmt1.jpgAbb. 9: Das Einführen eines plattformspezifischen Backend-Objekts gilt in der Welt von Python als „sauber“

Wer seine Bibliothek aus den Paketquellen der jeweiligen Distribution bezieht, hat – wie im Kasten erwähnt – kein Problem. Da wir per pip installiert haben, müssen wir im ersten Schritt für die Bereitstellung eines Backends sorgen. Als Erstes wechseln wir in den interaktiven Modus, wo wir die Bibliothek nach dem gerade aktivierten Backend befragen:

(workspace) tamhan@TAMHAN14:~/suspython/workspace$ python
. . .
>>> import matplotlib
>>> matplotlib.get_backend()
'agg'

Zur Lösung des Problems müssen wir eine Gruppe zusätzlicher Bibliotheken herunterladen. Wir wollen hier auf den TK-GUI-Stack setzen, um Zeit zu sparen:

sudo apt-get install tcl-dev tk-dev python-tk python3-tk

Die eigentliche Einbindung ist einfach – wichtig ist nur, dass der Aufruf von use() vor dem Laden des pyplot-Moduls passieren muss:

import matplotlib
matplotlib.use('tkagg')
import matplotlib.pyplot as plt
 
labels = 'A', 'B', 'C', 'D'
. . .
plt.show()

Fazit

Wer von C zu Java, von Java zu C oder von C zu C# umsteigt, findet im Großen und Ganzen Bekanntes vor. Python tanzt insofern aus der Reihe, als sowohl in Sachen Syntax als auch in Sachen Sprachumfang Ungewohntes auftaucht: Vieles, was in anderen Programmiersprachen in Form von Bibliotheken vorliegt, ist hier Teil der Sprache.

Die vergleichsweise chaotische Weiterentwicklung sorgt dafür, dass auch exotischere Programmierkonzepte schnell von der Community absorbiert werden. Daraus folgt, dass das Produkt die Rolle eines Schmelzkessels einnimmt, in dem sich alte und neue Ideen vermischen.

Die Hysterie wegen des Weggangs von Guido van Rossum ist in meinen Augen unnötig: Python war vorher schon chaotisch und wird in der Zukunft nicht plötzlich am Chaos scheitern. Über die Frage, ob man sich den Wahnsinn antun soll, kann man heute nur noch leidlich diskutieren. Es gibt Situationen, in denen die Bibliotheken tausende von Mannstunden einsparen. Man sollte beim Kauf des Lehrbuchs offen sein und der Umgebung eine Chance geben – glauben Sie meiner Wenigkeit, dass Sie den Aufwand nicht bereuen werden.

hanna_tam_sw.tif_fmt1.jpgTam Hanna befasst sich seit der Zeit des Palm IIIc mit der Programmierung und Anwendung von Handcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenewsdienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.

Mail
Tam Hanna

Tam Hanna befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenewsdienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.