Schleifen in Python

Schleifen dienen dazu, einen gewissen Anweisungsblock mehrmals ausführen zu lassen. Progammierstruktur: Iteration

Table of Contents

Dieser Blog wird vom IAIP gratis zur Verfügung gestellt und ist einer aus einer Reihe von Einführungsblogs zum Thema «Programmieren mit Python».

Der Blog ist ein  Bestandteil des Kurses «Einführung in Python Teil 1». Der Kurs führt dich durch die einzelnen Blogs, enthält Zusatzmaterialien, viele Aufgaben mit Lösungen und Quizze zur Lernkontrolle. Der Kurs hat eine Kursgebühr von CHF 50.- Mit dem Einschreiben zum Kurs hilfst du mit, dass solche Blogs auch zukünftig noch gratis zur Verfügung gestellt werden können.

Die while- Schleife

Schleifen (wir verwenden hier die Begriffe «Schleifen» und «Schlaufen» gleichbedeutend) gehören neben den bedingten Anweisungen und Verzweigungen (if, if-else Befehle) zu den Kontrollstrukturen von Python. Schleifen dienen dazu, einen gewissen Anweisungsblock mehrmals ausführen zu lassen. Wie schon bei den bedingten Anweisungen ist der betroffene Anweisungsblock eingeschoben. So weiss der Interpreter, welche Anweisungen wiederholt werden sollen. 

Dabei kennt Python 2 Arten von Schleifen, namentlich die while- Schleife und die for- Schleife. Als erstes werden wir die while- Schlaufe behandeln. Anschliessend werden wir die for– Schleife besprechen.

Die Grundform der while- Schleife

Betrachten wir zuerst wiederum ein kleines Beispiel:

i = 0
while i < 10:
    print(i, end=", ")
    i += 1

Das Programm führt zu folgender Ausgabe:

«while» ist Englisch und bedeutet übersetz «während/ solange». Das beschreibt auch schon treffend, wie die while- Schleife funktioniert: solange etwas erfüllt ist, wird …  gemacht.

Nach der Zuweisung i = 0 folgt im Beispielprogramm die Schleife. Sie beginnt mit einem zusammengesetzten Kommando, wie wir dieses bereits von den bedingten Anweisungen und Verzweigungen kennen. Es hat einen Schleifenkopf, welcher durch das Schlüsselwort while eingeleitet wird. Nach diesem folgt eine Bedingung (hier: i < 10) und ein Doppelpunkt schliesst den Kopf in gewohnter Weise ab.

Ist die Bedingung erfüllt, so wird der Schleifenkörper ausgeführt. Dieser besteht aus einer oder mehreren Anweisungen. Nach Abarbeitung der Anweisungen springt das Programm wieder an den Anfang der Schleife zurück. Die Bedingung wird erneut ausgewertet und alles beginnt von vorne. Das wiederholt sich so lange, bis die Auswertung der Bedingung den Wahrheitswert False ergibt. Dann wird die Kommandofolge übersprungen und mit dem Rest des Programms fortgefahren.

Die bei der Schleife auszuführenden Anweisungen (der Rumpf) kommen entweder gleich anschliessend und werden durch Strichpunkte abgetrennt, oder sie werden eingerückt auf den Folgezeilen notiert, so wie wir dies von den bedingten Anweisungen kennen.

Zusammenfassend ergibt sich folgende Syntax:

  while Bedingung:
       Anweisung
       Anweisung
        …

Die bedingte Anweisungen der while Schleife unterscheiden sich im Prinzip kaum von jenen der if Anweisung. Im Unterschied zu den «schlaufenlosen» bedingten Anweisungen wird hier der Anweisungsblock einfach zusätzlich solange wiederholt, bis die Bedingung nicht mehr erfüllt ist. Sollte die Bedingung schon bei der allerersten Auswertung falsch (False) sein, dann werden die bedingten Anweisungen der Schleife einfach übersprungen und es wird direkt mit dem Rest des Programms weitergemacht. In diesem Fall wird die Schlaufe kein einziges Mal durchlaufen. Man bezeichnet die while- Schleife deshalb auch als sogenannte «abweisenden Schleife» (auch vorprüfende oder kopfgesteuerte Schleife).

