Python-Programmierung: Kontrollstrukturen

Aus Wikibooks

Wechseln zu: Navigation, Suche
Nuvola apps bookcase.svg Programmiersprachen Nuvola apps bookcase 1.svg PythonNuvola mimetypes dvi.png Kontrollstrukturen

<< Inhaltsverzeichnis

Inhaltsverzeichnis

[Bearbeiten] Übersicht

Kontrollstrukturen realisieren Verzweigungen, Fallunterscheidungen, d.h. die bedingte Ausführung von Programmcode. In Python stehen vier Konstrukte zur Verfügung, um den Programmfluss zu beeinflussen:

  • if ... elif ... else für die bedingte Ausführung alternativer Codeabschnitte.
  • while für Programmschleifen mit Eintrittsbedingung
  • for für Iterationen über Entitäten (Werte)
  • try ... except ... finally ... else zur Behandlung von Ausnahmen

[Bearbeiten] Das if Statement

Das if-Statement ermöglicht die bedingte Ausführung von Programmcode. Jedes if-Statement endet daher mit einem Doppelpunkt und leitet daher einen neuen Programmblock ein (Einrückung). Zwischen if und Doppelpunkt steht die Bedingung (das Prädikat) die entscheidet, ob der folgende Block ausgeführt werden soll, oder nicht.

[Bearbeiten] Die Bedingung (Prädikat)

Als Bedingung kann jeder gültige Python Ausdruck (Expression) verwendet werden. Betritt der Ausführungspfad das if-Statement, wird dieser Ausdruck ausgewertet und die entsprechend den Regeln für Python Wahrheitswerte entschieden, ob das Ergebnis logisch wahr (True) oder falsch (False) ist. Evaluiert der Ausdruck zu True wird der nachfolgende Block ausgeführt.

>>> x = 5
>>> if x > 2:
...     print "x ist groesser als 2"
...     
x ist groesser als 2
>>> def dummyFunktion():
...    pass
...
>>> if dummyFunktion:
...    print "Funktionen sind wahr"
...
Funktionen sind wahr
>>> if None:
...    print "Sollte nicht ausgegeben werden"
...
>>> if True:
...    print "Yupp, paßt"
...
Yupp, paßt

[Bearbeiten] Alternativen: else und elif

Oft sieht man sich mit der Situation konfrontiert, dass man Code ausführen möchte, wenn die if-Bedingung nicht erfüllt ist, also eine entweder...oder'-Situation vorliegt. Für diesen Fall kennt, wie die viele andere Programmiersprachen auch, Python das else-Statement. Es steht allein in einer Zeile, wird mit einem Doppelpunkt abgeschlossen auf den ein neuer Block folgt. Das else: steht auf der gleichen Einrückungshöhe wie das korrespondierende if.

>>> x = 4711
>>> if not x%2:
...     print "gerade"
... else:
...     print "ungerade"
...
ungerade

Eine if Option, die in diesen Form in anderen Sprachen selten anzutreffen ist, ist das elif, auch Else-If genannt. Er ermöglicht, Mehrfachenbedingungen zu formulieren, ohne in absurd hohe und unleserliche Einrückungstiefen abzugleiten, die vor allen eine hierarchische Gliederung suggerieren, die so gar nicht vorhanden ist.

# Ohne elif
if x > 0:
    print "positiv"
else:
    if x < 0:
        print "negativ"
    else:
        print "null"
 
# mit elif
 
if x > 0:
    print "positiv"
elif x < 0:
    print "negativ"
else:
    print "null"

[Bearbeiten] Umsetzung von case Statements

elif wird oft als Möglichkeit angesehen, switch()/case Konstrukte anderer Sprachen umzusetzen. Doch dies ist weder guter Stil, noch effizient. Ein Beispiel:

# Mit elif
 
if selektor == "A":
        doAktionA()
elif selektor == "B":
        doAktionB()
elif selektor == "C":
        doAktionC()
elif selektor == "Help":
        giveHelp()
else:
        showError()
 
