Kontrollstrukturen sind das A und O der Programmierung. Wir unterscheiden die Strecke (aufeinanderfolgende Anweisungen), Verzweigungen und Wiederholungen. Die Strecke kennst Du schon. Das sind aufeinander folgende Anweisungen, die in der Reihenfolge ihrer Notation ausgeführt werden. Spannend wird es, wenn wir in Abhängigkeit von Bedingungen alternative Ausführungspfade beschreiten und Anweisungen wiederholt ausführen können.
Während wir uns die grundlegenden Datentypen vor allem mithilfe der Python-Shell angesehen haben, schreiben wir ab jetzt vorwiegend richtige Programme. Vorerst sind das kurze Programme. Du wirst aber sehen, in Bälde auch umfangreichere Programme schreiben zu können, Programme, die wirklich sinnvolle Dinge leisten. – Die Grundzutat sind Kontrollstrukturen.
Fallunterscheidungen
Python bietet uns zwei Möglichkeiten, Fallunterscheidungen zu implementieren. Das sind die klassische if
-Anweisung und der bedingte Ausdruck.
if
-Anweisung
Bei der if
-Anweisung wird eine Bedingung formuliert, in deren Abhängigkeit eine oder mehrere Anweisungen ausgeführt werden. Die abhängigen Anweisungen werden ausgeführt, wenn die Bedingung wahr ist. Die Bedingung kann beliebig komplex sein.
Einrückungen: Einrückungen haben in Python eine syntaktische Bedeutung. Sie drücken Abhängigkeiten aus. Einrückungen sollten stets vier Leerzeichen betragen und sind einheitlich im Programmcode zu handhaben.
Syntax der einfachen if
-Anweisung:
if BEDINGUNG: ANWEISUNG ...
Hinter der Bedingung ist ein Doppelpunkt zu notieren. Die von der Bedingung abhängigen Anweisungen sind eingerückt.
Kommt der Programmablauf zur if
-Verzweigung, wird deren Bedingung geprüft. Ist die Bedingung wahr, wird der eingerückte Programmblock ausgeführt. Er kann aus einer oder auch mehreren Anweisungen bestehen. Sind die Anweisungen abgearbeitet, wird das Programm hinter der if|
Verzweigung fortgesetzt. Ist die if
-Bedingung nicht zutreffend, wird der eingerückte Block übersprungen und es geht direkt hinter der if
-Verzweigung weiter.
Wenn mehrere Bedingungen sequentiell zu prüfen sind, im Sinne einer Wenn-Dann-Sonst-Kette, die jeweils alternative Programmabläufe zur Folge haben sollen, kann die if
-Bedingung um beliebig viele elif
-Verzweigungen ergänzt werden.
Syntax der erweiterten if
-Anweisung:
if BEDINGUNG_1: ANWEISUNG ... elif BEDINGUNG_2: ANWEISUNG ... elif BEDINGUNG_3: ANWEISUNG ... ...
Zuletzt gibt es einen optionalen else
-Zweig. Die darin notierten Anweisungen werden ausgeführt, wenn keine der vorher formulierten Bedingungen zutreffend war. else
kann sowohl mit der einfachen wie auch der erweiterten if
-Verzweigung genutzt werden.
Syntax des optionalen else
-Zweiges am Beispiel der erweiterten if
-Anweisung:
if BEDINGUNG_1: ANWEISUNG ... elif BEDINGUNG_2: ANWEISUNG ... ... else: ANWEISUNG ...
Implementieren wir ein einfaches Beispielprogramm. Es liest eine ganze Zahl über die Kommandozeile ein und prüft, ob diese Zahl gerade oder ungerade ist. Gerade Zahlen lassen sich ganzzahlig ohne Rest durch 2 teilen.
Der Zugriff auf sys.argv[1]
könnte zu einem Fehler führen, wenn kein Kommandozeilenargument angegeben wurde. Das kann jedoch nicht passieren. Fehlt ein Kommandozeilenargument, so ist die Bedingung, welche die Länge des Kommandozeilenvektors prüft, wahr. – Du erinnerst Dich: Die Länge von sys.argv
ist 2, wenn ein Kommandozeilenargument angegeben wurde. – Wurde nicht mindestens ein Argument übergeben, so bricht das Programm ab.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys # primitive Prüfung, ob Programmaufruf (halbwegs) korrekt erfolgt ist if len(sys.argv) < 2: print("Aufruf: gerade.py GANZZAHL") sys.exit(1) # Programm beenden (Status: 1) # wenn hier angekommen, wurde mindestens ein Argument übergeben # - lässt sich das Kommandozeilenargument in keine Ganzzahl wandeln, # kommt es zu einem Fehler! zahl = int(sys.argv[1]) if zahl % 2 == 0: # Zahl gerade? print("gerade") else: # sonst print("ungerade")
sys.exit()
bewirkt ein sofortiges Programmende. Dabei kann ein ganzzahliger Statuscode übergeben werden, den die aufrufende Shell auswerten kann. 0 signalisiert üblicherweise einen korrekten Programmablauf. Für die Signalisierung von Fehlern gibt es keine weiteren Konventionen. Du kannst Dir eigene Fehlernummern ausdenken, um verschiedene Gründe einer Programmbeendigung analysieren zu können.
Bedingter Ausdruck
Bei einfachen Fallunterscheidungen, bei denen nur zwei Fälle zu unterscheiden sind, und je Fall ein bestimmter Wert zugewiesen werden soll, können wir statt einer if
-Verzweigung einen bedingten Ausdruck verwenden. Dabei sei x
eine ganze Zahl.
if x >= 0: y = x else: y = -x
Augenscheinlich wird y
der Betrag von x
zugewiesen. Das geht mit einem bedingten Ausdruck auch kürzer:
y = x if x >= 0 else -y
Ein bedingter Ausdruck ist letztlich nur eine Kurzschreibweise für eine einfache if
–else
-Anweisung in Verbindung mit einer Zuweisung.
Schleifen
Grundform der while
-Schleife
Die spannendsten Kontrollstrukturen sind sicherlich Schleifen. Sie sind aber eventuell auch kritisch. Schleifen bergen die potenzielle Gefahr, dass wir Programme formulieren, die nicht terminieren.
Die while
-Schleife ist syntaktisch der if
-Verzweigung sehr ähnlich. Statt if
notieren wir jedoch while
. Das bedeutet dann, den eingerückten Anweisungsblock so oft wiederholt auszuführen, wie die Schleifenbedingung erfüllt ist. Nach jedem Durchgang des eingerückten Anweisungsblocks wird die Schleifenbedingung geprüft. Die Schleife bricht ab, wenn die Bedingung falsch ist. Die Schleife wird gar nicht erst betreten, wenn die Schleifenbedingung von Beginn an falsch ist.
Ein einfaches Spiel zahlenraten.py
while
-Schleife. Das Programm erzeugt eine pseudo-zufällige Ganzzahl im Intervall \([1, 1000]\). Dazu wird die Funktion random
genutzt. Die Funktion erwartet einen Start- und einen Endwert. Letzterer muss um 1 größer sein als die größte zu generierende Zahl.
Der Anwender unseres Beispielprogramms soll die Zahl raten, welche vorher zufällig generiert wurde. Seine Rateversuche lesen wir mit input()
ein. Nach jedem Versuch geben wir dem Ratenden aus, ob sein Tipp zu groß oder zu klein war. Die Anzahl der Rateversuche wird protokolliert und am Ende ausgegeben.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import random # Pseudo-Zufallszahlen zufallszahl = random.randint(1, 1001) # Zahl im Intervall [1, 1000] zaehler = 0 # noch kein Versuch geratene_zahl = 0 # Initialisierung # weil 'geratene_zahl' = 0 und 'zufallszahl' in [1, 1000] # ist die Schleifenbedinung zu Beginn garantiert erfüllt while geratene_zahl != zufallszahl: geratene_zahl = int(input("Ihr Tipp: ")) zaehler += 1 # Anzahl der Versuche zählen if geratene_zahl > zufallszahl: print("Die gesuchte Zahl ist kleiner.") elif geratene_zahl < zufallszahl: print("Die gesuchte Zahl ist größer.") print("Anzahl der Versuche: " + str(zaehler))
Die zum Verständnis des Programms erforderlichen Erläuterungen sind als Kommentare im Programmcode eingefügt. Weil sich Zeichenketten nicht mittels +
-Operator mit einer ganzen Zahl verknüpfen lassen, nutzen wir str()
. Der Konstruktor für Zeichenketten konstruiert aus seinem Argument eine Zeichenkette.
Hier eine Beispielsitzung die zeigt, wie ein Programmlauf aussehen kann.
$ zahlenraten.py Ihr Tipp: 500 Die gesuchte Zahl ist größer. Ihr Tipp: 750 Die gesuchte Zahl ist größer. Ihr Tipp: 875 Die gesuchte Zahl ist kleiner. Ihr Tipp: 816 Die gesuchte Zahl ist größer. Ihr Tipp: 845 Die gesuchte Zahl ist kleiner. Ihr Tipp: 831 Die gesuchte Zahl ist größer. Ihr Tipp: 838 Die gesuchte Zahl ist kleiner. Ihr Tipp: 835 Die gesuchte Zahl ist größer. Ihr Tipp: 836 Anzahl der Versuche: 9
Hinweis: Computer können keine Zufallszahlen generieren. Mithilfe entsprechender Algorithmen lassen sich jedoch Zahlen generieren, die sich wie Zufallszahlen verhalten. Wir nennen sie Pseudo-Zufallszahlen.
while
–else
Im Gegensatz zu den meisten anderen Programmiersprachen, weist die while
-Schleife in Python eine Besonderheit auf. Sie kann einen optionalen else
-Zweig besitzen. Dieser Codeblock wird nur dann einmalig ausgeführt, wenn die Schleifenbedingung erstmalig False
ergibt und die Schleife damit ordnungsgemäß, aufgrund der nicht mehr zutreffenden Bedingung, beendet wird. Der else
-Zweig kommt nicht zur Ausführung, wenn die Schleifenbedingung von vornherein falsch war/ist.
Wir wollen uns diese Option anhand des letzten Beispiels, zahlenraten.py
, ansehen. Das Programm soll dahingehend erweitert werden, dem Anwender eine Abbruchmöglichkeit zu geben, wenn er keine Lust mehr hat, weiter zu raten. Die Eingabe einer 0 soll das Programm beenden. Die Zahl 0 ist geeignet, weil sie nicht im Intervall der zu erratenden Zahlen liegt. Das neue Programm soll zahlenraten2.py
heißen.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import random # Pseudo-Zufallszahlen zufallszahl = random.randint(1, 1001) # Zufallszahl zaehler = 0 # noch kein Versuch geratene_zahl = 0 # Initialisierung # weil 'geratene_zahl' = 0 und 'zufallszahl' in [1, 1000] # ist die Schleifenbedinung zu Beginn garantiert erfüllt while geratene_zahl != zufallszahl: geratene_zahl = int(input("Ihr Tipp (ABBRUCH=0): ")) zaehler += 1 # Anzahl der Versuche zählen if geratene_zahl == 0: # Abbruchbedingung prüfen print("ABBRUCH") break # Schleife vorzeitig beenden # Anwender will weiter machen if geratene_zahl > zufallszahl: print("Die gesuchte Zahl ist kleiner.") elif geratene_zahl < zufallszahl: print("Die gesuchte Zahl ist größer.") else: # Ausgabe erfolgt nur, wenn Zahl erfolgreich geraten print("Anzahl der Versuche: " + str(zaehler))
Die break
-Anweisung beendet eine Schleife vorzeitig. Im nächsten Abschnitt werden wir sie detaillierter behandeln. Im Moment reicht es zum Verständnis, wenn Sie wissen, dass break
die Schleife abbricht. Weil der Abbruch jedoch nicht auf eine falsche Schleifenbedingung zurückzuführen ist, wird der else
-Zweig der while
-Schleife nicht ausgeführt. Dieser wird nur dann ausgeführt, wenn der Anwender die gesuchte Zahl erraten und damit die Schleifenbedingung den Wert False
angenommen hat.
Beispielsitzungen:
$ zahlenraten2.py Ihr Tipp (ABBRUCH=0): 500 Die gesuchte Zahl ist größer. Ihr Tipp (ABBRUCH=0): 725 Die gesuchte Zahl ist größer. Ihr Tipp (ABBRUCH=0): 0 ABBRUCH $ zahlenraten2.py Ihr Tipp (ABBRUCH=0): 500 Die gesuchte Zahl ist kleiner. Ihr Tipp (ABBRUCH=0): 250 Die gesuchte Zahl ist größer. Ihr Tipp (ABBRUCH=0): 375 Anzahl der Versuche: 3
for
-Schleife
Die for
-Schleife ist das zweite Schleifenkonstrukt in Python. Im Vergleich zu gleichnamigen Schleifenkonstrukten in anderen Programmiersprachen, ist diese Schleife in Python ein wenig speziell. Sie iteriert über eine gegebene Anzahl von Objekten. In vielen anderen Programmiersprachen wird daher auch, diese spezielle Form der for
-Schleife betreffend, von einer for-each-Schleife gesprochen.
Zur Verdeutlichung betrachten wir ein etwas gekünsteltes Programm argumente.py
. Das Programm soll die ihm übergebenen Kommandozeilenargumente zeilenweise ausgeben.
$ argumente.py Karsten Brodmann "Susi Sorglos" 1 2 3 Karsten Brodmann Susi Sorglos 1 2 3 $ _
Du weißt bereits, dass die Kommandozeilenargumente in einer Liste namens sys.argv
in Form von Zeichenketten abgelegt sind. Listen sind iterierbar. Das bedeutet eine Liste elementweise durchlaufen zu können. Die Konstruktion
for VARIABLE in ITERIERBARES_OBJEKT: ANWEISUNG ... ANWEISUNG
tut genau dies. Neben Listen sind auch verschiedene andere Objekte iterierbar. Später wirst Du selbst Objekte mit dieser Eigenschaft implementieren können.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys for argument in sys.argv: print(argument)
In Bezug auf das Beispiel ist es jedenfalls so, dass alle Elemente der Liste, beginnend bei Index 0, bis zu deren Ende durchlaufen und ausgegeben werden. Dabei benötigen wir den Index nicht. Ein sogenannter Iterator, der für Listen implementiert ist, sorgt dafür, die Elemente der Liste von Anfang bis Ende zu liefern. Die Elemente werden im Kopf der for
-Schleife nach und nach der Variablen argument
zugewiesen. Mit dieser Variablen wird im Schleifenrumpf gearbeitet. Sind alle Elemente des iterierbaren Objekts, hier der Liste sys.argv
, durchlaufen, endet die Schleife.
Schleifen vorzeitig beenden oder fortsetzen
Schleifen und die darin enthaltenen Anweisungen müssen nicht zwangsläufig immer vollständig durchlaufen werden. Innerhalb einer Schleife können Bedingungen eintreten, die es unsinnig machen, bestimmte Anweisungen innerhalb der Schleife noch auszuführen. Eventuell kann es sogar möglich sein, eine Schleife komplett zu beenden, auch wenn die Schleifenbedingung noch wahr ist.
Das Programm textkopieren.py
liest die Standardeingabe. Dies tut es, solange noch Bytes daraus gelesen werden können. Das Ende der Standardeingabe wird durch eine leere Zeichenkette dargestellt. Sie ist in Python generell das Symbol für ein Dateiende.
Im Beispielprogramm findet keine großartige Verarbeitung statt. Es wird lediglich die Standardeingabe byteweise in die Standardausgabe kopiert, so dass wir mithilfe von Eingabe-/Ausgabeumlenkungen eine Datei kopieren können.
Eine Verarbeitung ist nur möglich, wenn ein Byte zur Verarbeitung vorhanden ist. Bei Erreichen des Dateiendes, ist das nicht mehr der Fall. Ein break
beendet dann die Schleife und die Programmausführung wird hinter der Schleife fortgesetzt.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys while True: b = sys.stdin.read(1) # ein Byte aus Standardeingabe lesen if b == '': # Dateiende erreicht? break # wenn ja, abbrechen # ok, es wurde erfolgreich ein Zeichen gelesen # --> Verarbeitung: hier nur 1:1 ausgeben print(b, end='')
Für das Lesen der Standardeingabe gibt es im Modul sys
eine Variable stdin
. Sie stellt eine Abstraktion der Standardeingabe dar und bietet uns eine Methode namens read()
. Dieser übergeben wir als Parameter die Anzahl an Bytes, die bei einem Leseversuch gelesen werden sollen.
Nun mag es unverständlich sein, die Eingabe Byte für Byte zu lesen, wenn einige Zeichen, wie beispielsweise die deutschen Umlaute, aus mehr als einem Byte bestehen. Das ist insoweit unproblematisch, als ein Zeichenstrom letztlich auch nur aus einer Sequenz von Bytes besteht. Und genau einen solchen geben wir aus, wenn wir jedes gelesene Byte unverändert wieder ausgeben.
Die Schleifenkonstruktion ist hinsichtlich ihrer „Bedingung“ interessant. True
ist ein konstanter Wahrheitswert. Die Schleifenbedingung ist folglich stets wahr. Die Schleife kann nicht abbrechen, ist eine Endlosschleife. Der Schleifenabbruch erfolgt mittels break
, weil diese Anweisung, wenn sie ausgeführt wird, den Schleifenrumpf verlässt.
Beispielsitzung (Programm auf den eigenen Quellcode angewendet):
$ textkopieren.py <textkopieren.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys while True: b = sys.stdin.read(1) # ein Byte aus Standardeingabe lesen if b == '': # Dateiende erreicht? break # wenn ja, abbrechen # ok, es wurde erfolgreich ein Byte gelesen # --> Verarbeitung: hier nur 1:1 ausgeben print(b, end='')
Analog zu break
gibt es continue
. Auch hier werden folgende Anweisungen nicht ausgeführt. Jedoch wird die Schleife nicht abgebrochen. Es beginnt ein neuer Schleifendurchlauf.
break
und continue
werden in Bezug auf for
-Schleifen ganz analog verwendet.
Vielleicht stört Dich im obigen Programm der byteweise Zugriff. Du möchtest wirklich zeichenweise lesen. Das ist in Python so einfach nicht machbar. Der folgende Workaround ist aber fast immer eine gute Lösung. Wir betrachten zuerst das zeilenweise Lesen. sys.stdin
kann nämlich zeilenweise iteriert werden. Jede Zeile ist eine Zeichenkette.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys for zeile in sys.stdin: print(zeile, end='')
Und weil Zeichenketten ihrerseits zeichenweise iteriert werden können, gönnen wir uns eine zweite for
-Schleife zu diesem Zweck.
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys for zeile in sys.stdin: for zeichen in zeile: print(zeichen, end='')
Jede gelesene Textzeile endet mit einem Zeilenumbruch. sys.stdin
liest und liefert jede Zeile vollständig. Will man den Zeilenumbruch loswerden, so hilft die Methode strip()
, die jedem String-Objekt zur Verfügung steht. Mit ihrer Hilfe kann man Whitespaces am Beginn und am Ende einer Zeichenkette entfernen. strip()
liefert ein neues Zeichenkettenobjekt.
Diese Implementierungen setzen Textdateien voraus!
WORK IN PROGRESS …