Natürlich können wir, gleich wie bei den anderen Kontrollstrukturen auch, Schleifen in Schleifen realisieren. Hierzu ein Beispiel einer solchen verschachtelten (engl.: nested) Struktur:

i = 2
while i < 5:
    j = 1
    while j < 4:
        print(str(i)+str(j), end=", ")
        j += 1
    i += 1

Das Programm führt erwartungsgemäss zu folgender Ausgabe:

Nachfolgend ein weiteres kleines Beispielprogramm, welches wir anschliessend ausbauen werden.

import random
zahl = random.randint(1, 100)

eingabe = 0
while eingabe != zahl:
    eingabe = int(input("Bitte gib eine Zahl ein: "))

print("Gratuliere. Du hast die Zahl erraten.")

Das Programm realisiert ein kleines Spiel, wobei der Spieler eine Zufallszahl zwischen 1 und 100 erraten muss.

Als erstes wird eine Zufallszahl erzeugt. Hierzu muss das Modul random importiert werden, welches eine Reihe an Funktionen zur Generierung von Zufallszahlen und Zufallsauswahlen beinhaltet. Anschliessend kann man mittels der darin enthaltenen Funktion randint(von, bis) eine zufällige Zahl generieren und in der Variable zahl ablegen.

Die while Schleife läuft solange, wie die Eingabe im Terminal (Variable eingabe) nicht der gesuchten Zufallszahl (Variable zahl) entspricht.

Damit sich das Spiel nicht unendlich in die Länge zieht, können wir dem Spieler noch eine Rückmeldung geben. Mittels 3 bedingten Anweisungsblöcken bekommt der Spieler jeweils ein Feedback, ob die Eingabe grösser, kleiner oder genau der gesuchten Zahl entspricht. Wir erweitern unser Programm wie folgt:

import random
zahl = random.randint(1100)

eingabe = 0
while eingabe != zahl:
    eingabe = int(input("Bitte gib eine Zahl ein: "))
    if eingabe > zahl:
        print("Deine Zahl ist zu gross!")
    if eingabe < zahl:
        print("Deine Zahl ist zu klein!")
    if eingabe == zahl:
        print("Genau richtig!")
Als letztes wollen wir noch die Anzahl Versuche zählen (Variable versuche)
import random
zahl = random.randint(1, 100)

eingabe, versuche = 0, 0
while eingabe != zahl:
    eingabe = int(input("Bitte gib eine Zahl ein: "))
    versuche += 1
    if eingabe > zahl:
        print("Deine Zahl ist zu gross!")
    if eingabe < zahl:
        print("Deine Zahl ist zu klein!")
    if eingabe == zahl:
        print("Genau richtig!")
        print("Du hast", versuche, "Versuche benötigt!")

Schleifenabbruch mit break und continue

Mitunter muss man eine Schleife abbrechen. Beispielsweise weil eine Fehlersituation aufgetreten ist oder ein Benutzer etwas Spezielles eingegeben hat. Für solche Situationen stellt Python das break Kommando (engl.: unterbrechen).

Wir erweitern hierzu unser kleines Ratespiel. Der bereits bekannte Code wird ergänzt, so dass die Eingabe der Zahl 0 zu einem Spielabbruch führt.

Die break Anweisung beendet jeweils die innerste umgebende Schleife.

Hier ein Hinweis: Manchmal hat man eine verschachtelte Struktur und möchte mit der break Anweisung gleich alle Schlaufen verlassen. Dies ist als solches nicht möglich. Oft kann man die einzelnen Schlaufen aber in einer einzigen Zusammenfassen. Falls nicht, gibt es weitere Auswege. Eine gängige Praxis besteht beispielsweise darin, die Schlaufen in eine Funktion zu packen. Denn mit der return() Anweisung verlässt man immer die Funktion, ganz egal, in welchem Block sich diese befindet. Oder man setzt in den Schleifenköpfen zusätzlich eine Hilfsvariable ein, welche angibt, dass die Abbruchbedingung erfüllt ist.