# Mit einem Mapping
switchTab = {
        "A": doAktionA,
        "B": doAktionA,
        "C": doAktionA,
        "Help": giveHelp,
}
 
swtichTab.get(selektor, showError)()

Der Charakter von if...elif...else ist es, Bedingungen zu überprüfen, während ein switch()/case Konstrukt, über eine Auswahl selektiert. Diese Aufgabe wird aber viel besser durch ein Mapping, ein assoziatives Array, umgesetzt. Zumal diese Variante auch deutlich effizienter ist (Aufwand O(1) gegenüber O(n), mit n gleich der Anzahl der Elemente über die selektiert wird.)

[Bearbeiten] Einrückung und if...else Eindeutigkeit

Von Python-Neulinge wird oft die Bildung von Programmblöcken durch Einrückung kritisiert oder gar als lästig empfunden, etwa, wenn sie den Kardinalfehler begingen, Tabulatoren und Leerzeichen miteinander zu mischen. Andere Sprachen, wie Perl, Java oder auch C und C++, scheinen hier wesentlich liberaler zu präsentieren, ist die Einrückung dort, im Gegensatz zu Python, weder Bestandteil der Syntax noch von semantischer Bedeutung. Das bei den genannten Sprachen dennoch Code visuell klar zu strukturieren wird, wozu auch Einrückungen zählen, zeigt die wie wichtig ein konsistenter Stil für die Verständlichkeit von Programmcode in Wirklichkeit ist. Aus Sicht des Autors ist Pythons strenger, syntaktischer Ansatz gegenüber den anderen Sprachen im Vorteil, wenn nicht sogar überlegen, da er in der Lage ist, eine Typ von Fehlern zu vermeiden, der durch uneindeutigkeit bei der Zuordnung von if Statements entstehen können.

if (x >= 0)
        if (x == 0)
                x = FunktionB(x)
        y = FunktionA(x)

Doch, dies ist nach wie vor ein Buch über Python. Das C-Codeschnipsel zeigt ein typischen Fehler, der oft und gerne gemacht wird, und dann meist für viel Stress sorgt. Er demonstriert eine Einrückung, die einen anderen Programmfluss suggeriert, als tatsächlich ausgeführt wird. Derartige Fehler sind oft schwer zu identifizieren, da bei oberflächlicher Betrachtung alles plausibel und korrekt erscheint. Er entsteht meistens dann, wenn bestehender Code erweitert wird und ist Folge einer spezielle Eigenschaft von C, die aber auch in anderen Sprachen möglich ist.

Normaler Weisen werden geschweifte Klammern verwendet, um Programmblöcke zu bilden. Allerdings kann auf sie verzichtet werden, wenn der bedingt auszuführende Code nur aus einem Statement oder Ausdruck besteht. Der obige Fall entsteht meistens, aus Konstrukten wie dem folgenden:

if (x >= 0)
        y = FunktionA(x)

Das ist völlig korrekter und legitimer Code. FunktionA() wird nur ausgeführt, wenn x nicht negativ ist. Irgend wann während der Lebensdauer des Programms entsteht dann die Notwendigkeit, den Code um eine Fallunterscheidung zu erweitern, bei der FunktionB() FunktionA() vorgeschaltet werden soll, wenn x 0 ist. Und schon ist es passiert. Der Entwickler hat vergessen, die jetzt notwendigen Klammern zu setzten.

/* Was wirklich ausgeführt wird */
if (x >= 0)
        if (x == 0)
                x = FunktionB(x)
y = FunktionA(x)
 
/* Wie es aussehen sollte */
 
if (x >= 0) {
        if (x == 0)
                x = FunktionB(x)
        y = FunktionA(x)
}

Ja, wir befinden uns immer noch in einem Buch über Python. Wahrscheinlich hat jeder schon bemerkt, wo das ganze hinaus läuft. Der Fehler des C Programms wäre in Python nie einer gewesen.

# 1. Version
if x >= 0:
    y = FunktionA(x)
 
