Wir beginnen das Kennenlernen von Python mit einem simplen Programm, nämlich wie traditionell in den allermeisten Büchern zum Programmierenlernen mit »Hello World«, der Ausgabe eines Grußes auf der Konsole. Das haben wir schon in etwas komplizierterer Form in der Einleitung gesehen. Wir wollen es aber weiter vereinfachen und damit unsere Entdeckungsreise starten.
Python bietet einen interaktiven Kommandozeileninterpreter, auch Read-Eval-Print-Loop (REPL) genannt. Dieser interaktive Kommandozeileninterpreter erlaubt es uns, kleinere Python-Codeschnipsel auszuprobieren. Das erleichtert das schrittweise Erlernen.
Wir starten den interaktiven Kommandozeileninterpreter mit dem Kommando python (Windows) bzw. python3 (macOS) wie folgt:
$ python3
Python 3.11.2 (v3.11.2:878ead1ac1, Feb 7 2023, 10:02:41) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
Neben dem Hinweis auf die Version zeigt der Prompt >>> an, dass wir nun Python-Befehle eingeben können. Probieren wir es gleich einmal aus:
>>> print("Hello World from Python!")
Hello World from Python!
Herzlichen Glückwunsch zur ersten Programmausgabe mit Python. Die eingebaute Funktionalität print() ermöglicht Ausgaben auf der Konsole. Derartige Aktionen werden durch sogenannte Funktionen bereitgestellt, in diesem Fall durch die Funktion print(), der man in runden Klammern einen Text eingerahmt von Anführungszeichen übergibt.
Selbst wenn es in diesem Schnelleinstieg-Kapitel ein klein wenig anspruchsvoller wird, keine Sorge, Sie müssen noch nicht alle Details verstehen – wir werden diese im Verlauf dieses Buchs noch ausführlich besprechen. Jetzt geht es zunächst um die ersten Schritte und ein initiales Gespür. Tippen Sie die Beispiele einfach ab und wenn Sie sich sicher fühlen, dann variieren Sie diese auch gerne ein wenig.
Wir haben bereits einen netten Gruß auf der Konsole ausgeben können. Das war schon ein recht guter Anfang. Allerdings wäre es etwas eintönig, nur Texte ohne Variation auszugeben. Um den Gruß anzupassen oder Berechnungen ausführen zu können, lernen wir nun Variablen kennen.
Übrigens hätte man die Ausgabe sogar noch etwas kürzer erhalten können – allerdings ohne die wichtige Funktion print() kennenzulernen:
>>> "Hello World from Python!"
'Hello World from Python!'
Stellen wir uns vor, wir wollten einige Eigenschaften einer Person mit Python verwalten, etwa Name, Alter, Gewicht, Wohnort, Familienstand usw. Dabei helfen uns Variablen. Diese dienen zum Speichern und Verwalten von Werten. Dabei gibt es unterschiedliche Typen (auch Datentypen genannt), etwa für Texte, Zahlen und Wahrheitswerte. Wir schauen uns dies zunächst einmal einführend an und erweitern dann unser Wissen Schritt für Schritt, beispielsweise um ergänzende Infos oder Aktionen mit den Variablen. Zum Einstieg betrachten und nutzen wir folgende Typen:
Achtung: Weil Python die amerikanische Notation nutzt, werden als Dezimaltrenner Punkte statt Kommata verwendet. Somit muss es im Python-Programm 72.71 oder -1.357 heißen.
In Python erzeugen wir eine Variable, indem wir zunächst den Namen und danach eine Wertzuweisung nach folgendem Muster (auch Syntax genannt) nutzen:
variablenname = wert
Schauen wir uns ein paar Beispiele an:
>>> age = 18
>>> the_answer = 41
>>> simple_pi = 3.1415
>>> name = "Michael"
>>> one_million = 1_000_000
>>> the_answer = 42
>>> is_valid = True
Für das Beispiel mit einer Million sehen wir die Angabe von Unterstrichen, die sich gut zur Separierung, hier von Tausendersegmenten, eignen. Zwar habe ich es für the_answer gerade nur angedeutet, aber einer Variablen kann im Programmverlauf mehrmals ein anderer Wert zugewiesen werden. Denken Sie etwa an einen Punktestand in einem Spiel, einen Temperaturverlauf pro Tag und eine Variable temperature oder aber die Modellierung der Tageszeit von Morgens, Mittags, Nachmittags, Abends und Nachts. Für derart in sich abgeschlossene Wertebereiche existieren Aufzählungen, die wir in Abschnitt 6.5 kennenlernen werden.
Den aktuellen Wert einer Variablen können wir durch Eingabe des Namens in der Konsole abfragen bzw. auslesen.
>>> age
18
>>> name
'Michael'
>>> one_million
1000000
Alternativ können wir die bereits kurz vorgestellte Funktion print() nutzen, die einzelne Werte ausgibt:
>>> print(age)
18
>>> print(name)
Michael
>>> print(one_million)
1000000
Wir erkennen in beiden Fällen, dass die Simulation der Tausendertrennzeichen nur bei der Definition zum Tragen kommt, Python die Werte jedoch ohne diese Zeichen speichert.
Tatsächlich sind die Typen von Variablen in Python nicht direkt sichtbar, aber sie lassen sich mit der eingebauten Funktion type() folgendermaßen abfragen:
>>> type(age)
<class 'int'>
>>> type(simple_pi)
<class 'float'>
>>> type(name)
<class 'str'>
>>> type(is_valid)
<class 'bool'>
Neben den kurz erwähnten Typnamen sehen wir weitere Informationen wie class, die hier nicht relevant sind und erst später in Kapitel 4 besprochen werden.
Python ist eine sogenannte dynamisch typisierte Sprache. Das bedeutet, dass sich zum einen der Typ ausgehend vom Wert ergibt und sich der Typ zum anderen sogar im Programmverlauf ändern kann, etwa von float
>>> age = 50.25
>>> type(age)
<class 'float'>
auf str durch eine entsprechende Zuweisung eines textuellen Werts:
>>> age = "Mittelalt"
>>> type(age)
<class 'str'>
Allerdings ist eine derartige Wiederverwendung kein guter Stil und kann zu Missverständnissen und Fehlverwendungen führen. Das gilt insbesondere, wenn im zeitlichen Verlauf statt einer Zahl ein Text gespeichert wird und somit der Typ wechselt.
Gelegentlich sind Ganzzahlen statt Gleitkommazahlen als Ergebnis gewünscht. Liefert eine Berechnung jedoch Gleitkommazahlen, so können wir mit dem sogenannten Casting arbeiten: Durch die Angabe typ(wert), wobei typ für str, int usw. steht, lässt sich ein Wert in den angegebenen Typ umwandeln. Nachfolgend nutzen wir int, um eine Gleitkommazahl in eine Ganzzahl zu verwandeln, wodurch die Nachkommastellen wie gewünscht abgeschnitten werden. Somit wird aus 3.1415 der Wert 3:
>>> int(3.1415)
3
Bei diesen Casts ändert sich insbesondere der Typ. In unserem Beispiel war das Abschneiden der Nachkommastellen ein schöner und gewünschter Nebeneffekt.
Schauen wir uns nun noch ein paar Details im Zusammenhang mit der Funktion print() an, die einzelne Werte ausgibt – mehrere textuelle Werte kann man mit + verknüpfen und mit upper() in Großbuchstaben wandeln:
>>> print(age)
18
>>> print("Hello " + name.upper())
Hello MICHAEL
>>>
Auf ein Detail möchte ich nochmals hinweisen: Bei print() handelt es sich um eine in Python integrierte sogenannte Funktion, man spricht auch von Built-in-Funktion. Diese dienen dazu, gewisse Aktionen standardisiert bereitstellen zu können. Funktionen schauen wir uns dann in Abschnitt 2.5 genauer an.
Besonderheit: Zahlen und Texte gemischtBislang scheint die Ausgabe mit print() recht eingängig. Was passiert aber, wenn wir Zahlen und Texte gemischt ausgeben wollen? Das wäre etwa der Fall, wenn wir die meisten der bisher definierten Variablen in eine Konsolenausgabe integrieren wollen.
Betrachten wir zunächst ein vereinfachtes Beispiel bei dem wir – wie möglicherweise von anderen Programmiersprachen gewohnt – die auszugebenden Bestandteile mit + verbinden:
>>> anzahl = 3
>>> print("Bitte " + anzahl + " Mal klingeln!")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
Das führt zu einem Programmabbruch. Wir erkennen hier, dass nur textuelle Bestandteile mit + verknüpft werden können – ein simpler Cast mit str() löst das Problem:
Weil die Verknüpfung von textuellen Bestandteilen zur Ausgabe recht gebräuchlich ist, ist eine Alternative in Python integriert: An print() kann man eine kommaseparierte Folge von Werten übergeben, die dann automatisch in Strings umgewandelt und mit einem Abstand von einem Leerzeichen ausgegeben werden:
>>> print("Hello", name, "the answer is", the_answer, "and not", age)
Hello Michael the answer is 42 and not 18
Aber Moment: Wo sind denn die anfangs erwähnten Typen? Darauf kommen wir zurück, nachdem wir uns die Konsolenausgabe etwas genauer angeschaut haben.
Besonderheit: SeparatorWenn die Werte nicht mit einem Abstand von einem Leerzeichen ausgeben werden sollen, sondern gegebenenfalls direkt nacheinander oder mit speziellen Trennzeichenfolgen, bietet print() den Parameter sep:
>>> print("Hello", name, "no_space", "in_between", sep="")
HelloMichaelno_spacein_between
>>> print("Important", "Message", "INFO", sep=" --- ")
Important --- Message --- INFO
Besonderheit: ZeilenumbruchBislang werden die Ausgaben mit print() automatisch mit Zeilenumbruch beendet. Wenn man allerdings mehrere Werte hintereinander in einer Zeile darstellen möchte, dann ist das Standardverhalten unpraktisch. Dieses lässt sich mit Angabe eines leeren Endezeichens in Form von end="" abwandeln – dadurch erfolgt auf der Konsole kein Zeilenumbruch mehr, weshalb die Eingabeaufforderung (>>>) direkt nach dem letzten ausgegebenen Zeichen steht:
>>> print("Hello " + name, end="")
Hello Michael>>>
Zur Ausgabe von Werten mit print() müssen diese in eine textuelle Form überführt werden. Das geschieht automatisch, wenn man die zuvor gezeigte kommaseparierte Variante nutzt. Alternativ kann man die Bestandteile durch einen Aufruf der Python-Standardfunktionalität str() in einen String wandeln und dann mit + verknüpfen. Dabei muss man aber auf die korrekte Angabe der Leerzeichen zum Abstand achten.
Egal welche der Varianten man wählt, alle haben so ihre Tücken. Als Abhilfe bietet Python zur formatierten Aufbereitung diverse Möglichkeiten.
>>> print("Hello %s the answer is %d and not %d" % (name,
the_answer, age))
Hello Michael the answer is 42 and not 18
>>>
>>> print("Hello {} the answer is {} and not {}".format(name,
the_answer, age))
Hello Michael the answer is 42 and not 18
>>>
>>> print(f"Hello {name} the answer is {the_answer} and not {age}")
Hello Michael the answer is 42 and not 18
Zunächst sehen wir die Variante mit %, dann {} in Kombination mit format() sowie schließlich die sogenannten f-Strings, wo man benannte Platzhalter in einem String nutzt, dem ein f vorangestellt ist – Details zu diesen Möglichkeiten erfahren Sie später in Abschnitt 3.1.3.
Ohne es explizit zu erwähnen, haben wir uns bei der Benennung von Variablen an ein paar Regeln gehalten. Zunächst einmal muss jede Variable (und auch die später vorgestellten Funktionen, Methoden und Klassen) durch einen eindeutigen Namen, auch Identifier oder Bezeichner genannt, gekennzeichnet werden.
Bei der Benennung sollte man folgende Regeln und Hinweise beachten:
Übrigens spielt es für Python keine Rolle, ob Sie sehr kurze (i, m, p), kryptische (dpy, mtr) oder aber gut verständliche Namen (days_per_year, max_table_rows) nutzen. Für Sie und das Verständnis beim Nachvollziehen eines Python-Programms macht das aber einen himmelweiten Unterschied. Natürlich sind kurze Variablennamen wie etwa x und y zur Bezeichnung von Koordinaten vollkommen verständlich, aber was sagt beispielsweise ein einzelner Buchstabe m aus? Schauen wir uns ein Beispiel an, das Ihnen das Gesagte verdeutlichen soll:
>>> # Gut verständliche Namen
>>> minutes_per_hour = 60
>>> days_per_year = 365
>>>
>>> # Nicht wirklich verständlich, was die Abkürzungen bedeuten
>>> m = 60
>>> dpy = 365
>>> # ACHTUNG: ziemlich schwierig unterscheidbar
>>> arrival_Time = "16:50"
>>> arrival_time = 16.50
Die letzten beiden Variablendefinitionen sind ziemlich ungünstig, da man sich beim Unterscheiden schwertut. Dadurch kommt es vermutlich leichter zu Fehlverwendungen.
Im Listing sehen wir sogenannte Kommentare. Damit kann man erklärende Zusatzinformationen hinterlegen. In diesem Fall werden mit # einzeilige Kommentare eingeleitet. Alles, was nach dem # bis zum Ende der Zeile folgt, wird bei der Ausführung von Python ignoriert (vgl. Abschnitt 2.7).
Operatoren beschreiben Aktionen (Operationen) zwischen Variablen und/oder Werten. Die Addition ist ein Beispiel, nämlich für den Operator +. Das lässt sich wunderbar mit der Python-Kommandozeile nachprüfen:
>>> sum1 = 200 + 50
>>> sum2 = sum1 + 500
>>> sum3 = sum2 + 750
>>> print("Summen: 1:", sum1, "/ 2:", sum2, "/ 3:", sum3)
Summen: 1: 250 / 2: 750 / 3: 1500
Generell bietet Python folgende Varianten von Operatoren:
Die arithmetischen Operatoren sind uns größtenteils aus der Schule bekannt. Neben den Grundrechenarten Addition, Subtraktion, Multiplikation und Division gibt es noch die Modulo-Berechnung sowie die Potenzierung. Tabelle 2–1 bietet einen Überblick.
Betrachten wir einfache Beispiele für diese Operationen, exemplarisch für Ganzzahlen als Parameter. Beachtenswert dabei ist insbesondere, dass die einfache Division eine Gleitkommazahl als Ergebnis liefert:
>>> print(9 - 7)
2
>>> print(7 + 2)
9
>>> print(7 * 2)
14
>>> print(15 / 4)
3.75
Schauen wir uns ergänzend die in anderen Sprachen (oft) unbekannten Operatoren an, nämlich ** zur Potenzierung und // zur Ganzzahldivision:
>>> print(2 ** 8)
256
>>> print(10 // 7)
1
>>> print(1 + 6 * 7 - 7 // 6)
42
Das Ergebnis der letzten Berechnung könnte Sie vielleicht überraschen. Überlegen wir kurz, um es zu verstehen: In Python gelten genau wie in der Mathematik die Vorrangregeln, also Punkt- vor Strichrechnung. Dadurch kommt es zu folgender Auswertung: 1+(6*7)−1. Damit wir keine Nachkommastellen erhalten, nutzen wir mit // die Ganzzahldivision.
Tab. 2–1: Arithmetische Operatoren
Operator |
Name |
Beschreibung und Beispiel |
+ |
Addition |
Addiert zwei Werte: x + y, z. B. 2 + 5 = 7 |
- |
Subtraktion |
Subtrahiert zwei Werte: x - y, z. B. 9 - 2 = 7 |
* |
Multiplikation |
Multipliziert zwei Werte: x * y, z. B. 2 * 7 = 14 |
** |
Potenzierung |
Potenziert zwei Werte (xy): x "* y, z. B. 2 ** 8 = 256 |
/ |
Division |
Dividiert zwei Werte: x / y, z. B. 15 / 4 = 3.75 |
// |
Ganzzahldivision |
Dividiert zwei Werte ohne Rest: x // y, z. B. 9 // 7 = 1 |
% |
Modulo |
Berechnet den Rest der Division zweier Werte: x % y, z. B. 9 % 7 = 2 |
Bei der Division erinnern wir uns vielleicht an den Spezialfall der verbotenen Teilung durch 0. Das ist in der Mathematik nicht erlaubt bzw. liefert den Wert ∞ (unendlich). Mit diesem Wert kann man aber nicht mehr weiterrechnen. Auch Python verbietet eine derartige Division und reagiert darauf mit dem Auslösen eines ZeroDivisionError. Das Thema Fehlerbehandlung wird später in Kapitel 9 vertieft.
>>> 7.0 / 0.0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: float division by zero
>>> 0.0 / 0.0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: float division by zero
Hier reicht uns erst einmal das Wissen, dass nach einer solchen Fehlersituation die Ausführung eines Python-Programms gestoppt wird. In der Kommandozeile hingegen können Sie nach einem solchen Fehler problemlos weitere Kommandos eintippen.
Schauen wir uns für einige Zahlen noch die Modulo-Berechnung an, die den Divisionsrest ermittelt:
>>> 5 % 1
0
>>> 5 % 2
1
>>> 5 % 3
2
>>> 5 % 4
1
>>> 5 % 5
0
Anstelle der hier gewählten Kurzform können wir natürlich auch erst Zahlen einer Variablen zuweisen und danach dann die Operationen ausführen:
>>> five = 5
>>> remainder = 9 % five
>>> remainder
4
Übrigens …
Der Modulo-Operator arbeitet auch auf Gleitkommazahlen. Wenn man beispielsweise ermitteln möchten, welche Restzeit sich bei Etappen von 45 Minuten und einer Fahrtzeit von 6,5 Stunden ergibt, dann kann der Modulo-Operator gute Dienste leisten:
>>> 6.5 % 0.75
0.5
Bei den Zuweisungsoperatoren denkt man natürlich zunächst an die einfache Zuweisung mit dem Operator =, den wir nun schon einige Male genutzt haben und der sich ziemlich natürlich erschließt: Er weist einer Variablen, hier clicks_per_hour, den rechts hinter dem = stehenden Wert, in diesem Fall 1234567, zu:
>>> clicks_per_hour = 1234567
Nehmen wir nun die Variable x und die Wertzuweisung von 100 als Beispiel:
>>> x = 100
Um einen Wert, etwa 10, hinzuzufügen, können wir Folgendes schreiben:
>>> x = x + 10
>>> x
110
Manchmal möchte man die Addition eines Werts kompakter als beispielsweise
>>> x = x + 10
notieren. Dazu existiert als Kurzform der Additionszuweisungsoperator +=:
>>> x += 10
>>> x
120
Für diese Kurzschreibweise gibt es noch die im Anschluss weiter unten demonstrierten praxisrelevanten Varianten +=, -=, *=, **=, /=, //= und %=, die vorab in der folgenden Tabelle aufgeführt sind.
Tab. 2–2: Zuweisungsoperatoren
Operator |
Name |
Beschreibung |
+= |
Addition |
Addiert zwei Werte: x += y |
-= |
Subtraktion |
Subtrahiert zwei Werte: x -= y |
*= |
Multiplikation |
Multipliziert zwei Werte: x *= y |
**= |
Potenzierung |
Potenziert zwei Werte (xy): x **= y |
/= |
Division |
Dividiert zwei Werte: x /= y |
//= |
Ganzzahldivision |
Dividiert zwei Werte ohne Rest: x //= y |
%= |
Modulo |
Berechnet den Rest der Division zweier Werte: x %= y |
Viele der Operationen verdeutlicht folgendes Beispiel:
>>> counter = 0
>>> counter += 100
>>> counter
100
>>> counter -= 10
>>> counter
90
>>> counter /= 9
>>> counter
10.0
>>> counter *= 7
>>> counter
70.0
>>> counter %= 9
>>> counter
7.0
>>> counter **= 2
>>> counter
49.0
Aus der Mathematik kennen wir diverse Operatoren, um Werte miteinander zu vergleichen. Exakt so funktioniert das auch in Python. Man möchte beispielsweise als wahr oder falsch abbilden können, ob zwei Werte gleich oder größer gleich sind. Für derartige Aussagen dienen die Wahrheitswerte True oder False vom Typ bool.
Tab. 2–3: Vergleichsoperatoren
Operator |
Name |
Beschreibung |
== |
gleich |
x == y |
!= |
ungleich |
x != y |
> |
größer |
x > y |
>= |
größer gleich |
x >= y |
< |
kleiner |
x < y |
<= |
kleiner gleich |
x <= y |
Schauen wir uns wieder eine Abfolge von Aktionen zur Verdeutlichung an:
>>> 7 == 8
False
>>> 7 != 8
True
>>> 7 < 8
True
>>> 7 <= 8
True
>>> 7 > 8
False
>>> 7 >= 8
False
Wir können selbstverständlich Variablen vom Typ bool basierend auf den Ergebnissen erzeugen und abhängig vom Wert gewisse Aktionen ausführen. Dazu werden wir in Kürze Fallunterscheidungen und bedingte Ausführungen einsetzen.
>>> is_equal
False
>>> age = 18
>>> younger_than30 = age < 30
>>> younger_than30
True
Um einfache Bedingungen zu prüfen, haben wir gerade die Vergleichsoperatoren kennengelernt. Oftmals soll nicht nur eine, sondern es müssen mehrere Bedingungen erfüllt sein, oder eine aus mehreren. Mitunter muss auch die Bedingung invertiert werden: Aus größer gleich 18 (>= 18) wird kleiner 18 (< 18). Mithilfe der logischen Operatoren lassen sich diese Aktionen in Python formulieren. Die Operatoren and und or arbeiten auf zwei booleschen Werten (bool), der Operator not auf einem.
Operator |
Name |
Beschreibung |
and |
UND |
True, wenn beide Bedingungen erfüllt sind, ansonsten False |
or |
ODER |
True, wenn mindestens eine der Bedingungen erfüllt ist, ansonsten False |
not |
NEGATION |
True, wenn die Bedingung nicht erfüllt ist, ansonsten False |
Schauen wir uns wieder eine Abfolge von Aktionen zur Verdeutlichung an. Zunächst definieren wir die zwei Ganzzahlvariablen age und points und kombinieren dann einige Abfragen – dabei sehen wir, dass bei and beide Bedingungen erfüllt sein müssen, damit True geliefert wird, und bei or zumindest eine der beiden:
>>> points = 127
>>>
>>> age > 20 and points > 100
True
>>> age > 20 and points > 200
False
>>>
>>> age > 30 or points > 100
True
>>> age > 20 or points > 200
True
>>> age > 30 or points > 200
False
>>>
>>> not points > 500
True
Es lassen sich nicht nur einzelne Werte miteinander verknüpfen, sondern auch ganze Ausdrücke, z. B. zur Prüfung der Möglichkeit, ein weiteres Bier in einer Bar zu bestellen. Dies soll gegeben sein, wenn das Alter >= 18 ist und man zudem noch genug Bares hat oder der Sitznachbar die Runde übernimmt:
one_more_beer = (age >= 18) and (credit > 0 or paid_by_neighbour)
Wenn man eine Variable auf zwei Grenzen prüfen möchte, dann lässt sich das mit zwei Vergleichen kombiniert mit and lösen:
>>> age = 22
>>> age >= 18 and age <= 65
True
In Python gibt es noch eine sehr praktische Besonderheit, die gerade bei Vergleichen auf untere und obere Grenzen recht hilfreich und deutlich besser lesbar ist:
>>> 18 <= age <= 65
True
Unsere bisherigen kleinen Python-Experimente liefen alle Zeile für Zeile von oben nach unten ab. In der Praxis muss man oft Bedingungen prüfen, etwa ob eine Person älter als 18 Jahre ist oder ob die Bestellmenge größer als 100 ist und ein Rabatt gewährt wird.
Zur Formulierung derartiger Bedingungen haben wir gerade verschiedene Operatoren kennengelernt. Um abhängig von deren Auswertung einige Programmzeilen nur bedingt auszuführen, dient die if-Anweisung. Damit kann man steuern, dass nur beim positiven Ausgang der Auswertung die nachfolgenden Anweisungen ausgeführt werden. Um diese bedingt auszuführenden Anweisungen von den restlichen Anweisungen abzugrenzen, bedient man sich sogenannter Blöcke. Im Gegensatz zu vielen anderen Programmiersprachen, die hierzu geschweifte Klammern ({}) nutzen, werden Blöcke in Python durch eine Einrückung nachfolgender Anweisungen bestimmt. Oftmals wird dazu eine Einrückung von 4 Leerzeichen genutzt (laut PEP-8-Empfehlung2 ; zwingend ist lediglich eine konsistente Einrückung). Bitte beachten Sie, dass die Blöcke ausschließlich über die Einrückung definiert werden. Eine Zeile mit einer anderen Einrücktiefe beendet den Block bzw. stellt einen Syntaxfehler dar.
Wir haben nun genug Vorwissen, um eine bedingte Ausführung mehrerer Python-Anweisungen (genauer in Form eines Blocks) formulieren zu können. Dazu verwenden wir das Schlüsselwort if, eine Bedingung, gefolgt von einem Doppelpunkt, sowie einen Block mit den Anweisungen nach folgendem Muster:
>>> if condition:
... Aktionen
Sofern die Bedingung zu True ausgewertet wird, werden die Anweisungen innerhalb des Blocks abgearbeitet. Betrachten wir ein einfaches Beispiel, das prüft, ob die Multiplikation von 6*7 den Wert 42 ergibt, und in dem Fall eine Nachricht ausgibt:
>>> if 6 * 7 == 42:
... print("We found the answer!")
...
We found the answer!
Bei der Eingabe auf der Kommandozeile muss Python das Blockende erkennen. Zum Abschluss drücken Sie bitte zweimal Return – danach kommt es zur Ausführung.
Machen wir es etwas praxisrelevanter:
>>> age = 27
>>> if age >= 18:
... print("You are allowed to drive a car ...")
... print("- in case you have a driving license")
...
You are allowed to drive a car ...
- in case you have a driving license
Anhand der Ausgaben auf der Konsole erkennen wir, dass der Block ausgeführt wurde. Das ist der Fall, weil die Variable age den Wert 27 besitzt und damit der Vergleich if age >= 18 zu True ausgewertet wird.
Oftmals möchte man nicht nur eine Bedingung mit if prüfen, sondern auch auf deren Nichterfüllung reagieren können. Das wird ansatzweise bereits an den vorherigen Beispielen deutlich. Vereinfachen wir das obige Beispiel, indem lediglich eine Wertzuweisung statt einer Konsolenausgabe erfolgt, und ergänzen den Negativfall mit else:
>>> age = 27
>>> if age > 18:
... result = "allowed"
... else:
... result = "NOT ALLOWED"
...
'allowed'
Kurzschreibweise: Ternärer OperatorDiese Art der Abfrage ist etwas lang. Für derart einfache Fallunterscheidungen existiert mit dem sogenannten ternären Operator (vgl. Abschnitt 6.4) eine Kurzschreibweise, die folgende Struktur besitzt:
positiveCase if condition else negativeCase
Damit lässt sich die obige Zuweisung etwas kürzer schreiben:
>>> age = 27
>>> result = "allowed" if age > 18 else "NOT ALLOWED"
>>> result
'allowed'
Oftmals benötigt man neben einer Bedingung mit if noch weitere Prüfungen. Im nachfolgenden Beispiel wollen wir abhängig von einer Uhrzeit einen entsprechenden Gruß aufbereiten, also etwa »Good morning«, wenn es noch nicht 12 Uhr ist, oder aber »Good evening« ab 18 Uhr, aber vor 22 Uhr. Das können wir wie folgt mit if, elif und else in Kombination umsetzen – elif steht für else if in anderen Programmiersprachen.
Wenn der Umfang an abzutippenden Zeilen und Python-Kommandos zunimmt, empfiehlt es sich, auf eine IDE oder einen Texteditor umzusteigen, wie es der folgende Praxistipp thematisiert.
time = 15
if time < 12:
print("Good morning")
elif time < 18:
print("Good afternoon")
elif time < 22:
print("Good evening")
else:
print("Good night")
Good afternoon
Im Beispiel ist die Uhrzeit 15 nicht kleiner als 12, sodass die erste Bedingung False ist. Dagegen ist die zweite Bedingung True, sodass »Good afternoon« ausgegeben wird. Wäre die Uhrzeit nach 18 Uhr und vor 22 Uhr, erhielten wir »Good evening« und ansonsten resultiert die Auswertung in »Good night« als Ausgabe.
Hinweis: Komfortablere Programmeingabe
Für etwas umfangreichere Python-Programme bietet es sich an, in die PyCharm-IDE zu wechseln, weil diese das Editieren von mehrzeiligen Anweisungen komfortabel erlaubt, was eine Schwäche vom Python-Kommandozeileninterpreter ist. Alternativ können Sie die Zeilen in einem Texteditor abtippen und als Datei speichern. Diese lassen sich dann wie in Abschnitt 1.2.5 beschrieben als Skript ausführen. Für den Fall, dass ich einen dieser Bearbeitungsmodi empfehle, sind in den Programmlistings nicht mehr die Indikatoren >>> der REPL gezeigt, sondern lediglich die einzutippenden Kommandos.
Insgesamt bietet Python die folgenden bedingten Anweisungen:
Wir haben schon einiges Basiswissen zusammengetragen. Sie erinnern sich sicher an die schon genutzten Aufrufe der in Python bereits vordefinierten Funktionen print(), type() und str() – man spricht auch von Built-in-Funktionen.
Nehmen wir nun einmal an, wir wollten eine Art Taschenrechner programmieren bzw. in unserem Programm immer wieder ähnliche Berechnungen anstellen. Obwohl es etwa bei einfachen Additionen noch problemlos möglich ist, wiederholt die gleichen Zeilen zu schreiben, ändert sich die Situation bereits bei einer Funktionalität, die ein paar Vergleiche oder andere Aktionen enthält. Um Funktionalität unter einem Namen zu bündeln, können wir dafür benutzerdefinierte Funktionen erstellen.
Eine Funktion ist ein benanntes Stück Sourcecode, also eine Folge von Anweisungen, die nur dann abgearbeitet werden, wenn die Funktion aufgerufen wird. An diese können Sie Eingabedaten, sogenannte Parameter, übergeben. Funktionen werden verwendet, um bestimmte Aktionen auszuführen, und zwar auf wiederverwendbare Art und Weise. Das Ganze besitzt folgende Syntax:
def funktion_name(Parameter1, Parameter2, ...):
Anweisung1
Anweisung2
Definieren Sie eine Funktion also einmal und verwenden Sie die Aktionen viele Male überall dort im Python-Programm, wo dies benötigt wird.
Um eine Funktion, und damit die dort enthaltenen Anweisungen, auszuführen, nutzen wir deren Namen, eine öffnende runde Klammer, optional eine kommaseparierte Liste von Parametern und zum Abschluss eine schließende runde Klammer. Für zwei Parameter sieht das wie folgt aus:
funktion_name(parameter_wert1, parameter_wert2)
Klingt noch etwas abstrakt, im Anschluss machen wir das Ganze konkreter mit der Standardfunktionalität print() und mit drei Parametern, wie wir es ähnlich zuvor schon getan haben:
print("Michael", "likes", "Python")
Greifen wir das Beispiel des einfachen Taschenrechners wieder auf. Schauen wir uns als Beispiele die Funktionen zur Addition und Multiplikation an:
>>> def add(value1, value2):
... return value1 + value2
...
>>> def mult(value1, value2):
... return value1 * value2
...
>>> result = add(7, 2)
>>> mult(result, 3)
27
Genauso wäre es möglich, die in den vorher gezeigten Altersprüfungen genutzte Abfrage als verständliche Funktion bereitzustellen:
>>> def is_adult(age):
... return age >= 18
...
Wir erkennen Folgendes: Funktionen bestehen aus einem Block mit einer oder mehreren Anweisungen. Um beim Aufruf Informationen übergeben zu können, dienen Parameter, die wiederum Typen besitzen. Insgesamt erlauben es uns Funktionen, wiederkehrende Funktionalität zu bündeln und über den Namen anzusprechen, statt die Sourcecode-Zeilen im Programm wiederholen zu müssen.
Aufgerufene Funktionen dienen oftmals dazu, eine Aufgabe auszuführen, die ein Ergebnis berechnet und dieses dem Aufrufer zurückliefert, etwa für eine Addition. Dazu wird das Schlüsselwort return genutzt. Manchmal, etwa wie bei der Funktion print(), erhalten wir keine Rückgabe. In derartigen Fällen gibt es dann allerdings kein return mit Wertangabe, sondern nur eine oder mehrere Anweisungen:
>>> def greet(name):
... print("Hello", name)
...
>>> greet("Michael")
Hello Michael
Im nachfolgenden Beispiel wollen wir als wiederverwendbare Funktionalität das Minimum von drei Ganzzahlen ermitteln, zu deren Repräsentation wir x, y und z als Parameternamen verwenden.3
def min_of_3(x, y, z):
if x < y:
if x < z:
return x
else:
return z
else:
if y < z:
return y
else:
return z
Mal ganz ehrlich: Sind Sie allein beim Betrachten sicher, dass sich dort kein Flüchtigkeitsfehler eingeschlichen hat? Zumindest der Programmablauf (Kontrollfluss) ist doch ein wenig unübersichtlich. Einfacher geht das Ganze, wenn man vordefinierte Bausteine verwendet, was wir uns im Anschluss anschauen. Zuvor probieren wir die obige Implementierung der Funktionalität einmal für verschiedene Wertekombinationen aus:
min_of_3(1,2,3)
1
min_of_3(11,2,3)
2
min_of_3(11,22,3)
3
Wir könnten beispielsweise mit folgender Implementierung der Funktion min_of() zur Berechnung des Minimums für zwei Zahlen starten:
def min_of(x, y):
if x < y:
return x
else:
return y
Tatsächlich ließe sich das mit dem auf Seite 63 kurz vorgestellten ternären Operator (Details finden Sie dann in Abschnitt 6.4) noch kompakter schreiben:
def min_of_shorter(x, y):
return x if x < y else y
Wenn man diese Bausteine besitzt, kann man die ursprüngliche Funktion min_of_3() und das komplizierte if-Gebilde folgendermaßen vereinfachen:
def min_of_3(x, y, z):
return min_of(x, min_of(y, z))
Überprüfen wir kurz, ob die gleichen Werte wie zuvor geliefert werden:
min_of_3(1,2,3)
1
min_of_3(11,2,3)
2
min_of_3(11,22,3)
3
Perfekt, funktional ist das gleichwertig, aber es war durch den Einsatz der Funktionen viel weniger Aufwand und ist deutlich kürzer. Auch die Verständlichkeit ist um Längen besser, da wir nicht mehrfach verschachtelte if-Gebilde nachvollziehen müssen.
Nebenbei ist es immer eine gute Idee, sich ein wenig in den Python-Basisbausteinen umzuschauen, weil es dort eine Vielzahl an nützlichen Funktionalitäten bereits vordefiniert gibt. In unserem Fall könnten wir die in Python vordefinierte Funktion min() zur Berechnung des Minimums nutzen.
Wir haben bereits die praktische Funktion print() kennengelernt. Betrachten wir, was man beispielsweise für mathematische Funktionalitäten sonst noch so in Python findet:
Mit den bereits in Python vordefinierten Funktionen können wir die Implementierung von min_of_3_python_built_in() wie folgt vereinfachen – wir müssen nicht mal eine Funktion min() selbst schreiben, sondern nutzen die vordefinierte aus Python:
def min_of_3_python_built_in(x, y, z):
return min(x, min(y, z));
Hier mag der Vorteil noch nicht ganz so klar werden, da es sehr einfach war, eine min()-Funktion zu implementieren. Je komplexer die Funktionalität jedoch wird, desto eher schleichen sich auch Fehler ein. Praktischerweise sind die Bausteine von Python jedoch gründlich getestet.
Während der Abarbeitung von Python-Programmen kann es zu Fehlersituationen kommen. Zu deren Beschreibung dienen sogenannte Exceptions. Sie werden etwa intern ausgelöst, wenn eine Datei oder eine Position in einem String nicht existiert, auf die man zugreifen möchte, oder wenn eine mathematische Berechnung ungültig ist.
Schauen wir uns zum ersten Verständnis die Umwandlung zweier Texte in den Typ int an, einmal rein mit Ziffern und einmal für Buchstaben und Ziffern gemischt:
>>> int("7271")
7271
>>> int("E605")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'E605'
Die erste Umwandlung ist problemlos möglich, aber der Versuch, den Text E605 in eine Ganzzahl zu wandeln, führt (selbstverständlich) zu einem Problem. Das resultiert in einem Programmabbruch und produziert einen sogenannten Traceback, eine Protokollierung der Aufrufhierarchie bis zur fehlerhaften Programmstelle. Dort sehen wir, dass es zu einem in Python vordefinierten ValueError kommt. Sind die Anweisungen in ein Programm eingebettet, so werden die Zeilen nach einer solchen Fehlersituation ohne spezielle Behandlung nicht mehr ausgeführt und die Abarbeitung unseres Programms gestoppt. Mehr Details erfahren Sie in Kapitel 9. Auf der Kommandozeile kann man dagegen problemlos weitere Anweisungen eingeben.
In den vorangegangenen Beispielen haben wir mitunter bereits Kommentare genutzt, hier möchte ich ein paar Varianten davon zeigen.
Ein Kommentar ist ein Teil Ihres Python-Programms und sollte hilfreiche Anmerkungen enthalten, wird jedoch bei der Ausführung ignoriert. In Python markiert # den Beginn einer Kommentarzeile. Wenn mehrere aufeinanderfolgende Zeilen kommentiert werden sollen, muss das Symbol # an den Anfang jeder Zeile gesetzt werden. Man kann auch mehrzeilige, in drei Anführungszeichen eingerahmte Strings (vgl. Abschnitt 3.2.3) als Kommentar nutzen:
>>> # Kommentar
>>> age = 11 # Zeilenkommentar
>>>
>>> # Blockkommentar 1
>>> # Blockkommentar 2
>>> # Blockkommentar 3
>>>
>>> """
... Kommentarzeile 1
... Kommentarzeile 2
... KommentarZeile 3
... """
'\nKommentarzeile 1\nKommentarzeile 2\nKommentarZeile 3\n'
Bislang haben wir lediglich ein paar Anweisungen innerhalb des Python-Kommandozeileninterpreters ausgeführt. Dieser ist wunderbar für erste Experimente geeignet. Aber wie gehen wir damit um, wenn wir die erstellten Funktionalitäten später nochmals wiederverwenden möchten? Dazu hatten wir Funktionen kennengelernt. Jedoch gibt es mit unserer bisherigen Arbeitsweise ein kleines Problem: In dem Moment, wo wir den Python-Kommandozeileninterpreter wieder beenden, sind unsere ganzen Mühen futsch. Alle Variablen und Funktionen sind verloren. Das wäre sehr schade, wenn wir gerade beispielsweise eine nützliche Sammlung mathematischer Funktionalitäten oder ein kleines Spiel implementiert hätten.
Wollen wir eine Funktionalität auch später bereitstellen können, so müssen wir diese in einer Datei speichern. Dateien mit Python-Anweisungen nennt man Module. Für Python sollten diese die Endung .py besitzen. Außerdem sollten Modulnamen allgemein in Kleinbuchstaben geschrieben werden. Zur Bearbeitung des Sourcecodes eines Moduls kann man zwar einen Texteditor nutzen, geeigneter ist jedoch eine IDE. Das gilt insbesondere, wenn die Programme umfangreicher werden. Dann bietet es sich an, gewisse Funktionalitäten auf verschiedene Dateien aufzuteilen und semantisch zu gruppieren. Ebenso kann die Motivation sein, praktische Funktionalitäten nicht immer wieder zu kopieren, sondern einmal als Datei bereitzustellen.
Rekapitulieren wir: Eine Datei mit Python-Anweisungen wird als Modul bezeichnet – in der Softwarearchitektur versteht man unter Modulen eigentlich etwas anderes, nämlich in sich abgeschlossene Programmeinheiten mit klaren Schnittstellen und einer sauberen Definition von Abhängigkeiten, aber in Python ist die Begrifflichkeit nun einmal so, wie sie ist.
Python-Programme oder Module können wiederum Funktionalitäten aus anderen Modulen nutzen. Dazu ist ein sogenannter Import nötig. Um in das aktuelle Programm ein externes Modul, also eine andere .py-Datei, zu integrieren, ist folgende Syntax mit import üblich, wobei der Modulname ohne die Endung .py angegeben wird:
import modulname
Das Einbinden anderer Module schauen wir uns am Beispiel des Moduls random zum Erzeugen von Zufallszahlen an. Dort existieren mehrere Funktionen unter anderem randrange(). Probieren wir einmal einen direkten Zugriff aus:
>>> random.randrange(10, 100)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'random' is not defined
Offensichtlich und logischerweise, da bislang noch kein passender Import erfolgt ist, können wir nicht auf die Funktionalität zugreifen. Es erfordert folgenden Import:
>>> import random
Danach rufen wir die Funktionen auf – außerdem hat man durch den Import auch Zugriff auf andere Funktionen wie choice() zur zufälligen Auswahl aus einer Werteangabe:
>>> random.randrange(10, 100)
24
>>> random.randrange(10, 100)
63
>>> random.choice(["Napoli", "Funghi", "Diavolo"])
'Diavolo'
Möchte man den Import auf eine Funktionalität einschränken und darauf direkt mit dem Namen ohne Angabe des Modulnamens zugreifen, so schreibt man Folgendes:
>>> from random import randrange
>>> randrange(10, 100)
89
Ein direkter Aufruf von choice() ist dann allerdings nicht möglich, weil der Name ja nicht importiert wurde:
>>> choice(["Napoli", "Funghi", "Diavolo"])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'choice' is not defined
Es ist aber auch problemlos möglich, mehrere Funktionalitäten durch kommaseparierte Angabe einzubinden, hier, um zusätzlich choice() zur Verfügung zu stellen:
>>> from random import randrange, choice
>>>
>>> choice(["Napoli", "Funghi", "Diavolo"])
'Funghi'
Fassen wir kurz zusammen: Ein Modul entspricht in Python einer Datei, die Python-Kommandos, also beispielsweise Variablendefinitionen, Funktionen und weitere Anweisungen, enthält. Der Modulname ergibt sich aus dem Dateinamen, allerdings ohne die Endung .py.
Praktischerweise kann man innerhalb eines Moduls auf den Modulnamen über die Variable __name__ zugreifen, die automatisch bei einem Import zugewiesen wird.
Wenn Module von anderen Modulen importiert werden, dann werden die dort enthaltenen Anweisungen einmalig von oben nach unten abgearbeitet. Dadurch werden alle darin aufgelisteten Funktionen zum Zeitpunkt des Importierens aufgerufen und ausgeführt.
Für den Fall, dass eine Python-Datei mit dem Kommandozeileninterpreter aufgerufen oder aus der IDE gestartet wird, so enthält __name__ den Namen __main__. Das ist für folgende Situation nützlich: In größeren Programmen ist das Verhalten, dass die Python-Anweisungen eines Moduls beim Importieren einmal abgearbeitet werden, in der Regel hinderlich. Soll also die Funktionalität bei einem Import nicht aufgerufen werden, so kann man dies durch folgenden Trick erreichen:
def main():
# Hauptprogramm
if __name__ == "__main__":
main()
Dies ist insbesondere dann nützlich, wenn Module eigenständig aufgerufen oder aber in ein anderes Programm eingebettet werden sollen.
In Python sind mit Listen (list), Mengen (set) und Dictionaries (dict) die in der Programmierpraxis gebräuchlichsten Datenstrukturen direkt in die Sprache integriert, sie sind also built-in. Das gilt ebenfalls für Tupel (tuple).
Man spricht in dem Zusammenhang auch von Collection-Literalen, weil man die Datenstrukturen einfach so textuell in eckigen bzw. geschweiften Klammern mit direkter Werteangabe notiert, wie wir es im Anschluss etwas genauer sehen werden.
Sollen mehrere Werte, etwa Namen, Städte, Aufgaben, Kunden usw. verwaltet werden, dann sind Listen oftmals eine gute Wahl – aus dem realen Leben kennen wir etwa Einkaufslisten, To-do-Listen, Wunschlisten und die guten Vorsätze für das neue Jahr :-)
In Python können Listen mehrere Werte speichern, die zudem eine Reihenfolge besitzen und auch Duplikate enthalten können. Listen werden in eckigen Klammern in Form einer kommaseparierten Aufzählung von Werten wie folgt definiert:
>>> names = ["Tim", "Michael", "Tom", "Michael"]
Alternativ kann man eine leere Liste mit folgenden beiden Varianten erzeugen:
>>> empty_list_1 = []
>>> empty_list_2 = list()
Für Listen erfolgt der lesende und schreibende Zugriff über die Angabe eines Index in eckigen Klammern – die vorderste Position besitzt den Wert 0:
>>> names[0]
'Tim'
>>> names[2] = ">>Tommy<<"
>>> names
['Tim', 'Michael', '>>Tommy<<', 'Michael']
Die Anzahl an Elementen ermittelt man durch Aufruf von len(). Einzelne Werte kann man mit append() hinzufügen – um eine Liste von Werten zu ergänzen, nutzt man +=:
>>> len(names)
4
>>> names.append("Mike")
>>> names
['Tim', 'Michael', '>>Tommy<<', 'Michael', 'Mike']
>>> names += ["Willi", "Yves"]
>>> names
['Tim', 'Michael', '>>Tommy<<', 'Michael', 'Mike', 'Willi', 'Yves']
Tupel dienen dazu, mehrere unterschiedliche Informationen zu einer Einheit zu bündeln. Tupel sind damit ähnlich zu Listen, jedoch werden die Werte in runden Klammern aufgeführt und die Intention ist eine andere, nämlich die eines fixen Datenbestands fixer Größe – man kann Tupel grob als unveränderliche Listen ansehen.
>>> pizza_top_3 = ("diavolo", "napoli", "funghi")
Man kann ein Tupel auch aus einer Liste durch Aufruf von tuple() erzeugen:
>>> pizza_top_3 = tuple(["diavolo", "napoli", "funghi"])
>>> pizza_top_3
('diavolo', 'napoli', 'funghi')
Führen wir zum besseren Verständnis noch ein paar Aktionen aus, nämlich einen indizierten Zugriff, eine Ausgabe mit print() und eine Wertzuweisung:
>>> print(pizza_top_3[0])
diavolo
>>>
>>> print(pizza_top_3)
('diavolo', 'napoli', 'funghi')
>>>
>>> pizza_top_3[0] = "surprise"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Das führt zu den obigen Ausgaben, die insbesondere zeigen, dass man die Werte innerhalb eines Tupels nachträglich nicht mehr ändern kann, weshalb die Wertzuweisung in einem Fehler resultiert.
Tupel weisen eine syntaktische Besonderheit auf. Man kann nämlich einfach auf der linken Seite mehrere Variablen kommasepariert auflisten und diese werden dann automatisch mit den von der Position korrespondierenden Werten aus dem Tupel belegt. Dieses praktische Verhalten nennt sich Tuple Unpacking:
>>> # Tuple Unpacking => elementweise in Variablen übertragen
>>> first, second, last = pizza_top_3
>>> print(first, second, last)
diavolo napoli funghi
Ebenso ist es möglich, einer Variablen eine kommaseparierte Folge von Werten zuzuweisen, wodurch die Variable zu einem Tupel wird. Hier spricht man von Tuple Packing:
>>> first_primes = (2, 3, 5, 7)
>>> print(first_primes)
(2, 3, 5, 7)
>>>
>>> # Tuple Packing => Werte als Tupel einer Variablen zuweisen
>>> first_primes_auto = 2, 3, 5, 7
>>> print(first_primes_auto)
(2, 3, 5, 7)
Hinweis: Besonderheit zu viele Elemente
Was passiert eigentlich, wenn rechts mehr Werte als links Variablen aufgeführt sind?
>>> one, two = 1, 2, 3, 4, 5
Das wird von Python erkannt und resultiert in einer Fehlermeldung. Mit folgender Schreibweise kann man eine Hilfsvariable definieren, die die überschüssigen Werte in Form einer Liste aufsammelt:
>>> one, two, *rest = 1, 2, 3, 4, 5
>>> rest
[3, 4, 5]
Python ermöglicht die Modellierung mathematischer Mengen. Definitionsgemäß besitzen diese weder eine Reihenfolge noch Duplikate. Aus Ersterem folgt, dass man nicht über die Position zugreifen kann. Letzteres führt dazu, dass Duplikate beim Hinzufügen automatisch entfernt werden. Eine Menge von Werten lässt sich mit geschweiften Klammern wie folgt definieren:
>>> fruits = {"Apple", "Ananas"}
>>> fruits.add("Apple")
>>> fruits.add("Banana")
>>> fruits.add("Orange")
Schauen wir uns kurz den Inhalt der Menge an. Zudem prüfen wir mit in, ob ein Element enthalten ist, und ermitteln mit len() die Anzahl an Elementen:
>>> fruits
{'Banana', 'Orange', 'Apple', 'Ananas'}
>>>
>>> "Ananas" in fruits
True
>>> "Melon" in fruits
False
>>>
>>> len(fruits)
4
Die letzte wichtige Built-in-Datenstruktur sind die Dictionaries. Diese bilden Schlüssel auf Werte ab, etwa Namen auf Telefonnummern, Personen auf Hobbys usw. Der Zugriff erfolgt über die schon bekannte eckige Klammersyntax. Im Gegensatz zu Listen und Tupeln nutzt man aber dort den Wert des Schlüssels und keine Indexposition.
Betrachten wir als Beispiel die Abbildung von Namen auf Mobiltelefone. Wie bei Mengen erfolgt hier die Vorgabe von Schlüsseln und Werten in geschweiften Klammern, jedoch mit einem Doppelpunkt zwischen Schlüssel und Wert.
>>> person_phone = {"Tim" : "iPhoneSE2",
... "Michael" : "iPhone 13 Pro",
... "Marc": "iPhone X"}
>>>
>>> person_phone
{'Tim': 'iPhoneSE2', 'Michael': 'iPhone 13 Pro', 'Marc': 'iPhone X'}
>>>
>>> len(person_phone)
3
>>>
>>> person_phone["Marc"]
'iPhone X'
Im folgenden Beispiel sehen wir, dass es problemlos möglich ist, für die Werte wiederum Listen oder andere Container zu verwenden und diese wie für Listen gewohnt mit eckigen Klammern und der Angabe des Schlüssels auszulesen:
>>> person_hobbies = {"Tim" : ["Computer", "Java"],
... "Mike" : ["Java", "Python"],
... "Jim" : ["Swimming"]}
>>>
>>> person_hobbies
{'Tim': ['Computer', 'Java'], 'Mike': ['Java', 'Python'], 'Jim': ['Swimming']}
>>>
>>> len(person_hobbies)
3
>>>
>>> person_hobbies["Mike"]
['Java', 'Python']
Außerdem ist es durch eine einfache Zuweisung möglich, Schlüssel-Wert-Abbildungen hinzuzufügen:
>>> person_hobbies["James"] = ["Movies"]
>>> person_hobbies
{'Tim': ['Computer', 'Java'], 'Mike': ['Java', 'Python'], 'Jim': ['Swimming'], 'James': ['Movies']}
>>>
>>> len(person_hobbies)
4
Das war lediglich ein Schnelleinstieg für den ersten Überblick. Die Thematik vertiefen wir in den Kapiteln 5 und 7.
Mitunter soll ein bestimmter Abschnitt des Sourcecodes mehrmals ausgeführt werden. Das wird mithilfe von Schleifen realisiert. Davon existieren diverse Varianten in Python, die wir nun kennenlernen wollen.
Im Kontext der gleich vorgestellten Schleifen wird ab und an range(startIncl, endExcl) genutzt. Damit lassen sich Wertebereiche in den angegebenen Grenzen generieren. Das Besondere an diesen Wertebereichen ist, dass diese nicht vollständig erzeugt werden, sondern dass sukzessive bei Bedarf neue Werte geliefert werden. Möchte man die Werte des Wertebereichs direkt erstellen, so kann man dies mit einem Aufruf von list() ummanteln:
>>> range(1, 10)
range(1, 10)
>>> list(range(1, 10))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Soll ein Wertebereich von 0 starten, so kann man die untere Grenze weglassen, weil diese standardmäßig 0 ist. Dies ist hier für den Wertebereich 0–5 gezeigt:
>>> list(range(0, 5))
[0, 1, 2, 3, 4]
>>> list(range(5))
[0, 1, 2, 3, 4]
Wenn Sie eine Folge von Anweisungen, also einen Block, eine gewisse Anzahl Mal ausführen wollen, so ist die for-Schleife mit der Angabe eines Wertebereichs per range(startIncl, endExcl) eine gute Wahl. Probieren wir es wieder aus:
>>> for i in range(2, 6):
... print("Durchlauf:", i)
...
Durchlauf: 2
Durchlauf: 3
Durchlauf: 4
Durchlauf: 5
Zunächst wird der Wertebereich vor dem Beginn der Schleife festgelegt und in diesem Fall dort der Variablen i der Startwert (hier i = 2) zugewiesen. Mithilfe des Wertebereichs wird indirekt eine Bedingung ausgedrückt, nämlich in diesem Fall i < 6. Dadurch wird festgelegt, wie häufig die Schleife ausgeführt werden soll (hier: i muss kleiner als 6 sein). Sofern die Bedingung gilt, wird die Zählvariable, hier i, nach jedem Durchlauf automatisch um den Wert 1 erhöht und die Schleife beginnt von vorne. Erreicht die Zählvariable den Grenzwert, wird die Schleife beendet. Die Erhöhung findet erst nach der vollständigen Abarbeitung des Blocks in der Schleife, auch Schleifenkörper oder -rumpf genannt, s tatt.
Für gewisse Anwendungsfälle soll der Schleifenzähler nicht um den Wert 1 erhöht werden, sondern man benötigt mehr Flexibilität, etwa um in 5er-, 10er- oder 100er-Schritten hochzuzählen. Diese Anforderung lässt sich mit Ranges leicht durch einen zusätzlichen Parameter erreichen: range(startIncl, endExcl), stepSize). Nachfolgend wird die Grenze auf 10 erhöht und eine Schrittweite von 3 verwendet – genauso ist es denkbar und einfach möglich, in 2er- oder 10er-Schritten voranzuschreiten.
Mitunter benötigt man auch das Herunterzählen, etwa von 10 auf 0 oder von 3 auf 1. Letzteres implementieren wir durch Umkehren der Bereichsangaben, also die größere Zahl als erster Paramter im Range, und negativer Schrittweite folgendermaßen:
>>> for i in range(3, 0, -1):
... print(i)
...
3
2
1
Gebräuchlicher als eine reine Wiederholung sind Schleifen über Datenbestände, etwa die Elemente einer Liste. Das lässt sich folgendermaßen mit einem indizierten Zugriff realisieren – allerdings ist das in Python stilistisch unschön, da elegantere und im Anschluss vorgestellte Varianten existieren.
>>> names = ["Barbara", "Lilija", "Sophie"]
>>> for i in range(len(names)):
... print(names[i])
...
Barbara
Lilija
Sophie
Nachfolgend möchte ich noch auf eine Python-Besonderheit eingehen, nämlich in einer Schleife keinen Variablennamen anzugeben, sondern _. Damit wird ausgedrückt, dass dieser Wert im weiteren Verlauf nicht mehr benutzt wird:
>>> for _ in range(5):
... print("Again")
...
Again
Again
Again
Again
Again
Interessanterweise könnte man jedoch auch auf den Wert zugreifen – das widerspricht jedoch der eigentlichen Intention:
>>> for _ in range(5):
... print("Again", _)
...
Again 0
Again 1
Again 2
Again 3
Again 4
Das Hantieren mit Indexwerten birgt Fehler. Möchte man nur eine vordefinierte Wertemenge durchlaufen (man spricht auch von Iterieren), so kann man bevorzugt statt eines Wertebereichs auch eine Liste oder eine direkte Angabe von Werten verwenden. Statt der Indexvariablen wird eine Variable mit dem Wert und einer Angabe der zu durchlaufenden Werte genutzt.
Nachfolgend definieren wir eine Liste, die wir nun ohne Index durchlaufen. Dazu dient die Variante mit for und in, wobei man dort eine Variable angibt, die das jeweilige Listenelement pro Schleifendurchlauf enthält:
>>> for current_name in ["Barbara", "Lilija", "Sophie"]:
... print(current_name)
...
Barbara
Lilija
Sophie
Für jeden Wert in der Liste wird dieser zunächst der Variablen current_name zugewiesen. Im Anschluss werden dann die Aktionen des Schleifenrumpfs, hier die Konsolenausgabe mit print(), ausgeführt. Das wiederholt sich, bis alle Werte aus names verarbeitet wurden.
Der Vergleich der indexbasierten und der wertebasierten for-in-Schleife zeigt, dass letztere etwas einfacher zu formulieren ist, keine Indexzugriffe benötigt und dadurch lesbarer ist. Allerdings hat man in dem Fall dann (logischerweise) auch keinen Zugriff mehr auf den Index.
Wir haben nun schon mehrere Arten gesehen, wie wir eine Schleife gestalten können. Folgende Varianten sollten uns mittlerweile geläufig sein:
message = ["Python", "has", "several", "loop", "variants"]
for i in range(len(message)):
print(i, message[i], end=',')
print()
for current_word in message:
print(current_word, end=',')
print()
In diesem Beispiel erinnern wir uns an den Parameter end für print() zur Spezifikation einer Trennzeichenfolge. Dadurch werden die Wörter kommasepariert ohne Zeilenumbruch hintereinander ausgegeben. Das führt zu folgenden Ausgaben:
0 Python,1 has,2 several,3 loop,4 variants,
Python,has,several,loop,variants,
Im ersten Fall haben wir Zugriff auf den Index und können somit die Position mit ausgeben, im zweiten Fall ist das nicht möglich, dafür ist die Iteration etwas klarer.
Manchmal benötigt man eben nicht nur Zugriff auf den Index, sondern auch direkt auf den Wert. Dazu bietet Python eine dritte Schleifenvariante, nämlich mit enumerate(), was sowohl Zugriff auf den Index als auch den Wert bietet:
>>> message = ["Python", "has", "several", "loop", "variants"]
>>> for i, current_word in enumerate(message):
... print(i, current_word, end=',')
...
0 Python,1 has,2 several,3 loop,4 variants,>>>
Das produziert wie die obere indexbasierte Variante die gleiche Ausgabe, allerdings ist die Schleife deutlich einfacher zu lesen und zu verstehen. Beachten Sie aber, dass hier durch das veränderte Endezeichen zum Schluss kein Zeilenumbruch erfolgt.
Schauen wir uns ein weiteres Beispiel an, das nach jedem ungeraden Zeichen einen Unterstrich in der Ausgabe ergänzt und jedes gerade Zeichen durch Aufruf von upper() großschreibt – hier lernen wir, dass auch Strings mit einer for-in-Schleife durchlaufen werden können:
>>> for i, ch in enumerate("fun with chars"):
... if i % 2 != 0:
... print(ch + "_", end="")
... else:
... print(ch.upper(), end="")
...
Fu_N _Wi_Th_ c_Ha_Rs_>>>
Wir erhalten folgende Ausgabe, die die zeichenweise Iteration4 zeigt:
Fu_N _Wi_Th_ c_Ha_Rs_
Wahl der Schleifenvariante
Bedenken Sie immer: Halten Sie den Sourcecode möglichst verständlich und einfach! Falls Sie keinen Zugriff auf den Index benötigen, dann empfiehlt sich die Variante mit in.
Die while-Schleife durchläuft einen Block so lange, wie eine angegebene Bedingung erfüllt ist. Im Gegensatz zur for-Schleife müssen bei Bedarf gewisse Initialisierungsarbeiten explizit vorab erfolgen. Gleiches gilt innerhalb des Blocks der Schleife (des Schleifenkörpers) auch für das Hochzählen bzw. Modifizieren der Schleifenvariablen, um die Bedingung später einmal zu False auszuwerten und dadurch die Schleife tatsächlich (irgendwann) zu beenden.
while Bedingung:
# auszuführender Block
Schauen wir uns dies konkret an einem Beispiel an:
>>> i = 0
>>> while i < 5:
... print(i)
... i += 1
...
0
1
2
3
4
Nochmals als Hinweis: Vergessen Sie nicht, die in der Bedingung verwendete Variable zu erhöhen, sonst wird die Schleife nie beendet!
Als Erstes definieren wir eine Schleifenvariable namens i mit dem Wert 0. Danach wird in while die Einhaltung der Bedingung (hier i < 5) geprüft. Solange dies gegeben ist, werden die Anweisungen des sogenannten Schleifenrumpfs ausgeführt. Darin erfolgt eine Konsolenausgabe und danach wird der Wert von i um 1 erhöht.
Python bietet ein wirklich lesenswertes Onlinetutorial. Dieses finden Sie unter https://docs.python.org/3/tutorial/. Neben einem Einstieg behandelt es diverse andere Themen recht ausführlich.