Neben der break Anweisung zur Steuerung des Schleifenflusses gibt es auch noch das Kommando continue. Mit continue kann man aus der Mitte einer Schleife heraus wieder an ihren Anfang springen.

In unserem Ratespiel können wir beispielsweise 2 continue Anweisungen einfügen, so dass die jeweils folgenden bedingten Anweisungen gar nicht überprüft werden müssen.

Bei einer while Schleife wird dann zuerst wieder die Bedingung im Kopf getestet. In der Zusammenfassung erhalten wir folgendes Programm:

import random
zahl = random.randint(1, 100)

eingabe, versuche = 0, 0
while eingabe != zahl:
    eingabe = int(input("Bitte gib eine Zahl ein: "))
    if eingabe == 0:
        print("Ok. Das Spiel wird abgebrochen. Schade")
        print("Die richtige Zahl wäre ", zahl, " gewesen.")
        break
    versuche += 1
    if eingabe > zahl:
        print("Deine Zahl ist zu gross!")
        continue
    if eingabe < zahl:
        print("Deine Zahl ist zu klein!")
        continue
    if eingabe == zahl:
        print("Genau richtig!")
        print("Du hast genau", versuche, "Versuche benötigt!")

Pass Anweisung

Oft in einem Zug mit der break und continue Anweisung erläutert, wenn auch grundsätzlich verschieden, ist die pass Anweisung

Die pass– Anweisung tut nichts. Sie wird dort eingesetzt, wo syntaktisch eine Anweisung benötigt wird, das Programm jedoch aktuell nichts tun soll. Man kann sie beispielsweise nutzen, wenn man erst später den Code schreiben möchte, oder wenn etwas automatisch ersetzt werden soll. Die Anweisung wird vor allem im Zusammenhang mit Klassen und Vererbung verwenden.

Auch wenn nichts geschehen soll, ist das pass Statement nicht mit einem Kommentar zu verwechseln. Kommentare werden vom Interpreter einfach weggelassen, wohingegen eine pass Anweisung eine null Operation (NOP) ist.

Der Anwendungsfall lässt sich am Beispiel von Schleifen gut demonstrieren. Der folgende Code lässt sich nicht ausführen:

import random
i = 0
while i < 1000:
    # hier kommt dann später Code
print("test")

Der Interpreter streicht die Kommentarzeile weg. Was bleibt, ist ein Schleifenkopf ohne zugehörigen Körper, also quasi eine angebrochene Kontrollstruktur. Möchte man den Code ausführen, so erscheint folgende Meldung:

Wir können das Problem aber einfach lösen, indem wir mit der pass Anweisung einfach eine null Operation ausführen.

import random
i = 0
while i < 1000:
    pass # hier kommt dann später Code
print("test")

Schleife mit Verzweigung: while – else Anweisung

In Python lässt sich die while Schleife mit einer else Anweisung ergänzen. Es ist vom Prinzip her genauso, wie man dies von der Verzweigungsanweisung if – else kennt. Entsprechend werden die Anweisungen im else Teil dann ausgeführt, wenn die Bedingung nicht (mehr) erfüllt ist.

Man kann sich darüber streiten, ob diese zusätzliche Möglichkeit einen echten Mehrwert generiert. Grundsätzlich kommt man in der Praxis auch gut ohne damit aus. Der Einsatz von else macht vor allem dann Sinn, wenn es im ersten Anweisungsblock irgendwo ein break-Kommando gibt. Dann springt man aus der Schleife, ohne dass die unter else aufgeführten Anweisungen ausgeführt werden.

import random

von, bis = 1, 100
zahl = random.randint(von, bis)
eingabe, versuche = 0, 0
vers_max = 10

while eingabe != zahl:
    eingabe = int(input("Bitte gib eine Zahl ein: "))

    if eingabe == 0:
        abbruch = input("Du willst das Spiel abbrechen? ")
        if abbruch == "ja":
            print("Ok. Das Spiel wird abgebrochen. Schade")
            print("Die richtige Zahl wäre ", zahl, " gewesen.")
            break
        else:
            continue
    elif eingabe < von or eingabe > bis:
        print("Die Zahl muss zwischen", von, "und ", bis, "liegen!")
        continue
    else:
        versuche += 1
        if eingabe > zahl:
            print("Deine Zahl ist zu gross!")
        if eingabe < zahl:
            print("Deine Zahl ist zu klein!")
        if versuche < 10:
            print("Du hast noch", vers_max - versuche, "Versuche!")
        else:
            print("Game over. Du hast verloren!")
            break