# Später erweiterter Version
if x >= 0:
    if x == 0:
        x = FunktionB(x)
    y = Funktion(x)

Da die Einrückung die Blockgrenzen bilden, kann es in Python zu keinen Fehler, wie dem beschrieben, kommen, der auch nur für ein typisches Beispiel einer ganzen Klasse ähnlicher Fehler stand, die durch missverständliche Einrückung in Sprachen wie C entstehen können.

[Bearbeiten] Schleifen

Mit Programmschleifen werden sich wiederholende Tätigkeiten und Vorgänge realisiert. Python verfügt über zwei Arten von Schleifen, dem while- und dem for-Statement. Obwohl beide Konstrukte eine ähnliche Wirkung besitzen, sie wiederholen einen Codeblock, unterscheiden sie sich in der Art ihrer Anwendung. Während bei while die Prüfung einer Bedingung, eines Prädikats, im Vordergrund steht, dessen Wahrheitswert darüber entscheidet, ob der Codeblock ausgeführt werden soll oder nicht, legt if sein Augenmerk auf die Anwendung des Codeblocks auf die Elemente einer Sequenz.

[Bearbeiten] Das while-Statement - Schleife mit Eintrittsbedingung

Ein kleines Beispielprogramm:

import sys
 
summe = 0
schritte = 0
 
while summe < 1000:
        schritte += 1
        summe += int(sys.stdin.readline().strip())
 
print schritte, " Schritte bis 1000"

Das while Statement besteht aus einer Abfragebedingung und einem Block. Der Block wird so lange wiederholt, wie die Bedingung zu wahr evaluiert. Die Bedingung wird vor jedem Betreten des Blocks geprüft, d.h. der Block wird kein Mal ausgeführt, wenn die Bedingung zu Beginn der Schleife bereits den Wahrheitswert False ergibt.

[Bearbeiten] Schleifen über Sequenzen - Das for Statement

In der realen Welt kennt man das Problem: Viele Tätigkeiten bestehen darin, mit einer Anzahl von Dingen wiederholt die gleiche Tätigkeit auszuführen. Ob es nun Tankquittungen sind, die in eine Fahrtkostenaufstellung eingetragen, Klassenarbeiten, die korrgiert oder ein Becher mit gesammelten Münzen, die sortiert werden wollen, immer liegt eine Menge von Objekten vor, die zu ver- oder bearbeitet sind.

Im virtuellen Bereich der Informatik sieht es nicht anders aus. Da wollen die Ergebniszeilen (rows) einer Datenbankanfrage (query) dargestellt, Datensätze einer Datei sortiert und gruppiert, oder Zeichenketten durchsucht werden. Zu diesem Zweck wurde schon in frühen Programmiersprachen, die for Schleife erfunden. Ihre frühsten Varianten, z.B. BASIC, war nur in der Lage, einen Programmabschnitt n Mal zu wiederholen und dabei einen Zählvariable zur Verfügung zu stellen, die als Index in ein Array (Feld) diente:

10 FOR i = 0 TO 10 STEP 2
20 PRINT i,
30 NEXT i
 
0, 2, 4, 6, 8

Programmiersprachen, wie C gingen vom Konzept der Zählung weg und betrachteten for als eine modifizierte Form einer while Schleife:

/* Dieses for-Konstrukt... */
for (Expression_1, Expression_2, Expression_3) {
    /* ... Schleifencode ... */
}
 
/* ...entspricht in etwa diesem while (wenn man das continue-Statement außen vor lässt) */
 
Expression_1;
while (Expression_2) {
    /* ... Schleifencode ... */
    Expression_3;
}

Vom konzeptionellen Ansatz, dass wir es mit einer Schleife über Dinge (Objekte) zu tun haben, ist bei diesen Sprachen nicht viel übrig geblieben.

Python verfolgt eine völlig andere Herangehensweise. Das Python for-Statement iteriert (schreitet) über eine Sequenz von (Objekt)-Werten:

>>> for item in [ 1, 2, 3, None, "Ein String", file ]:
...     print item, " ist ein ", type(item)
...
1  ist ein  <type 'int'>
2  ist ein  <type 'int'>
3  ist ein  <type 'int'>
None  ist ein  <type 'NoneType'>
Ein String  ist ein  <type 'str'>
<type 'file'>  ist ein  <type 'type'>

Was passiert: Zwischen for und in erwartet der Pythoninterpreter ein Name. Bei jedem Schleifendurchlauf (Iterationsschritt) wird an diesem Namen ein Element der nach in angegebenen Sequenz gebunden. Wie man sieht, müssen die Elemente der Sequenz nicht vom gleichem Typ sein. Auch muss es sich bei der Sequenz um keine Liste handeln. Alle Pythonobjekte, deren Klassen das Iteratorprotokoll unterstützen, sind möglich.

>>> for i in range(10):
...     print i,
...
0 1 2 3 4 5 6 7 8 9

Beim arbeiten mit Mappings/Dictonaries (dict) kommt es oft vor, in innerhalb der Schleife sowohl den Key, als auch den Wert, des aktuellen Elements zu Verfügung zu haben. for und die Mapping-Methode items() machen dies kinderleicht:

>>> for farbe, hexcode in dict(rot="#ff0000", gruen="#00ff00", blau="#0000ff").items():
...     print farbe, " hat den Farbecode ", hexcode
...
blau  hat den Farbecode  #0000ff
gruen  hat den Farbecode  #00ff00
rot  hat den Farbecode  #ff0000

Gelegentlich reicht es nicht, einfach nur über die Elemente einer Sequenz zu iterieren, auch die Position innerhalb der Sequenz ist von Interesse. Oft trifft man in diesem Fall ein echtes Antipattern an.

>>> # So NICHT!
>>> idx = 0
>>> for item in [ "Ene", "mene", "Muh", "und", "raus", "bist", "du!" ]:
...     print idx, "->", item
...     idx += 1
...
0 -> Ene
1 -> mene
2 -> Muh
3 -> und
4 -> raus
5 -> bist
6 -> du!

Das dieses Pattern nicht nur unschön aussieht, sondern auch gefährlich ist, erkennt man leicht, wenn man sich vorstellt, dass in der Schleife auch noch continue Statements stehen könnten. Dabei ist die Lösung sehr einfach und hört auf die Builtin-Funktion enumerate():

>>> for idx, item in enumerate([ "Ene", "mene", "Muh", "und", "raus", "bist", "du!" ]):
...     print idx, "->", item
...
0 -> Ene
1 -> mene
2 -> Muh
3 -> und
4 -> raus
5 -> bist
6 -> du!

Ein gelegentliche Blick in die Pythondokumentation kann auch nach Jahren noch erhellend wirken.

[Bearbeiten] Ausbruch aus dem Schleifendiktat - continue, break und else

Mit break ist es möglich eine Schleife vorzeitig zu beenden. Sie wird exakt an der Stelle des breaks beendet. continue hingegen, erlaubt es, die Schleife mit der nächsten Iteration fortzuführen, einschließlich der Prüfung der Schleifenbedingung bei while.

else gibt es sowohl für for- als auch für while-Schleifen. Der dem else-Statement folgende Block wird am Ende einer Schleife ausgeführt, also genau dann, wenn die Sequenz einer for-Schleife erschöpft ist oder eine while-Bedingung zu False evaluiert wurde, aber auch nur dann, wenn die Schleife nicht mit break beendet wurde.

>>> for i in "Hallo Welt!":
...     if i == " ":
...         break
...     print i,
...
H a l l o
>>> for i in "Hallo Welt!":
...     if i in "aeiou":
...         continue
...     print i,
...
H l l  W l t !


[Bearbeiten] Das fehlende do while

In anderen Programmiersprachen gibt es eine do while Schleife. In Python gibt es das nicht. Man kann aber mit folgender Konstruktion den gleichen Effekt erzielen:

while True:
   #Anweisungen
   if abbruch=1:
       break


<< Inhaltsverzeichnis

Persönliche Werkzeuge