4. Mehr Werkzeuge zur Ablaufsteuerung¶
Neben der eben vorgestellten while-Anweisung verwendet Python noch ein paar weitere, die uns in diesem Kapitel begegnen werden.
4.1. if-Anweisungen¶
Die wohl bekannteste Anweisungsart ist die if-Anweisung. Zum Beispiel:
x = int(input("Bitte gib eine ganzzahlige Zahl ein: "))
Bitte gib eine ganzzahlige Zahl ein: 42
if x < 0:
... x = 0
... print('Negativ in Null geändert')
... elif x == 0:
... print('Null')
... elif x == 1:
... print('Eins')
... else:
... print('Mehr')
...
Mehr
Es kann kein oder mehrere elif-Teile geben, und der else-Teil ist optional. Das Schlüsselwort ‚elif‘ ist die Abkürzung für ‚else if‘ und ist nützlich, um übermäßige Einrückungen zu vermeiden. Eine if … elif … elif …-Sequenz ersetzt die switch- oder case-Anweisungen, die man aus anderen Sprachen kennt.
Wenn du denselben Wert mit mehreren Konstanten vergleichst oder auf bestimmte Typen oder Attribute prüfst, findest du möglicherweise auch die match-Anweisung nützlich. Für weitere Details siehe match-Anweisungen.
4.2. for-Anweisungen¶
Die for-Anweisung in Python unterscheidet sich ein wenig von dem, was du vielleicht aus C oder Pascal gewohnt bist. Anstatt immer über eine arithmetische Zahlenfolge zu iterieren (wie in Pascal) oder der nutzenden Person die Möglichkeit zu geben, sowohl den Iterationsschritt als auch die Abbruchbedingung zu definieren (wie in C), iteriert Pythons for-Anweisung über die Elemente einer beliebigen Sequenz (eine Liste oder eine Zeichenkette) in der Reihenfolge, in der sie in der Sequenz erscheinen. Zum Beispiel (ohne Wortspiel):
>>> # Einige Zeichenketten messen:
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words:
... print(w, len(w))
...
cat 3
window 6
defenestrate 12
Code, der eine Sammlung verändert, während über dieselbe Sammlung iteriert wird, kann knifflig sein. Stattdessen ist es gewöhnlich einfacher, über eine Kopie der Sammlung zu iterieren oder eine neue Sammlung zu erstellen:
# Eine Beispiel-Sammlung erstellen
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# Strategie: Über eine Kopie iterieren
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
# Strategie: Eine neue Sammlung erstellen
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
4.3. Die Funktion range()¶
Wenn du über eine Zahlenfolge iterieren musst, ist die eingebaute Funktion range() praktisches Hilfsmittel. Sie erzeugt arithmetische Folgen:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
Der angegebene Endpunkt ist nie Teil der erzeugten Sequenz; range(10) erzeugt 10 Werte, welche genau den gültigen Indizes für die Elemente einer Sequenz der Länge 10 entsprechen. Es ist möglich, die Spanne bei einer anderen Zahl beginnen zu lassen oder einen anderen Schrittabstand anzugeben (sogar negativ; manchmal wird dies als ‚Schrittweite‘ bezeichnet):
>>> 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]
Um über die Indizes einer Sequenz zu iterieren, kannst du range() und len() wie folgt kombinieren:
>>> 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
In den meisten dieser Fälle ist es jedoch praktischer, die Funktion enumerate() zu verwenden, siehe Schleifentechniken.
Etwas Merkwürdiges passiert, wenn du ein range einfach ausgibst:
>>> range(10)
range(0, 10)
In vielerlei Hinsicht verhält sich das von range() zurückgegebene Objekt wie eine Liste, aber das ist es in Wahrheit nicht. Es ist ein Objekt, das die aufeinanderfolgenden Elemente der gewünschten Sequenz zurückgibt, wenn man darüber iteriert, aber es erstellt die Liste nicht wirklich und spart so Speicherplatz.
We say such an object is iterable, that is, suitable as a target for
functions and constructs that expect something from which they can
obtain successive items until the supply is exhausted. We have seen that
the for statement is such a construct, while an example of a function
that takes an iterable is sum():
>>> sum(range(4)) # 0 + 1 + 2 + 3
6
Später werden wir weitere Funktionen sehen, die Iterables zurückgeben und Iterables als Argumente entgegennehmen. Im Kapitel Datenstrukturen werden wir genauer über list() sprechen.
4.4. break- und continue-Anweisungen¶
Die break-Anweisung bricht aus der innersten umschließenden for- oder while-Schleife aus:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(f"{n} equals {x} * {n//x}")
... break
...
4 equals 2 * 2
6 equals 2 * 3
8 equals 2 * 4
9 equals 3 * 3
Die continue-Anweisung setzt die Ausführung mit der nächsten Iteration der Schleife fort:
... print(f"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
4.5. else-Klauseln bei Schleifen¶
In einer for- oder while-Schleife kann die break-Anweisung mit einer else-Klausel kombiniert werden. Wenn die Schleife beendet wird, ohne dass break ausgeführt wurde, wird die else-Klausel ausgeführt.
In einer for-Schleife wird die else-Klausel ausgeführt, nachdem die Schleife ihre letzte Iteration beendet hat, also wenn kein break aufgetreten ist.
In einer while-Schleife wird sie ausgeführt, nachdem die Bedingung der Schleife falsch wird.
In beiden Schleifenarten wird die else-Klausel nicht ausgeführt, wenn die Schleife durch ein break abgebrochen wurde. Andere Arten, die Schleife vorzeitig zu beenden, wie etwa ein return oder eine ausgelöste Ausnahme (Exception), überspringen die Ausführung der else-Klausel natürlich ebenfalls.
Dies wird in der folgenden for-Schleife veranschaulicht, die nach Primzahlen sucht:
... 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 der korrekte Code. Sieh genau hin: Die else-Klausel gehört zur for-Schleife, nicht zur if-Anweisung.)
Eine Möglichkeit, sich die else-Klausel vorzustellen, besteht darin, sie gedanklich mit dem if innerhalb der Schleife zu koppeln. Wenn die Schleife ausgeführt wird, durchläuft sie eine Sequenz wie if/if/if/else. Das if befindet sich innerhalb der Schleife und wird mehrmals angetroffen. Wenn die Bedingung irgendwann wahr ist, erfolgt ein break. Wenn die Bedingung nie wahr ist, wird die else-Klausel außerhalb der Schleife ausgeführt.
Wenn sie mit einer Schleife verwendet wird, hat die else-Klausel mehr mit der else-Klausel einer try-Anweisung gemeinsam als mit der von 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. Für weitere Informationen zur try-Anweisung und zu Ausnahmen siehe Behandlung von Ausnahmen.
4.6. pass-Anweisungen¶
Die pass-Anweisung tut nichts. Sie kann verwendet werden, wenn eine Anweisung syntaktisch erforderlich ist, das Programm aber keine Aktion erfordert. Zum Beispiel:
>>> while True:
... pass # Warten auf Tastaturunterbrechung (Strg+C)
...
Dies wird häufig verwendet, um minimale Klassen zu erstellen:
>>> class MyEmptyClass:
... pass
...
Ein weiterer Ort, an dem pass verwendet werden kann, ist als Platzhalter für einen Funktionsrumpf oder einen bedingten Zweig, wenn Sie an neuem Code arbeiten. So können Sie weiterhin auf einer abstrakteren Ebene denken. Das pass wird stillschweigend ignoriert:
>>> def initlog(*args):
... pass # Denken Sie daran, dies zu implementieren!
...
Für diesen letzten Fall verwenden viele Leute das Ellipsis-Literal ... anstelle von pass. Diese Verwendung hat für Python keine besondere Bedeutung und ist kein Teil der Sprachdefinition (Sie könnten hier jeden konstanten Ausdruck verwenden), aber ... wird konventionell ebenfalls als Platzhalter für einen Rumpf verwendet. Siehe Das Ellipsis-Objekt.
4.7. match-Anweisungen¶
A match statement takes an expression and compares its value to successive
patterns given as one or more case blocks. This is superficially
similar to a switch statement in C, Java or JavaScript (and many
other languages), but it’s more similar to pattern matching in
languages like Rust or Haskell. Only the first pattern that matches
gets executed and it can also extract components (sequence elements
or object attributes) from the value into variables.
Die einfachste Form vergleicht einen Subjektwert 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"
Note the last block: the „variable name“ _ acts as a wildcard and
never fails to match. If no case matches, none of the branches is executed.
Sie können mehrere Literale in einem einzigen Muster mithilfe von | ("oder") kombinieren:
case 401 | 403 | 404:
return "Not allowed"
Muster können wie Entpackungs-Zuweisungen aussehen und verwendet werden, um Variablen zu binden:
# point ist ein (x, y)-Tupel
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")
Schauen Sie sich das genau an! Das erste Muster hat zwei Literale und kann als Erweiterung des oben gezeigten Literal-Musters betrachtet werden. Die nächsten beiden Muster kombinieren jedoch ein Literal und eine Variable, und die Variable bindet einen Wert aus dem Subjekt (point). Das vierte Muster erfasst zwei Werte, was es konzeptionell ähnlich wie das Entpacken einer Zuweisung (x, y) = point macht.
Wenn Sie Klassen zur Strukturierung Ihrer Daten verwenden, können Sie den Klassennamen gefolgt von einer Argumentliste verwenden, die einem Konstruktor ähneln, jedoch die Möglichkeit bieten, 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")
Sie können Positions-Parameter mit einigen eingebauten Klassen verwenden, die eine Reihenfolge für ihre Attribute festlegen (z. B. dataclasses). Sie können auch eine spezifische Position für Attribute in Mustern definieren, indem Sie das spezielle Attribut __match_args__ in Ihren Klassen setzen. Wenn es auf ("x", "y") gesetzt ist, sind die folgenden Muster alle äquivalent (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)
Eine empfohlene Art, Muster zu lesen, besteht darin, sie als eine erweiterte Form dessen zu betrachten, was Sie auf die linke Seite einer Zuweisung setzen würden, um zu verstehen, welche Variablen auf was gesetzt werden. Nur den einzeln stehenden Namen (wie var oben) wird von einer match-Anweisung etwas zugewiesen. Namen mit Punkten (wie foo.bar), Attributnamen (die x= und y= oben) oder Klassennamen (erkennbar am „"(…)" daneben wie Point oben) wird niemals etwas zugewiesen.
Muster können beliebig verschachtelt werden. Wenn wir beispielsweise eine kurze Liste von Point-Objekten haben, denen __match_args__ hinzugefügt wurde, könnten wir 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")
Wir können einem Muster eine if-Klausel hinzufügen, die als "Guard(Wächter) bezeichnet wird. Wenn der Guard nicht zutrifft, fährt match mit dem nächsten case-Block fort. Beachten Sie, dass das Erfassen der Werte erfolgt, bevor der Guard ausgewertet wird:
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 Hauptmerkmale dieser Anweisung:
Wie bei Zuweisungen durch Entpacken haben Tupel- und Listenmuster exakt dieselbe Bedeutung und passen tatsächlich auf beliebige 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 Zuweisungen durch Entpacken. Der Name nach*darf auch_sein, sodass(x, y, *_)auf eine Sequenz von mindestens zwei Elementen passt, ohne die verbleibenden Elemente zu binden.Mapping-Muster:
{\"bandwidth\": b, \"latency\": l}fängt die Werte\"bandwidth\"und\"latency\"aus einem Dictionary ab. Anders als bei Sequenzmustern werden zusätzliche Schlüssel ignoriert. Ein Entpacken wie**restwird ebenfalls unterstützt. (Aber**_wäre redundant und ist daher nicht erlaubt.)Untermuster können mit dem Schlüsselwort
asabgefangen werden:case (Point(x1, y1), Point(x2, y2) as p2): ...
fängt das zweite Element der Eingabe als
p2ab (solange die Eingabe eine Sequenz aus zwei Punkten ist)Die meisten Literale werden auf Gleichheit verglichen, die Singletons
True,FalseundNonejedoch auf Identität.Muster dürfen benannte Konstanten verwenden. Diese müssen punktierte Namen sein, um zu verhindern, dass sie als Erfassungsvariable interpretiert werden:
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 eine ausführlichere Erklärung und weitere Beispiele können Sie einen Blick in PEP 636 werfen, das im Tutorial-Format verfasst ist.
4.8. Funktionen definieren¶
Wir können eine Funktion erstellen, die die Fibonacci-Folge bis zu einer beliebigen Grenze ausgibt:
>>> def fib(n): # Fibonacci-Folge kleiner als n ausgeben
... """Fibonacci-Folge kleiner als n ausgeben."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Die eben definierte Funktion aufrufen:
>>> 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 Funktions*definition* ein. Darauf müssen der Funktionsname und die in Klammern gesetzte Liste der formalen Parameter folgen. Die Anweisungen, die den Funktionskörper bilden, beginnen in der nächsten Zeile und müssen eingerückt sein.
Die erste Anweisung des Funktionskörpers kann optional ein Zeichenketten-Literal sein. Dieses Zeichenketten-Literal ist der Dokumentationsstring der Funktion, oder Docstring. (Mehr über Docstrings findet sich im Abschnitt Dokumentations-Zeichenketten.) Es gibt Werkzeuge, die Docstrings verwenden, um automatisch Online- oder gedruckte Dokumentation zu erzeugen oder es dem Benutzer ermöglichen, interaktiv durch den Code zu navigieren; es gehört zum guten Stil, Docstrings in geschriebenen Code einzufügen, gewöhnen Sie es sich also am besten an.
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; wohingegen Variablenreferenzen zuerst in der lokalen Symboltabelle suchen, dann in den lokalen Symboltabellen umschließender Funktionen, danach in der globalen Symboltabelle und schließlich in der Tabelle der eingebauten Namen. Daher kann globalen Variablen und Variablen umschließender Funktionen innerhalb einer Funktion nicht direkt ein Wert zugewiesen werden (außer wenn globale Variablen in einer global-Anweisung oder Variablen umschließender Funktionen in einer nonlocal-Anweisung benannt sind), obwohl auf sie verwiesen werden darf.
Die tatsächlichen Parameter (Argumente) eines Funktionsaufrufs werden beim Aufruf in die lokale Symboltabelle der aufgerufenen Funktion eingefügt. Daher werden Argumente per Call by Value übergeben (wobei der Wert immer eine Objekt*referenz* ist, nicht der Wert des Objekts). [1] Wenn eine Funktion eine andere Funktion aufruft oder sich selbst rekursiv aufruft, wird für diesen Aufruf eine neue lokale Symboltabelle erstellt.
Eine Funktionsdefinition verknüpft den Funktionsnamen mit dem Funktionsobjekt in der aktuellen Symboltabelle. Der Interpreter erkennt das Objekt, auf das dieser Name zeigt, als benutzerdefinierte Funktion an. Andere Namen können ebenfalls auf dasselbe Funktionsobjekt zeigen und verwendet werden, um auf die Funktion zuzugreifen:
>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89
Wenn Sie von anderen Sprachen kommen, könnten Sie einwenden, dass fib keine Funktion, sondern eine Prozedur ist, da sie keinen Wert zurückgibt. Tatsächlich geben selbst Funktionen ohne return-Anweisung einen Wert zurück, wenn auch einen eher langweiligen. Dieser Wert heißt None (es ist ein eingebauter Name). Die Ausgabe des Werts None wird vom Interpreter normalerweise unterdrückt, wenn er der einzige ausgegebene Wert wäre. Sie können ihn sehen, wenn Sie es wirklich möchten, indem Sie print() verwenden:
>>> fib(0)
>>> print(fib(0))
None
Es ist einfach, eine Funktion zu schreiben, die eine Liste der Zahlen der Fibonacci-Folge zurückgibt, anstatt sie auszugeben:
>>> def fib2(n): # Fibonacci-Folge bis n zurückgeben
... """Gibt eine Liste zurück, die die Fibonacci-Folge bis n enthält."""
... result = []
... a, b = 0, 1
... while a < n:
... result.append(a) # siehe unten
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # aufrufen
>>> f100 # das Ergebnis ausgeben
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Dieses Beispiel demonstriert, wie gewohnt, einige neue Python-Funktionen:
Die
return-Anweisung gibt einen Wert aus einer Funktion zurück.returnohne Ausdrucksargument gibtNonezurück. Das Erreichen des Endes einer Funktion gibt ebenfallsNonezurück.Die Anweisung
result.append(a)ruft eine Methode des Listenobjektsresultauf. Eine Methode ist eine Funktion, die zu einem Objekt »gehört« und den Namenobj.methodennameträgt, wobeiobjein Objekt ist (dies kann ein Ausdruck sein) undmethodennameder Name einer Methode ist, die durch den Typ des Objekts definiert wird. Verschiedene Typen definieren unterschiedliche Methoden. Methoden verschiedener Typen können denselben Namen haben, ohne dass es zu Mehrdeutigkeiten kommt. (Es ist möglich, eigene Objekttypen und Methoden mittels Klassen zu definieren, siehe 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 zuresult = result + [a], aber effizienter.
4.9. Mehr zum Definieren von Funktionen¶
Es ist auch möglich, Funktionen mit einer variablen Anzahl von Argumenten zu definieren. Es gibt drei Formen, die kombiniert werden können.
4.9.1. Standardwerte für Argumente¶
Die nützlichste Form ist die Angabe eines Standardwerts für ein oder mehrere Argumente. Dies erzeugt eine Funktion, die mit weniger Argumenten aufgerufen werden kann, als sie definiert ist zu erlauben. 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 auf verschiedene Arten aufgerufen werden:
nur das erforderliche Argument übergeben:
ask_ok('Do you really want to quit?')eines der optionalen Argumente übergeben:
ask_ok('OK to overwrite the file?', 2)oder sogar alle Argumente übergeben:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
Dieses Beispiel führt auch das Schlüsselwort in ein. Dies prüft, ob eine Sequenz einen bestimmten Wert enthält oder nicht.
Die Standardwerte werden zum Zeitpunkt der Funktionsdefinition im definierenden Gültigkeitsbereich ausgewertet, so dass
i = 5
def f(arg=i):
print(arg)
i = 6
f()
wird 5 ausgeben.
Wichtige Warnung: Der Standardwert wird nur einmal ausgewertet. Dies macht einen Unterschied, wenn der Standardwert ein veränderbares Objekt wie eine Liste, ein Dictionary oder Instanzen der meisten Klassen ist. Die folgende Funktion sammelt beispielsweise die ihr übergebenen Argumente bei nachfolgenden Aufrufen an:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
Dies gibt Folgendes aus
[1]
[1, 2]
[1, 2, 3]
Wenn Sie nicht möchten, dass der Standardwert zwischen nachfolgenden Aufrufen geteilt wird, können Sie die Funktion stattdessen so schreiben:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
4.9.2. Schlüsselwortargumente¶
Funktionen können auch mittels Schlüsselwortargumenten der Form kwarg=wert aufgerufen werden. Die folgende Funktion beispielsweise:
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, "!")
akzeptiert ein erforderliches Argument (voltage) und drei optionale Argumente (state, action und type). Diese Funktion kann auf jede der folgenden Arten aufgerufen werden:
parrot(1000) # 1 Positionsargument
parrot(voltage=1000) # 1 Schlüsselwortargument
parrot(voltage=1000000, action='VOOOOOM') # 2 Schlüsselwortargumente
parrot(action='VOOOOOM', voltage=1000000) # 2 Schlüsselwortargumente
parrot('a million', 'bereft of life', 'jump') # 3 Positionsargumente
parrot('a thousand', state='pushing up the daisies') # 1 Positionsargument, 1 Schlüsselwortargument
aber alle folgenden Aufrufe wären ungültig:
parrot() # erforderliches Argument fehlt
parrot(voltage=5.0, 'dead') # Nicht-Schlüsselwortargument nach einem Schlüsselwortargument
parrot(110, voltage=220) # doppelter Wert für dasselbe Argument
parrot(actor='John Cleese') # unbekanntes Schlüsselwortargument
In einem Funktionsaufruf müssen Schlüsselwortargumente auf Positionsargumente folgen. Alle übergebenen Schlüsselwortargumente müssen mit einem der von der Funktion akzeptierten Argumente übereinstimmen (z. B. ist actor kein gültiges Argument für die Funktion parrot), und ihre Reihenfolge ist nicht von Bedeutung. Dies schließt auch nicht-optionale Argumente ein (z. B. ist auch parrot(voltage=1000) gültig). Kein Argument darf mehr als einmal einen Wert erhalten. Hier ist ein Beispiel, das aufgrund dieser Einschränkung fehlschlägt:
>>> 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 abschließender formaler Parameter der Form **name vorhanden ist, erhält dieser ein Dictionary (siehe Mapping-Typen – dict), das alle Schlüsselwortargumente enthält, mit Ausnahme derjenigen, die einem formalen Parameter entsprechen. Dies kann mit einem formalen Parameter der Form *name kombiniert werden (beschrieben im nächsten Unterabschnitt), welcher ein Tupel erhält, das die Positionsargumente außerhalb der formalen Parameterliste enthält. (*name muss vor **name stehen.) Wenn wir beispielsweise eine Funktion wie folgt 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 könnte 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")
und sie würde natürlich Folgendes ausgeben:
-- 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
Beachte, dass die Reihenfolge, in der die Schlüsselwortargumente ausgegeben werden, garantiert der Reihenfolge entspricht, in der sie im Funktionsaufruf übergeben wurden.
4.9.3. Spezielle Parameter¶
Standardmäßig können Argumente an eine Python-Funktion entweder über die Position oder explizit als Schlüsselwort übergeben werden. Aus Gründen der Lesbarkeit und Performance ist es sinnvoll, die Art und Weise der Argumentübergabe einzuschränken, sodass Entwickler nur auf die Funktionsdefinition schauen müssen, um festzustellen, ob Argumente über die Position, über Position oder Schlüsselwort oder über Schlüsselwörter übergeben werden.
Eine Funktionsdefinition kann wie folgt aussehen:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
wobei / und * optional sind. Wenn sie verwendet werden, geben diese Symbole die Art des Parameters dadurch an, wie die Argumente an die Funktion übergeben werden dürfen: Nur-Positions- (positional-only), Positions- oder Schlüsselwort- (positional-or-keyword) und Nur-Schlüsselwort-Parameter (keyword-only). Schlüsselwortparameter werden auch als benannte Parameter bezeichnet.
4.9.3.1. Positions- oder Schlüsselwortargumente¶
Wenn / und * in der Funktionsdefinition nicht vorhanden sind, können Argumente an eine Funktion über die Position oder über ein Schlüsselwort übergeben werden.
4.9.3.2. Reine Positionsargumente¶
Schaut man sich das genauer an, ist es möglich, bestimmte Parameter als positional-only (reine Positions-Parameter) zu kennzeichnen. Wenn sie positional-only sind, spielt die Reihenfolge der Parameter eine Rolle, und sie können nicht als Schlüsselwort übergeben werden. Reine Positions-Parameter stehen vor einem Schrägstrich /. Der / wird verwendet, um die reinen Positions-Parameter logisch vom Rest der Parameter zu trennen. Wenn in der Funktionsdefinition kein / vorkommt, gibt es keine reinen Positions-Parameter.
Parameter, die dem / folgen, können positional-or-keyword oder keyword-only sein.
4.9.3.3. eine Schlüsselwortargumente¶
Um Parameter als keyword-only zu kennzeichnen, was anzeigt, dass die Parameter als Schlüsselwortargument übergeben werden müssen, setze ein * in die Argumentliste direkt vor den ersten keyword-only-Parameter.
4.9.3.4. Funktionsbeispiele¶
Betrachte die folgenden Definitionen der Beispielfunktionen und achte dabei genau auf 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, die geläufigste Form, schränkt die Aufrufkonvention nicht ein und Argumente können über die Position oder per Schlüsselwort übergeben werden:
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
Die zweite Funktion pos_only_arg ist darauf beschränkt, nur Positionsparameter zu verwenden, da in der Funktionsdefinition ein / steht:
>>> 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_arg erlaubt nur Schlüsselwortargumente, wie durch ein * in der Funktionsdefinition angezeigt wird:
>>> 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 derselben 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'
Betrachte schließlich diese Funktionsdefinition, bei der es zu einer möglichen Kollision zwischen dem Positionsargument name und **kwds kommt, das name als Schlüssel enthält:
def foo(name, **kwds):
return 'name' in kwds
Es gibt keinen möglichen Aufruf, der dazu führt, dass sie True zurückgibt, da das Schlüsselwort 'name' immer an den ersten Parameter gebunden wird. 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 unter Verwendung von / (reine Positionsargumente) ist es möglich, da dies 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 können die Namen reiner Positions-Parameter ohne Mehrdeutigkeit in **kwds verwendet werden.
4.9.3.5. Rückblick¶
Der Anwendungsfall bestimmt, welche Parameter in der Funktionsdefinition zu verwenden sind:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
Als Orientierungshilfe:
Verwende reine Positions-Parameter, wenn der Name der Parameter dem Benutzer nicht zur Verfügung stehen soll. Dies ist nützlich, wenn Parameternamen keine wirkliche Bedeutung haben, wenn die Reihenfolge der Argumente beim Aufruf der Funktion erzwungen werden soll oder wenn einige Positionsparameter und beliebige Schlüsselwörter entgegengenommen werden müssen.
Verwende keyword-only, wenn Namen eine Bedeutung haben und die Funktionsdefinition verständlicher ist, indem Namen explizit gemacht werden, oder wenn du verhindern willst, dass sich Benutzer auf die Position des übergebenen Arguments verlassen.
Verwende für eine API reine Positions-Parameter, um inkompatible API-Änderungen zu verhindern, falls der Name des Parameters in Zukunft geändert wird.
4.9.4. Beliebige Argumentlisten¶
Schließlich ist die am seltensten genutzte Option anzugeben, dass eine Funktion mit einer beliebigen Anzahl von Argumenten aufgerufen werden kann. Diese Argumente werden in einem Tupel zusammengefasst (siehe Tupel und Sequenzen). Vor der variablen Anzahl von Argumenten können null oder mehr normale Argumente vorkommen.
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
Normalerweise stehen diese variadischen Argumente an letzter Stelle in der Liste der formalen Parameter, da sie alle verbleibenden Eingabeargumente aufsammeln, die an die Funktion übergeben werden. 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'
4.9.5. Entpacken von Argumentlisten¶
Die umgekehrte Situation tritt ein, wenn die Argumente bereits in einer Liste oder einem Tupel vorliegen, aber für einen Funktionsaufruf entpackt werden müssen, der separate Positionsargumente erfordert. Beispielsweise erwartet die eingebaute range()-Funktion separate start- und stop-Argumente. Stehen diese nicht separat zur Verfügung, schreibe den Funktionsaufruf mit dem *\ -Operator, um die Argumente aus einer Liste oder einem Tupel zu entpacken:
>>> list(range(3, 6)) # Normaler Aufruf mit separaten Argumenten
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # Aufruf mit aus einer Liste entpackten Argumenten
[3, 4, 5]
Auf die gleiche Weise können Wörterbücher mit dem **\ -Operator Schlüsselwortargumente übergeben:
>>> 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 !
4.9.6. 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 einzelnen Ausdruck beschränkt. Semantisch sind sie lediglich syntaktischer Zucker für eine normale Funktionsdefinition. Wie verschachtelte Funktionsdefinitionen können Lambda-Funktionen auf Variablen aus dem umgebenden Gültigkeitsbereich verweisen:
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
Das obige Beispiel verwendet einen Lambda-Ausdruck, um eine Funktion zurückzugeben. Eine weitere Anwendung ist die Übergabe einer kleinen Funktion als Argument. Beispielsweise nimmt list.sort() eine Sortierschlüsselfunktion key an, bei der es sich um eine Lambda-Funktion handeln kann:
>>> 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')]
4.9.7. Dokumentations-Zeichenketten¶
Hier sind einige Konventionen über den Inhalt und die Formatierung von Dokumentations-Zeichenketten.
Die erste Zeile sollte immer eine kurze, prägnante Zusammenfassung des Zwecks des Objekts sein. Der Kürze halber sollte sie weder den Namen noch den Typ des Objekts explizit nennen, da diese auf andere Weise verfügbar sind (außer wenn der Name zufällig ein Verb ist, das die Funktionsweise beschreibt). Diese Zeile sollte mit einem Großbuchstaben beginnen und mit einem Punkt enden.
Gibt es weitere Zeilen in der Dokumentations-Zeichenkette, sollte die zweite Zeile leer sein, um die Zusammenfassung visuell vom Rest der Beschreibung zu trennen. Die folgenden Zeilen sollten aus einem oder mehreren Absätzen bestehen, die die Aufrufkonventionen des Objekts, seine Seiteneffekte usw. beschreiben.
Der Python-Parser entfernt die Einrückung aus mehrzeiligen String-Literalen, wenn diese als Modul-, Klassen- oder Funktions-Docstrings dienen.
Hier ist ein Beispiel für einen mehrzeiligen Docstring:
>>> def my_function():
... """Tut nichts, dokumentiert dies aber.
...
... Nein, wirklich, es tut nichts:
...
... >>> my_function()
... >>>
... """
... pass
...
>>> print(my_function.__doc__)
Tut nichts, dokumentiert dies aber.
Nein, wirklich, es tut nichts:
>>> my_function()
>>>
4.9.8. Funktions-Annotationen¶
Funktions-Annotationen sind völlig optionale Metadaten-Informationen über die Typen, die von benutzerdefinierten Funktionen verwendet werden (siehe PEP 3107 und PEP 484 für weitere Informationen).
Annotations are stored in the __annotations__
attribute of the function as a dictionary and have no effect on any other part of the
function. Parameter annotations are defined by a colon after the parameter name, followed
by an expression evaluating to the value of the annotation. Return annotations are
defined by a literal ->, followed by an expression, between the parameter
list and the colon denoting the end of the def statement. The
following example has a required argument, an optional argument, and the return
value annotated:
>>> 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'
4.10. Intermezzo: Programmierstil¶
Nun, da du im Begriff sind, längere und komplexere Stücke Python-Code zu schreiben, ist ein guter Zeitpunkt, über den Programmierstil (coding style) zu sprechen. Die meisten Sprachen können in unterschiedlichen Stilen geschrieben (oder genauer gesagt formatiert) werden. Manche sind besser lesbar als andere. Es anderen leicht zu machen, den eigenen Code zu lesen, ist immer eine gute Idee, und die Aneignung eines guten Programmierstils hilft dabei ungemein.
Für Python hat sich PEP 8 als der Styleguide herauskristallisiert, an den sich die meisten Projekte halten. Er fördert einen sehr gut lesbaren und ansprechenden Programmierstil. Alle Python-Entwickler sollten ihn irgendwann gelesen haben. Hier sind die wichtigsten Punkte für dich zusammengefasst:“
Verwende 4 Leerzeichen zur Einrückung und keine Tabulatoren.
4 Leerzeichen sind ein guter Kompromiss zwischen einer geringen Einrückung (ermöglicht eine größere Schachtelungstiefe) und einer großen Einrückung (leichter zu lesen). Tabulatoren sorgen für Verwirrung und werden am besten weggelassen.
Brich Zeilen um, sodass sie 79 Zeichen nicht überschreiten.
Dies hilft Nutzern mit kleinen Bildschirmen und ermöglicht es, auf größeren Bildschirmen mehrere Code-Dateien nebeneinander anzuzeigen.
Verwende Leerzeilen, um Funktionen und Klassen sowie größere Codeblöcke innerhalb von Funktionen voneinander zu trennen.
Setze Kommentare nach Möglichkeit in eine eigene Zeile.
Verwende Dokumentations-Zeichenketten.
Verwende Leerzeichen um Operatoren und nach Kommas, aber nicht direkt innerhalb von Klammerkonstrukten:
a = f(1, 2) + g(3, 4).Benenne deine Klassen und Funktionen einheitlich. Die Konvention ist,
UpperCamelCasefür Klassen undlowercase_with_underscoresfür Funktionen und Methoden zu verwenden. Verwende immerselfals Namen für das erste Methodenargument (siehe Ein erster Blick auf Klassen für mehr zu Klassen und Methoden).Verwende keine ausgefallenen Zeichenkodierungen, wenn dein Code in internationalen Umgebungen verwendet werden soll. Pythons Standard UTF-8 oder sogar einfaches ASCII funktionieren in jedem Fall am besten.
Verwende ebenso keine Nicht-ASCII-Zeichen in Bezeichnern, wenn auch nur die geringste Wahrscheinlichkeit besteht, dass Personen, die eine andere Sprache sprechen, den Code lesen oder warten.
Fußnoten