else:
    print("Genau richtig!")
    print("Du hast exakt", versuche, "Versuche benötigt!")

Das Beispiel zeigt einen möglichen Anwendungsfall. Hat der Spieler verloren, so wird das Programm beendet, ohne dass die letzten zwei print() Anweisungen ausgeführt werden.  

Die for- Schleife

Grundstruktur der for- Schleife

Neben der while– Schleife bietet die for– Schleife eine zweite Möglichkeit, um einen Anweisungsblock mehrmals auszuführen.

Die for- Schleife wird typischerweise genutzt, um über ein Folge von Objekten zu iterieren. Hierzu ein kleines Beispiel:

marke = ["BMW", "Audi", "Ferrari"]

for auto in marke:
    print(auto)

Die Variable marke beinhaltet hier eine Liste mit verschiedenen Markennamen. Listen in Python werden angelegt, indem man die einzelnen Elemente der Liste zwischen eckige Klammern [ ] schreibt. Die for Anweisung liest nun dieses Liste von links nach rechts aus. Im ersten Durchgang hat die Variable auto den Wert des ersten Elements der Liste, im zweiten Durchgang den Wert des zweiten Elements, etc. Die Ausführung des Codes führt entsprechend zu folgendem Resultat:

Die Syntax der for Schleifenanweisung besteht aus dem einleitenden for Schlüsselwort, gefolgt von der Schlaufenvariable, dem in Schlüsselwort und der Folge. Am Ende der Zeile  steht ein Doppelpunkt. Die zum Schlaufenkopf zugehörigen Anweisungen sind wiederum eingerückt.

for Schlaufenvariable in Folge:
      Anweisung 1
      Anweisung 2
       …

Grundsätzlich könnte man die Iteration durch eine Folge problemlos auch mit einer while Schleife realisieren. Allerdings ist der Code deutlich weniger kompakt.

i = 0
while i < len(marke):
    print(marke[i])
    i += 1

Zahlenbereiche erstellen mit der range Funktion

Oft möchte man eine Schleife über einen bestimmten Zahlenbereich. Beispielsweise für die Zahlen von 1 bis 10, also  i = 1, 2, 3, 4, … , 10.

Wir können hierzu einfach eine Liste mit den Zahlen schreiben:

i = 0
while i < [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
    print(i, end=", ")

Allerdings ist dieser Ansatz bei grösseren Zahlenreihen unpraktikabel. In Python gibt es aber eine einfache Möglichkeit, um solche Zählschleifen zu realisieren. Dazu benötigt man die range() Funktion (engl.: Bereich).

Nachfolgend drei Beispiele, wie man range() nutzen kann:

for i in range(6): print(i, end=" ")
print("\n")
for i in range(6,20): print(i, end=" ")   
print("\n")
for i in range(6,20,2): print(i, end=" ")   
print("\n")
for i in range(6,-5,-2): print(i, end=" ")   
print("\n")

Die Anweisungen führen bei Ausführung zu folgenden Ausgaben:

Eine range ist eine unveränderbare Folge von Zahlen mit einem Anfangswert, einem Endwert und einer Schrittweite. Wird der Funktion nur ein Wert übergeben, so steht dieser Wert für den Endwert (exklusive). Es wird eine Zahlenfolge startend von 0, einer Schrittweite von 1 und einem um 1 kleineren Endwert generiert. range(6) liefert demnach genau 6 Zahlen: 0, 1, 2, 3, 4, 5. Das entspricht den gültigen Indizes einer Sequenz mit sechs Elementen. Wichtig dabei ist, dass die Sequenz beim Wert 0 beginnt!

Bei zwei Werten steht die erste Zahl für den Anfangswert (inklusive) und die zweite für den Endwert (exklusive). Die Anweisung range(6, 20) liefert demnach die Werte 6 bis und mit 19. Bei drei Werten definiert die dritte Zahl die Schrittweite, wobei auch negative Schrittweiten möglich sind.

Auf den ersten Blick könnte man meinen, dass die range() Funktion automatisch eine Liste mit den entsprechenden Zahlen erstellt. Genau genommen liefert die Funktion aber keine Liste, sondern lediglich einen Iterator. Einen sogenannten range Folgetyp, welcher Zahlen in einem bestimmten Bereich generiert. Das hat einen kleinen Vorteil gegenüber der Erstellung einer Liste (oder eines Tupel). Da Range-Objekte anstelle jeder einzelnen Zahl der Folge im Wesentlichen nur den Anfangswert, den Endwert und die Schrittweite speichern, benötigen sie relativ wenig Speicherplatz, was vor allem bei grossen Zahlenwerten nützlich sein kann. Die Objekte range(10) und range(1000000) benötigen beispielsweise gleich viel Speicherplatz.

Man kann übrigens die lokalen Start-, End- und Schrittwerte des Objektes abfragen.

r = range(10)
print(type(r))
print(r.start, r.stop, r.step)

Bei Ausführung führt der Code zu folgender Ausgabe:

Ebenso kann man wie bei Listen über einen Index auf einzelne Elemente zugreifen und auch Slices bilden. Beispielsweise b=r[2:] . Die Funktionsweise des Zugriffs über einen Index wird im Blog zu den Listen im Detail erläutert.

Exkurs: Die range Funktion für Gleitkommazahlen

Die Parameter der range Funktion sind Ganzzahlen. Man kann dies relativ einfach überprüfen:

r = range(10)
print(type(r.start))
print(type(r.stop))
print(type(r.step))

Python wird bei Ausführung 3x <class ‹int›> in die Shell schreiben. Möchte man eine Folge von Gleitkommazahlen realisieren, so muss man diese über eine Funktion selber erzeugen. Beispielsweise so:

def frange(start, stop, step):

    i = start
    while i < stop:
        yield i
        i += step


for i in frange(0.2, 1.4, 0.1):
    print(round(i, 4), end=" ")

Die enumerate Funktion: Index und Element

Oftmals möchte man über die Indizes einer Liste iterieren. Die naheliegende Lösung liegt in der Erstellung eines range Objektes, wobei der übergebende Wert der Anzahl Elemente einer Liste entspricht. Diese lässt sich über die len() Funktionen abfragen:

car=["BMW","Audi","Mercedes","Fiat","Ferrari"]
for i in range(len(car)):
    print (i, car[i])

Es folgt:

Eine elegantere Möglichkeit besteht darin, die Funktion enumerate() zu benutzen:

for index, model in enumerate(car):
    print (index, model)

Mit enumerate () lassen sich der Index und das entsprechende Objekt gleichzeitig abrufen. Es erfolgt genau dieselbe Ausgabe.

Der else Zweig einer for- Schleife

Auch for – Schleifen können einen else– Zweig haben. Dieser macht wiederum vor allem im Zusammenhang mit einer break Anweisung Sinn. Das folgende Beispiel zur Berechnung der Primzahlen ist ein solcher Anwendungsfall.

von = int(input("Zahlen von: "))
bis = int(input("       bis: "))
for zahl in range(von, bis + 1):
    if zahl > 1:
        for i in range(2, max(zahl, 3)):
            if (zahl % i) == 0:
                break
        else:
            print(zahl, "ist eine Primzahl")

Die innere Schleife überprüft, ob sich die Zahl durch eine andere Zahl zwischen 2 und sich selber restlos teilen lässt. Falls ja, wird die Schleife verlassen, andernfalls muss es sich um eine Primzahl handeln.

Ergänzungen zum Thema Schleifen

Damit im Quellcode nicht jede Anweisung der Reihe nach genau 1x ausgeführt wird, benötigt man sogenannte Kontrollstrukturen. Diese werden zur Ablaufsteuerung in Programmen gebraucht.

Die Basis bilden Verzweigungen und Schleifen, die meist mit logischen Ausdrücken, der booleschen Algebra, verknüpft sind. Dadurch werden Anweisungen in einem Programm nur bedingt und allenfalls wiederholt ausgeführt.

Abweisende und nicht abweisende Schleifen

Schleifen werden in abweisende (auch: kopfgesteuerte) und nicht abweisende (auch fussgesteuerte) unterschieden.

Die vorgestellt while Schleife ist eine abweisende Schleife. Hier wird als erstes eine boolesche Ausführungsbedingung geprüft. Die Schleife wird dann solange ausgeführt, wie die Ausführungsbedingung wahr ist. Wie erwähnt kann dieser Schleifentyp dazu führen, dass die Anweisung innerhalb des Schleifenkörpers nie ausgeführt wird.

Python kennt lediglich solche abweisenden Schlaufen. Viele andere Programmiersprachen enthalten zusätzlich Befehle für nichtabweisende Schleifen. Solche Schleifen werden immer mindestens 1x ausgeführt. Realisiert wird dies, indem die zu überprüfende Bedingung am Ende des Anweisungsblocks steht.

In Basic hat eine nichtabweisende Schleife beispielsweise folgende Form

  Repeat
  Anweisung 1
  Anweisung 2
    …
  Until Bedingung = True

Auch in Pascal existiert eine solche Repeat- Until Schlaufe, in C schreibt man beispielsweise do {…} while (…).

Zählschleifen und Mengenschleifen

In der Praxis muss man oft einen gewissen Wertebereich (Beispielsweise die Zahlen von 0 bis 100) oder etwas abstrakter eine Menge an Elementen (Beispielsweise die Namen aus einer Liste : [«Egon», «Reto», «Ralph», …]) durchlaufen.

Beides sind Anwendungsfälle abweisender Schleifen, lassen sich also problemlos über eine while Schleife umsetzen. Normalerweise stellen Programmiersprachen für diese 2 Anwendungsfälle zusätzliche, spezielle Schleifentypen zur Verfügung. Namentlich sogenannte Zählschleifen (for Schleife) und Mengenschleifen (for each Schleife). Beide können der kopfgesteuerten Schleife zugeordnet werden, da vor der Ausführung geprüft wird, ob ein weiterer Durchlauf möglich und notwendig ist.

Die «klassische» numerische Zählschleifen (for Schleife), wie sie C und C++ kennt, besitzt eine Schleifenvariable, die mit einem Startwert initialisiert wird und nach jedem Durchlauf des Schleifenkörpers verändert wird. Sie wird in der Regel um einen festen Wert (z.B. 1) erhöht oder vermindert wird, bis der definierte Zielwert erreicht ist. Nachfolgend abgebildet ist eine for-Schleife in C, welche die Zahlen von 1 bis 100 ausdruckt:

Python kennt keine solche for Schleife. Wir können eine solche aber, wie bereits gezeigt, mithilfe des range Objektes relativ einfach realisieren. Das was wir in Python unter einer for Schleife verstehen ist eine allgemeinere Form, welche man auch als Mengenschleife (mitunter auch als for each Schleife bezeichnet) bezeichnet.

Bei Mengenschleifen handelt es sich um ein Sprachkonstrukt mit dessen Hilfe nacheinander die Elemente einer Menge oder Liste bearbeitet werden können. Dazu werden diese einer Variable zugewiesen. Die Zählschleife ist letzten Endes von aussen betrachtet ein Spezialfall einer Mengenschleife. Im Gegensatz zu den klassischen Zahlschleifen verwenden Mengenschleifen einfach keine nach aussen sichtbare Laufvariable (oder Iterator-Objekt), sondern geben in jeder Iteration immer direkt den Wert zurück. Intern wird meist dennoch ein Iterator-Objekt gehalten, um den Stand der Iteration festzuhalten. Bei Programmiersprachen, in welchen beide Schleifentypen explizit implementiert sind, werden Mengenschleifen mit den Schlüsselwörtern for each eingeleitet. Beispiele sind Java, PHP, Perl, gewisse Basic Versionen, etc.

Comments