weitere Werkzeuge zur Steuerung des Programmflusses
Neben der im vorherigen Kapitel vorgestellten while
-Anweisung gibt es Python noch einige weitere Anweisungen zur Steuerung des Programmflusses, die hier in diesem Kapitel erläutert werden.
if Bedingung
Die vielleicht bekannteste und oft genutzte Anweisung ist die if Bedingung. Beispiel:
>>> x = int(input("Bitte einen Integerwert eingeben: "))
Bitte einen Integerwert eingeben: 42
>>> if x < 0:
... x = 0
... print('Negative Zahl zu Null umgewandelt')
... elif x == 0:
... print('Null')
... elif x == 1:
... print('Eins')
... else:
... print('Mehr')
...
Mehr
Es kann keinen, einen oder mehr elif
Teile geben. Der else
Teil ist optional. Das Schlüsselwort elif
ist die Abkürzung für "else if" und ist nützlich, um eine übermäßige Einrückung zu vermeiden. Eine if ... elif ... elif ...
Sequenz ist ein Ersatz für die switch
oder case
Anweisungen, die man in anderen Sprachen findet.
Der Ausdruck hinter if
und elif
muss immer einen Wahrheitswert ergeben, also True oder False. Ist der Ausdruck wahr, wird der if
bzw. elif
Block betreteten und ausgeführt. Wenn nicht, wird er übersprungen und das nächste if
oder elif
überprüft. Ist ein Block mit else
vorhanden wird dieser nur dann ausgeführt, wenn vorher der Wahrheitswert für den if
und alle elif
Ausdrücke falsch (=nicht wahr) waren.
Wenn man denselben Wert mit mehreren Konstanten vergleichen oder nach bestimmten Typen oder Attributen suchen will, kann auch die Anweisung match
nützlich sein, welche weiter unten noch erläutert wird.
for-Anweisung - Schleifen mit for
Die for Anweisung von Python unterscheidet sich ein wenig von dem, was man vielleicht von anderen Programmiersprachen wie C oder Pascal gewohnt ist. Anstatt immer über eine arithmetische Folge von Zahlen zu iterieren (wie in Pascal) oder dem Benutzer die Möglichkeit zu geben, sowohl den Iterationsschritt als auch die Haltebedingung zu definieren (wie in C), iteriert Pythons for
Anweisung über die Elemente einer beliebigen Sammlung (wie eine Liste oder eine Zeichenkette) in der Reihenfolge, in der sie in der Sammlung vorkommen. Zum Beispiel:
>>> words = ['cat', 'window', 'defenestrate']
>>> for word in words:
... print(word, len(w))
...
cat 3
window 6
defenestrate 12
Code, der eine Sammlung ändert, während er über dieselbe Sammlung iteriert, kann schwierig zu bewerkstelligen sein und zu unerwünschten Nebeneffekten führen. Im schlechtesten Fall kann man so eine Endlosschleife erzeugen. Stattdessen ist es normalerweise einfacher, eine Schleife über eine Kopie der Sammlung laufen zu lassen oder eine neue Sammlung zu erstellen:
>>> # Create a sample collection
>>> users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# Strategy: Iterate over a copy
>>> for user, status in users.copy().items():
... if status == 'inactive':
... del users[user]
>>> users
{'Hans': 'active', '景太郎': 'active'}
>>> # Strategy: Create a new collection
>>> active_users = {}
>>> for user, status in users.items():
... if status == 'active':
... active_users[user] = status
>>> active_users
{'Hans': 'active', '景太郎': 'active'}
Das Iterieren über eine Sammlung, im Englischen auch als "iterable" bezeichnet, kommt sehr oft vor und ist "typisch pythonisch". Man iteriert dabei, wenn möglich, immer direkt über die Sammlung, und nicht per Indexzugriff. Auch wenn letzteres das gleiche Ergebnis liefert, gilt es als unpythonisch, als Anti-Pattern und schlechter Still. Beispiel:
>>> primes = [2, 3, 5, 7]
>>> # richtiges iterieren:
>>> for prime in primes:
... print(prime)
...
2
3
5
7
>>> # schlechtes iterieren, macht man so in der Regel nicht
>>> for i in range(len(primes)):
... print(primes[i])
...
2
3
5
7
range-Funktion
Wenn über eine Zahlenfolge iteriert werden muss, ist die eingebaute Funktion range sehr nützlich. Sie erzeugt arithmetische Progressionen:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
range
beginnt standmäßig mit 0 (Null). Der angegebene Endpunkt ist nie Teil der erzeugten Folge. range(10)
erzeugt 10 Werte, die Indizes für Elemente einer Folge der Länge 10 - also die Zahlen von 0 bis 9. Es ist möglich, den Bereich bei einer anderen Zahl beginnen zu lassen, oder eine andere Schrittweite anzugeben. Eine negative Schrittweite ist ebenfalls möglich:
>>> list(range(5, 10))
[5, 6, 7, 8, 9]
>>> list(range(0, 10, 3))
[0, 3, 6, 9]
>>> list(range(-10, -100, -30))
[-10, -40, -70]
Das Iterieren über eine Sammlung kombiniert mit range
und len
geht auch, gilt aber als schlechter Stil:
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb
Bei solchen Anwendungsfällen ist es besser, die eingebaute Funktion enumerate zu nutzen:
>>> for counter, word in enumerate(a):
... print(counter, word)
...
0 Mary
1 had
2 a
3 little
4 lamb
Wenn man versucht, print
auf range
anzuwenden, passiert etwas Unerwartetes:
>>> print(range(10))
range(0, 10)
In vielerlei Hinsicht verhält sich das von range
zurückgegebene Objekt wie eine Liste, aber in Wirklichkeit ist es das nicht. Es ist ein Objekt, das die aufeinanderfolgenden Elemente der gewünschten Sequenz zurückgibt, wenn man darüber iterieren, aber es bildet nicht wirklich die Liste und spart so Platz. Die Werte werden erst generiert, wenn tatsächlich darauf zugegriffen wird.
Ein solches Objekt ist "iterable", auf Deutsch: iterierbar, d.h. es eignet sich als Ziel für Funktionen und Konstrukte, die etwas erwarten, von dem sie aufeinanderfolgende Elemente erhalten können, bis der Vorrat erschöpft ist. Die for-Schleife ist ebenfalls ein solches Konstrukt, während ein Beispiel für eine Funktion, die eine Iterable annimmt, die eingebaute Funktion sum ist:
>>> sum(range(4)) # 0 + 1 + 2 + 3
6
Später werden noch mehr Funktionen zu sehen sein, die Iterables zurückgeben und Iterables als Argumente annehmen. Im Kapitel über Datenstrukturen wird z.B. ausführlicher über Listen gesprochen.
break und continue Anweisungen, else Klauseln in Schleifen
Die Anweisung break bricht aus der innersten umschließenden for
oder while
Schleife aus.
Eine for
oder while
Schleife kann eine else
Klausel enthalten.
In einer for
Schleife wird die else
Klausel ausgeführt, nachdem die Schleife ihre letzte Iteration erreicht hat. In einer while
Schleife wird sie ausgeführt, nachdem die Bedingung der Schleife falsch wird.
In beiden Arten von Schleifen wird die else
Klausel nicht ausgeführt, wenn die Schleife durch ein break
beendet wurde.
Dies wird in der folgenden for
Schleife veranschaulicht, die nach Primzahlen sucht:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
Ja, das ist valider Code. Die else
Klausel gehört zur for
Schleife, nicht zur if
-Anweisung.
Wenn die else
Klausel mit einer Schleife verwendet wird, hat sie mehr mit der else
Klausel einer try
Anweisung gemeinsam als mit der von der if
Anweisungen: die else
Klausel einer try
Anweisung wird ausgeführt, wenn keine Ausnahme auftritt, und die else
Klausel einer Schleife wird ausgeführt, wenn kein break
auftritt. Mehr über die try
Anweisung und Ausnahmen sind im Kapitel über Fehlerbehandlung zu finden.
Die ebenfalls aus C entlehnte Anweisung continue fährt mit der nächsten Iteration der Schleife fort:
>>> for num in range(2, 10):
... if num % 2 == 0:
... print("Found an even number", num)
... continue
... print("Found an odd number", num)
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9
pass Anweisung
Die Anweisung pass bewirkt: nichts. Sie kann verwendet werden, wenn eine Anweisung syntaktisch erforderlich ist, das Programm aber keine Aktion verlangt. Zum Beispiel:
>>> while True:
... pass # Busy-wait Endlosschleife, mit STRG+C beenden
...
pass
wird z.B. typischerweise verwendet, wenn man eine minimale, leere Klasse benötigt:
>>> class MyEmptyClass:
... pass
...
Ein anderer Fall, an dem pass
verwendet werden kann, ist als Platzhalter für eine Funktion oder einen Bedingungskörper, wenn man an neuem Code arbeitet. Dies ermöglicht, auf einer abstrakteren Ebene zu denken. Das pass
wird stillschweigend ignoriert:
>>> def initlog(*args):
... pass # TODO: muss später noch implementiert werden.
...
match Anweisung
Eine match Anweisung nimmt einen Ausdruck und vergleicht dessen Wert mit aufeinanderfolgenden Mustern, die als ein oder mehrere case
Blöcke angegeben sind. Oberflächlich betrachtet ähnelt dies einer switch-Anweisung in C, Java oder JavaScript (und vielen anderen Sprachen), aber es ähnelt eher dem Mustervergleich in Sprachen wie Rust oder Haskell. Nur das erste Muster, das passt, wird ausgeführt, und es können auch Komponenten (Sequenzelemente oder Objektattribute) aus dem Wert in Variablen extrahiert werden.
Die match
Anweisung wurde mit Python 3.10 eingeführt.
Die einfachste Form vergleicht einen Wert mit einem oder mehreren Literalen:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
Zu beachten ist der letzte Block: Der "Variablenname" _
fungiert als Wildcard und führt immer zu einer Übereinstimmung. Wenn kein Fall übereinstimmt, wird keine der Verzweigungen ausgeführt.
Man kann mehrere Literale in einem einzigen Muster mit |
("oder") kombinieren:
case 401 | 403 | 404:
return "Not allowed"
Muster können wie Entpackungszuweisungen aussehen und können zum Binden von Variablen verwendet werden:
# point is an (x, y) tuple
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
Lese dieses Muster sorgfältig! Das erste Muster besteht aus zwei Literalen und kann als Erweiterung des oben gezeigten Literalmusters betrachtet werden. Aber die nächsten beiden Muster kombinieren ein Literal und eine Variable, und die Variable bindet einen Wert aus dem Wert point
. Das vierte Muster erfasst zwei Werte, was es konzeptionell der Entpackungszuweisung (x, y) = point
ähnlich macht.
Wenn man Klassen verwendet, um Daten zu strukturieren, kann man den Klassennamen gefolgt von einer Argumentliste verwenden, die einem Konstruktor ähnelt, jedoch mit der Möglichkeit, Attribute in Variablen zu erfassen:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
Man kann Positionsparameter mit einigen eingebauten Klassen verwenden, die eine Reihenfolge für die Attribute vorgeben (z.B. Datenklassen). Man kann auch eine bestimmte Position für Attribute in Mustern definieren, indem man das spezielle Attribut __match_args__
in der Klasse setzt. Wenn es auf ("x", "y") gesetzt ist, sind die folgenden Muster alle gleichwertig (und binden alle das Attribut y
an die Variable var
):
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
Es empfiehlt sich, die Muster als eine erweiterte Form dessen zu betrachten, was man links von einer Zuweisung schreiben würde, um zu verstehen, welche Variablen auf was gesetzt werden würden. Nur die eigenständigen Namen (wie var
oben) werden durch eine match
Anweisung zugewiesen. Gepunktete Namen (wie foo.bar
), Attributnamen (wie x=
und y=
oben) oder Klassennamen (erkennbar an dem "(...)" neben ihnen wie Point
oben) werden niemals zugewiesen.
Muster können beliebig verschachtelt werden. Wenn man zum Beispiel eine kurze Liste von Punkten hat, die mit __match_args__
ergänzt wurde, kann man sie wie folgt abgleichen:
class Point:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
Man kann eine if
Klausel zu einem Muster hinzufügen, die als "guard" (auf Deutsch: Wächter) bekannt ist. Wenn die Schutzklausel falsch ist, fährt match
mit dem nächsten Fallblock fort. Zu beachten ist, dass die Werteerfassung vor der Auswertung des Guards erfolgt:
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
Einige weitere Features dieser Anweisung sind:
- Wie Entpackungszuweisungen haben Tupel- und Listenmuster genau dieselbe Bedeutung und passen tatsächlich zu beliebigen Sequenzen. Eine wichtige Ausnahme ist, dass sie nicht auf Iteratoren oder Zeichenketten passen.
- Sequenzmuster unterstützen erweitertes Entpacken:
[x, y, *rest]
und(x, y, *rest)
funktionieren ähnlich wie Entpackungszuweisungen. Der Name nach*
kann auch_
sein, sodass(x, y, *_)
mit einer Folge von mindestens zwei Elementen übereinstimmt, ohne die restlichen Elemente zu binden. - Mapping patterns:
{"bandwidth": b, "latency": l}
erfasst die "bandwidth" und "latency" Werte aus einem Wörterbuch. Anders als bei Sequenzmustern werden zusätzliche Schlüssel ignoriert. Ein Entpacken wie**rest
wird ebenfalls unterstützt (aber**_
wäre redundant, daher ist es nicht erlaubt.) - Unterpattern (englisch: subpattern) können über das Schlüsselwort
as
erfasst werden. Im folgenden Beispiel wird das zweite Element der Eingabe alsp2
erfassen (solange die Eingabe eine Folge von zwei Punkten ist)
case (Point(x1, y1), Point(x2, y2) as p2): ...
- Die meisten Literale werden durch Gleichheit verglichen, aber die Singletons
True
,False
undNone
werden durch Identität verglichen. - Muster können benannte Konstanten verwenden. Diese müssen gepunktete Namen sein, damit sie nicht als Capture-Variable interpretiert werden können:
from enum import Enum
class Color(Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")
Für weitere Details kann man die PEP 636 lesen, welche viele weitere Beispiele und Erklärungen enthält.
Funktionen definieren
Im Folgenden wird eine Funktion erstellt, die die Fibonacci-Reihe bis zu einem beliebigen Wert berechnet:
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Now call the function we just defined:
>>> fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
Das Schlüsselwort def leitet eine Funktionsdefinition ein. Diese muss vom Funktionsnamen und der in Klammern gesetzten Liste der formalen Parameter gefolgt werden, wobei auch eine leere Liste (=keine Parameter) erlaubt ist. Die Anweisungen, die den Körper der Funktion bilden, beginnen in der nächsten Zeile und müssen eingerückt werden. Als Einrückung werden wie unter Python üblich vier Leerzeichen empfohlen.
Die erste Anweisung des Funktionskörpers kann optional ein Zeichenkettenliteral sein. Dieses Zeichenkettenliteral ist die Dokumentationszeichenkette der Funktion, genannt "docstring". Mehr über docstrings ist weiter unten in diesem Kapitel zu finden. Es gibt Werkzeuge, die docstrings verwenden, um automatisch Online- oder gedruckte Dokumentation zu erzeugen, oder um den Benutzer interaktiv durch den Code blättern zu lassen. Es gilt als gute Praxis, docstrings in den eigenen Code einzuschließen - sollte man sich am besten direkt zur Gewohnheit machen.
Die Ausführung einer Funktion führt eine neue Symboltabelle ein, die für die lokalen Variablen der Funktion verwendet wird. Genauer gesagt speichern alle Variablenzuweisungen in einer Funktion den Wert in der lokalen Symboltabelle, während Variablenreferenzen zuerst in der lokalen Symboltabelle, dann in den lokalen Symboltabellen der umschließenden Funktionen, dann in der globalen Symboltabelle und schließlich in der Tabelle der eingebauten Namen nachsehen. Globalen Variablen und Variablen von umschließenden Funktionen kann also innerhalb einer Funktion nicht direkt ein Wert zugewiesen werden - es sei denn, globale Variablen werden in einer global
-Anweisung oder Variablen von umschließenden Funktionen in einer nonlocal
aufgeführt, obwohl sie referenziert werden können. Die Verwendung von global
ist aber in den allermeisten Fällen in einem sauber strukturierten Programm überflüssig und sollte von daher vermieden werden. Außerdem kann global
den Zustand eines Programms schwer nachvollziehbar machen.
Die eigentlichen Parameter (Argumente) eines Funktionsaufrufs werden in die lokale Symboltabelle der aufgerufenen Funktion eingeführt, wenn diese aufgerufen wird. Argumente werden also mit Aufruf durch Wert übergeben, wobei der Wert immer ein Objekt Referenz ist, nicht der Wert des Objekts. Wenn eine Funktion eine andere Funktion aufruft oder sich selbst rekursiv aufruft, wird eine neue lokale Symboltabelle für diesen Aufruf erstellt.
Eine Funktionsdefinition verknüpft den Funktionsnamen mit dem Funktionsobjekt in der aktuellen Symboltabelle. Der Interpreter erkennt das Objekt, auf das dieser Name verweist, als eine benutzerdefinierte Funktion. Andere Namen können ebenfalls auf dasselbe Funktionsobjekt verweisen und für den Zugriff auf die Funktion verwendet werden:
>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89
Wenn man aus anderen Sprachen kommt, wird man vielleicht einwenden, dass fib
keine Funktion, sondern eine Prozedur ist, da sie keinen Wert zurückgibt. In der Tat geben auch Funktionen ohne einereturn Anweisung einen Wert zurück, wenn auch einen ziemlich langweiligen. Dieser Wert ist None
, welche in Python ein gültiges Objekt ist. Das Schreiben des Wertes None
wird normalerweise vom Interpreter unterdrückt, wenn es der einzige Wert ist, der geschrieben wird. Dies kann man sehen, wenn man es wirklich will, indem man print
verwenden:
>>> fib(0)
>>> print(fib(0))
None
Es ist einfach, eine Funktion zu schreiben, die eine Liste der Zahlen der Fibonacci-Reihe zurückgibt, anstatt sie auszugeben:
>>> def fib2(n): # return Fibonacci series up to n
... """Return a list containing the Fibonacci series up to n."""
... result = []
... a, b = 0, 1
... while a < n:
... result.append(a)
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # aufrufen von fib2 mit dem Parameter 100
>>> f100 # Ausgabe des Ergebnisses
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Dieses Beispiel zeigt auch einige Features von Python, die bisher noch nicht behandelt wurden:
- Die Anweisung
return
liefert einen Wert aus einer Funktion zurück.return
ohne einen Wert gibt automatischNone
zurück. Wird eine Funktion ohne abschließendesreturn
beendet wird ebenfallsNone
zurück geliefert. - Die Anweisung
result.append(a)
ruft eine Methode des Listenobjektsresult
auf. Eine Methode ist eine Funktion, die zu einem Objekt "gehört" und den Namenobj.Methodenname
trägt, wobeiobj
ein Objekt ist (dies kann ein Ausdruck sein) undMethodenname
der Name einer Methode ist, die durch den Typ des Objekts definiert ist. Verschiedene Typen definieren verschiedene Methoden. Methoden verschiedener Typen können denselben Namen haben, ohne dass dies zu Mehrdeutigkeit führt. Es ist möglich, eigene Objekttypen und Methoden zu definieren, indem man Klassen verwendet, dazu später mehr im Kapitel über Klassen. Die im Beispiel gezeigte Methodeappend
ist für Listenobjekte definiert. Sie fügt ein neues Element am Ende der Liste hinzu. In diesem Beispiel ist sie äquivalent zuErgebnis = Ergebnis + [a]
, aber effizienter.
Hinweis zu Funktionsnamen
Bei der Benennung von Funktionen sollte man darauf achten, dass diese aussagekräftige Namen haben. Dies kommt der Verständlichkeit des geschriebenen Codes zugute. Des Weiteren sollte man darauf achten, dass der Funktionsname nicht einen Namen einer built-in Funktion oder einer importierten Funktion überschreibt. Dies kann zu unerwünschten Nebeneffekten und schwer nachzuvollziehenden Fehlern führen.
Im folgenden Beispiel wird die eingebaute Funktion sum überschrieben:
>>> data = [1, 2, 3]
>>> sum(data)
6
>>> def sum(name):
... print(name + ' is summing things up')
...
>>> sum(data)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in sum
TypeError: can only concatenate list (not "str") to list
>>>
Python besitzt keinen eingebauten Schutz, der verhindert, dass man bestehende Funktionsnamen überschreibt. Primär aus dem Grund, dass es Anwendungsfälle geben könnte, wo genau das gewollt ist (auch wenn diese Fälle rar sind).
Das Überschreiben von Python Schlüsselworten führt dagegen zu einem Fehler, wie zum Beispiel:
>>> def if():
File "<stdin>", line 1
def if():
^^
SyntaxError: invalid syntax
>>> def break(name):
File "<stdin>", line 1
def break(name):
^^^^^
SyntaxError: invalid syntax
mehr zur Definition von Funktionen
Es ist auch möglich, Funktionen mit einer variablen Anzahl von Argumenten zu definieren. Es gibt drei Formen, die miteinander kombiniert werden können.
Argumente mit Vorgabewerten
Die nützlichste Form ist die Angabe eines Standardwerts (Defaultwert) für ein oder mehrere Argumente. Dadurch wird eine Funktion geschaffen, die mit weniger Argumenten aufgerufen werden kann, als für sie definiert sind. Zum Beispiel:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply in {'n', 'no', 'nop', 'nope'}:
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
Diese Funktion kann mit verschiedenen Argumenten aufgerufen werden:
- nur mit dem Pflichtargument
prompt
:ask_ok('Do you really want to quit?')
- mit einem der optionalen Argumente:
ask_ok('OK to overwrite the file?', 2)
- mit allen Argumenten:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
In diesem Beispiel wird das Schlüsselwort in verwendet. Damit wird überprüft, ob eine Sequenz einen bestimmten Wert enthält.
Die Standardwerte werden an der Stelle der Funktionsdefinition im definierenden Bereich ausgewertet, sodass das folgende Beispiel 5 ausgibt:
>>> i = 5
>>> def f(arg=i):
... print(arg)
...
>>> i = 6
>>> f()
5
Wichtiger Hinweis: Der Standardwert wird nur einmal ausgewertet. Es macht einen Unterschied, wenn der Standardwert ein veränderbares Objekt wie eine Liste, ein Wörterbuch oder Instanzen der meisten Klassen ist. Die folgende Funktion akkumuliert zum Beispiel die Argumente, die ihr bei nachfolgenden Aufrufen übergeben werden:
>>> def f(a, L=[]):
... L.append(a)
... return L
...
>>> print(f(1))
>>> print(f(2))
>>> print(f(3))
Dies ergibt die Ausgabe:
[1]
[1, 2]
[1, 2, 3]
Wenn man nicht möchte, dass der Standardwert von nachfolgenden Aufrufen gemeinsam genutzt wird, kann die Funktion stattdessen wie folgt schreiben:
>>> def f(a, L=None):
... if L is None:
... L = []
... L.append(a)
... return L
...
Schlüsselwortargumente
Funktionen können auch mit Schlüsselwortargumente in der Form kwarg=Wert
aufgerufen werden. Zum Beispiel:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
Die Funktion akzeptiert ein erforderliches Argument (voltage
) und drei optionale Argumente (state
, action
und type
). Diese Funktion kann auf eine der folgenden Arten aufgerufen werden:
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
Die folgenden Aufrufe sind nicht gültig und führen zu einem Fehler:
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
In einem Funktionsaufruf müssen die Schlüsselwortargumente auf die Positionsargumente folgen. Alle übergebenen Schlüsselwortargumente müssen mit den Argumenten der Funktion übereinstimmen (z.B. actor
ist kein gültiges Argument für die Funktion parrot
) und ihre Reihenfolge ist egal. Dies schließt auch nicht-optionale Argumente ein (z.B. parrot(voltage=1000)
ist ebenfalls gültig). Kein Argument darf mehr als einmal einen Wert erhalten. Hier ein Beispiel, das an dieser Einschränkung scheitert:
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for argument 'a'
Wenn ein endgültiger formaler Parameter der Form **name
vorhanden ist, erhält er ein Wörterbuch (siehe Datentyp Mapping), das alle Schlüsselwortargumente mit Ausnahme derjenigen enthält, die einem formalen Parameter entsprechen. Dies kann mit einem formalen Parameter der Form *name
(beschrieben im nächsten Abschnitt) kombiniert werden, der ein Tupel erhält, dass die Positionsargumente jenseits der formalen Parameterliste enthält. Dabei muss *name
immer vor **name
stehen. Im folgenden Beispiel ist eine Funktion wie diese definieren:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
Sie kann wie folgt aufgerufen werden:
>>> cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
Der Aufruf ergibt folgende Ausgabe:
-- Do you have any Limburger?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
Zu beachten ist, dass die Reihenfolge, in der die Schlüsselwortargumente ausgegeben werden, garantiert der Reihenfolge entspricht, in der sie im Funktionsaufruf angegeben wurden.
spezielle Parameter
Standardmäßig können Argumente an eine Python-Funktion entweder nach Position oder explizit nach Schlüsselwort übergeben werden. Aus Gründen der Lesbarkeit und der Leistung ist es sinnvoll, die Art und Weise, wie Argumente übergeben werden können, einzuschränken, sodass ein Entwickler nur einen Blick auf die Funktionsdefinition werfen muss, um festzustellen, ob Elemente per Position, per Position oder Schlüsselwort oder per Schlüsselwort übergeben werden.
Eine Funktionsdefinition kann so aussehen:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
Hierbei sind /
und *
optional. Falls verwendet, geben diese Symbole die Art des Parameters an, wie die Argumente an die Funktion übergeben werden können: nur Positionsparameter, Positions- oder Schlüsselwortparameter und nur Schlüsselwortparameter. Schlüsselwortparameter werden auch als "benannte Parameter" bezeichnet.
Wenn /
und *
in der Funktionsdefinition nicht vorhanden sind, können Argumente an eine Funktion durch Position oder durch Schlüsselwort übergeben werden.
Positions- oder Schlüsselwortargumente?
Bei näherer Betrachtung ist es möglich, bestimmte Parameter als positional-only zu kennzeichnen. Bei positional-only ist die Reihenfolge der Parameter wichtig, und die Parameter können nicht per Schlüsselwort übergeben werden. Nur-Positions-Parameter werden vor einem /
(Schrägstrich) platziert. Das /
wird verwendet, um die Nur-Positions-Parameter logisch von den übrigen Parametern zu trennen. Steht kein /
in der Funktionsdefinition, so gibt es auch keine Parameter, die nur an einer bestimmten Stelle stehen.
Parameter, die dem /
folgen, können Positions- oder Schlüsselwort oder Nur-Schlüsselwort sein.
nur Schlüsselwortargumente
Um Parameter als keyword-only zu kennzeichnen, was bedeutet, dass die Parameter per Schlüsselwortargument übergeben werden müssen, setzt man ein *
in die Argumentenliste direkt vor den ersten keyword-only Parameter.
Beispiele für Funktionen
Die folgenden Beispiele für Funktionsdefinitionen veranschaulichen dies. Man beachte dabei die Markierungen /
und *
:
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
Die erste Funktionsdefinition standard_arg
ist die bekannteste Form, enthält keine Einschränkungen bezüglich der Aufrufkonvention und Argumente können durch Position oder Schlüsselwort übergeben werden:
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
Die zweite Funktion pos_only_arg
ist auf die Verwendung von Positionsparametern beschränkt, da sie ein /
in der Funktionsdefinition enthält:
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'
Die dritte Funktion kwd_only_args
erlaubt nur Schlüsselwortargumente, die durch ein *
in der Funktionsdefinition gekennzeichnet sind:
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
Und die letzte verwendet alle drei Aufrufkonventionen in der gleichen Funktionsdefinition:
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'
Zu guter Letzt noch diese Funktionsdefinition, die eine potentielle Kollision zwischen dem Positionsargument name
und **kwds
aufweist, welche name
als Schlüssel hat:
>>> def foo(name, **kwds):
... return 'name' in kwds
...
Es gibt keinen möglichen Aufruf, der True
zurückgibt, da das Schlüsselwort name
immer an den ersten Parameter gebunden ist. Zum Beispiel:
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
Aber mit /
(nur Positionsargumente) ist es möglich, da es name
als Positionsargument und name
als Schlüssel in den Schlüsselwortargumenten erlaubt:
>>> def foo(name, /, **kwds):
... return 'name' in kwds
...
>>> foo(1, **{'name': 2})
True
Mit anderen Worten: die Namen von reinen Positionsparametern können in **kwds
ohne Mehrdeutigkeit verwendet werden.
Zusammenfasung
Der Anwendungsfall bestimmt, welche Parameter in der Funktionsdefinition zu verwenden sind:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
Als Richtlinie:
- Man verwendet "positional-only", wenn man möchte, dass der Name der Parameter für den Benutzer nicht verfügbar ist. Dies ist nützlich, wenn Parameternamen keine wirkliche Bedeutung haben, wenn man die Reihenfolge der Argumente erzwingen will, wenn die Funktion aufgerufen wird oder wenn man einige Positionsparameter und beliebige Schlüsselwörter verwenden muss.
- Man verwendet nur Schlüsselwörter, wenn die Namen eine Bedeutung haben und die Funktionsdefinition durch die explizite Angabe von Namen verständlicher ist oder man verhindern will, dass sich Benutzer auf die Position des übergebenen Arguments verlassen.
- Für eine API sollte man nur Positionsangaben verwenden, um zu verhindern, dass die API kpautt ist, wenn der Name des Parameters in der Zukunft geändert werden sollte.
beliebige Argumentlisten
Die am seltensten verwendete Option ist schließlich die Angabe, dass eine Funktion mit einer beliebigen Anzahl von Argumenten aufgerufen werden kann. Diese Argumente werden in ein Tupel verpackt. Vor der variablen Anzahl von Argumenten können null oder mehr normale Argumente auftreten.
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
Normalerweise stehen diese variablen Argumente an letzter Stelle in der Liste der formalen Parameter, da sie alle verbleibenden Eingabeargumente, die an die Funktion übergeben werden, auffüllen. Alle formalen Parameter, die nach dem Parameter *args
stehen, sind 'keyword-only'-Argumente, was bedeutet, dass sie nur als Schlüsselwörter und nicht als Positionsargumente verwendet werden können:
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
Entpacken von Argumentlisten
Die umgekehrte Situation tritt ein, wenn die Argumente bereits in einer Liste oder einem Tupel vorliegen, aber für einen Funktionsaufruf, der separate Positionsargumente erfordert, ausgepackt werden müssen. Zum Beispiel erwartet die eingebaute Funktion range
separate start und stop Argumente. Wenn sie nicht separat verfügbar sind, schreibt man den Funktionsaufruf mit dem *
Operator, um die Argumente aus einer Liste oder einem Tupel auszupacken:
>>> list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # call with arguments unpacked from a list
[3, 4, 5]
Auf die gleiche Weise können Wörterbücher Schlüsselwortargumente mit dem **
Operator liefern:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
Lambda Ausdrücke
Kleine anonyme Funktionen können mit dem Schlüsselwort lambda erstellt werden. Diese Funktion gibt die Summe ihrer beiden Argumente zurück: lambda a, b: a+b
. Lambda-Funktionen können überall dort verwendet werden, wo Funktionsobjekte benötigt werden. Sie sind syntaktisch auf einen einzigen Ausdruck beschränkt. Semantisch gesehen sind sie nur ein syntaktischer Zucker für eine normale Funktionsdefinition. Wie verschachtelte Funktionsdefinitionen können Lambda-Funktionen auf Variablen aus dem enthaltenden Bereich verweisen:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
Im obigen Beispiel wird ein Lambda-Ausdruck verwendet, um eine Funktion zurückzugeben. Eine andere Verwendung ist die Übergabe einer kleinen Funktion als Argument:
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
docstrings
Im Folgenden werden einige Konventionen für den Inhalt und die Formatierung von docstrings (=Dokumentationsstrings) erläutert.
Die erste Zeile sollte immer eine kurze, prägnante Zusammenfassung des Zwecks des Objekts sein. Der Kürze halber sollte man nicht explizit den Namen oder den Typ des Objekts angeben, da dieser auf andere Weise verfügbar ist - es sei denn, der Name ist zufällig ein Verb, das die Funktion einer Funktion beschreibt. Diese Zeile sollte mit einem Großbuchstaben beginnen und mit einem Punkt enden.
Bei mehrzeiligen docstrings sollte die zweite Zeile leer sein, um die Zusammenfassung optisch vom Rest der Beschreibung zu trennen. Die folgenden Zeilen sollten aus einem oder mehreren Absätzen bestehen, die die Aufrufkonventionen des Objekts, seine Nebeneffekte usw. beschreiben.
Der Python-Parser entfernt keine Einrückung aus mehrzeiligen String-Literalen in Python, sodass Werkzeuge, die Dokumentation verarbeiten, die Einrückung auf Wunsch entfernen müssen. Dies geschieht nach der folgenden Konvention. Die erste nicht leere Zeile nach der ersten Zeile der Zeichenkette bestimmt den Grad der Einrückung für die gesamte Dokumentationszeichenkette. Man kann nicht die erste Zeile verwenden, da sie in der Regel an die öffnenden Anführungszeichen der Zeichenkette angrenzt, sodass ihre Einrückung im Zeichenfolgenliteral nicht ersichtlich ist. Leerzeichen, die dieser Einrückung "entsprechen", werden dann vom Anfang aller Zeilen der Zeichenkette entfernt. Zeilen, die weniger eingerückt sind, sollten nicht vorkommen, aber wenn sie vorkommen, sollten alle führenden Leerzeichen entfernt werden.
Im Folgenden ein Beispiel für einen mehrzeiligen docstring:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
Annotation von Funktionen
Funktionsannotationen sind vollständig optionale Metadaten-Informationen über die von benutzerdefinierten Funktionen verwendeten Typen (siehe PEP3107 und PEP484 für weitere Informationen).
Annotationen werden im __annotations__
Attribut der Funktion als Wörterbuch gespeichert und haben keine Auswirkung auf irgendeinen anderen Teil der Funktion. Parameter-Annotationen werden durch einen Doppelpunkt nach dem Parameternamen definiert, gefolgt von einem Ausdruck, der den Wert der Annotation auswertet. Rückgabe-Anmerkungen werden durch ein Literal ->
, gefolgt von einem Ausdruck, zwischen der Parameterliste und dem Doppelpunkt, der das Ende der def
Anweisung bezeichnet, definiert. Das folgende Beispiel hat ein erforderliches Argument, ein optionales Argument und den Rückgabewert mit der Anmerkung:
>>> def f(ham: str, eggs: str = 'eggs') -> str:
... print("Annotations:", f.__annotations__)
... print("Arguments:", ham, eggs)
... return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'
Coding Style - immer wichtig
Jetzt, wo man schon längere und komplexere Pythonprogramme schreiben kann, ist ein guter Zeitpunkt gekommen, um über den Coding Style zu sprechen. Die meisten Sprachen können in verschiedenen Stilen geschrieben (oder besser gesagt, formatiert) werden. Einige sind besser lesbar als andere. Es ist immer eine sehr gute Idee, es anderen leicht zu machen, den eigenen Code zu lesen - und ein guter Programmierstil hilft dabei enorm.
Für Python ist die PEP8 der in der Python-Welt maßgebliche Coding Style (quasi der "heilige Gral" für gut lesbaren Code). Er fördert einen sehr lesbaren und angenehmen Programmierstil. Jeder Python-Entwickler sollte ihn irgendwann einmal lesen. Im Folgenden sind die wichtigsten Punkte aus der PEP8 aufgelistet:
- Man verwendet zur Einrückung 4 Leerzeichen und keine Tabulatoren! 4 Leerzeichen sind ein guter Kompromiss zwischen kleinem Einzug (ermöglicht größere Verschachtelungstiefe) und großem Einzug (leichter zu lesen). Tabulatoren stiften Verwirrung und sollten daher weggelassen werden.
- Die Zeilenlänge sollte nicht mehr als 79 Zeichen betragen. Dies hilft Benutzern mit kleinen Bildschirmen und ermöglicht es, mehrere Code-Dateien nebeneinander auf größeren Bildschirmen.
- Man verwendet Leerzeilen, um Funktionen und Klassen sowie größere Codeblöcke innerhalb von Funktionen zu trennen.
- Wenn möglich setzt man Kommentare in eine eigene Zeile.
- Man verwendt Docstrings.
- Man verwendet Leerzeichen um Operatoren und nach Kommas, aber nicht direkt innerhalb von Klammerkonstrukten:
a = f(1, 2) + g(3, 4)
. - Man benennt seine Klassen und Funktionen einheitlich. Die Konvention ist,
UpperCamelCase
für Klassen undlowercase_with_underscores
für Funktionen und Methoden zu verwenden. Man verwendet immerself
als Name für das erste Methodenargument (mehr dazu später im Kapitel über Klassen und Methoden). - Man verwendet keine ausgefallenen Kodierungen, wenn der Code in internationalen Umgebungen verwendet werden soll. Pythons Standard UTF-8 oder sogar einfaches ASCII funktionieren in jedem Fall am besten.
- Man verwendet ebenfalls keine Nicht-ASCII-Zeichen in Bezeichnern, wenn auch nur die geringste Chance besteht, dass Menschen, die eine andere Sprache sprechen, den Code lesen oder pflegen werden.
- nächstes Kapitel: Datentypen und -strukturen
- vorheriges Kapitel: eine lockere Einführung in Python