4. Weitere Kontrollfluss-Werkzeuge

Neben der gerade eingeführten while-Anweisung verwendet Python noch einige weitere, denen wir in diesem Kapitel begegnen werden.

4.1. if-Anweisungen

Vielleicht der bekannteste Anweisungstyp ist die if-Anweisung. Zum Beispiel

>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
...     x = 0
...     print('Negative changed to zero')
... elif x == 0:
...     print('Zero')
... elif x == 1:
...     print('Single')
... else:
...     print('More')
...
More

Es kann null oder mehr elif-Teile geben und der else-Teil ist optional. Das Schlüsselwort ‚elif‘ ist die Kurzform für ‚else if‘ und ist nützlich, um übermäßige Einrückungen zu vermeiden. Eine ifelifelif …-Sequenz ist ein Ersatz für die switch- oder case-Anweisungen, die in anderen Sprachen vorkommen.

Wenn Sie denselben Wert mit mehreren Konstanten vergleichen oder auf bestimmte Typen oder Attribute prüfen, werden Sie vielleicht auch die match-Anweisung nützlich finden. Weitere Details finden Sie unter match Statements.

4.2. for-Anweisungen

Die for-Anweisung in Python unterscheidet sich ein wenig von dem, was Sie von C oder Pascal gewohnt sind. Anstatt immer über eine arithmetische Progression von Zahlen zu iterieren (wie in Pascal) oder dem Benutzer die Möglichkeit zu geben, sowohl den Iterationsschritt als auch die Abbruchbedingung zu definieren (wie in C), iteriert die for-Anweisung von Python über die Elemente jeder Sequenz (einer Liste oder einer Zeichenkette) in der Reihenfolge, in der sie in der Sequenz erscheinen. Zum Beispiel (kein Wortspiel beabsichtigt)

>>> # Measure some strings:
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words:
...     print(w, len(w))
...
cat 3
window 6
defenestrate 12

Code, der eine Sammlung modifiziert, während er über dieselbe Sammlung iteriert, kann knifflig sein. Stattdessen ist es normalerweise einfacher, über eine Kopie der Sammlung zu iterieren 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]

# Strategy:  Create a new collection
active_users = {}
for user, status in users.items():
    if status == 'active':
        active_users[user] = status

4.3. Die range()-Funktion

Wenn Sie tatsächlich über eine Zahlenfolge iterieren müssen, ist die eingebaute Funktion range() nützlich. Sie erzeugt arithmetische Progressionen

>>> for i in range(5):
...     print(i)
...
0
1
2
3
4

Der angegebene Endpunkt ist niemals Teil der erzeugten Sequenz; range(10) erzeugt 10 Werte, die gültigen Indizes für Elemente einer Sequenz der Länge 10. Es ist möglich, den Bereich bei einer anderen Zahl beginnen zu lassen oder eine andere Schrittweite anzugeben (sogar negativ; manchmal wird dies als ‚step‘ 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, können Sie 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 solchen Fällen ist es jedoch praktisch, die Funktion enumerate() zu verwenden, siehe Looping Techniques.

Eine seltsame Sache passiert, wenn man einfach einen Bereich ausgibt

>>> range(10)
range(0, 10)

In vielerlei Hinsicht verhält sich das von range() zurückgegebene Objekt so, als wäre es eine Liste, aber tatsächlich ist es das nicht. Es ist ein Objekt, das beim Iterieren die aufeinanderfolgenden Elemente der gewünschten Sequenz zurückgibt, aber es erstellt nicht wirklich die Liste und spart somit Speicherplatz.

Wir sagen, ein solches Objekt ist iterable, das heißt, es ist geeignet als Ziel für Funktionen und Konstrukte, die etwas erwarten, aus dem sie sukzessive Elemente erhalten können, bis die Versorgung erschöpft ist. Wir haben gesehen, dass die for-Anweisung ein solches Konstrukt ist, während ein Beispiel für eine Funktion, die ein Iterable annimmt, sum() ist

>>> sum(range(4))  # 0 + 1 + 2 + 3
6

Später werden wir weitere Funktionen sehen, die Iterables zurückgeben und Iterables als Argumente annehmen. In Kapitel Datenstrukturen werden wir detaillierter ü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 fährt mit der nächsten Iteration der Schleife fort

>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print(f"Found an even number {num}")
...         continue
...     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 ohne Ausführung von break endet, wird die else-Klausel ausgeführt.

In einer for-Schleife wird die else-Klausel ausgeführt, nachdem die Schleife ihre letzte Iteration beendet hat, das heißt, wenn kein Break aufgetreten ist.

In einer while-Schleife wird sie ausgeführt, nachdem die Bedingung der Schleife falsch geworden ist.

In beiden Schleifentypen wird die else-Klausel nicht ausgeführt, wenn die Schleife durch break beendet wurde. Natürlich werden auch andere Möglichkeiten, die Schleife vorzeitig zu beenden, wie z. B. ein return oder eine ausgelöste Ausnahme, die Ausführung der else-Klausel überspringen.

Dies wird im folgenden for-Schleifenbeispiel veranschaulicht, das 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 der korrekte Code. Sehen Sie genau hin: die else-Klausel gehört zur for-Schleife, nicht zur if-Anweisung.)

Man kann sich die else-Klausel als eine Kombination mit dem if innerhalb der Schleife vorstellen. Während die Schleife läuft, wird eine Sequenz wie if/if/if/else durchlaufen. Das if befindet sich innerhalb der Schleife und wird mehrmals angetroffen. Wenn die Bedingung jemals wahr ist, wird ein break ausgeführt. Wenn die Bedingung niemals wahr ist, wird die else-Klausel außerhalb der Schleife ausgeführt.

Bei Verwendung mit einer Schleife 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. Mehr über die try-Anweisung und Ausnahmen finden Sie unter Handling Exceptions.

4.6. pass-Anweisungen

Die pass-Anweisung tut nichts. Sie kann verwendet werden, wenn syntaktisch eine Anweisung erforderlich ist, das Programm jedoch keine Aktion erfordert. Zum Beispiel

>>> while True:
...     pass  # Busy-wait for keyboard interrupt (Ctrl+C)
...

Dies wird häufig zur Erstellung von Minimalklassen verwendet

>>> class MyEmptyClass:
...     pass
...

Ein weiterer Ort, an dem pass verwendet werden kann, ist als Platzhalter für eine Funktion oder einen bedingten Körper, wenn Sie an neuerem Code arbeiten, was Ihnen ermöglicht, auf einer abstrakteren Ebene weiterzudenken. Das pass wird stillschweigend ignoriert

>>> def initlog(*args):
...     pass   # Remember to implement this!
...

Für diesen letzten Fall verwenden viele Leute das Ellipsis-Literal ... anstelle von pass. Diese Verwendung hat keine besondere Bedeutung für Python und ist kein Teil der Sprachdefinition (Sie könnten hier jeden konstanten Ausdruck verwenden), aber ... wird konventionell als Platzhalterkörper verwendet. Siehe The Ellipsis Object.

4.7. match-Anweisungen

Eine match-Anweisung nimmt einen Ausdruck und vergleicht seinen Wert mit aufeinanderfolgenden Mustern, die als ein oder mehrere Fallblöcke angegeben sind. Dies ähnelt oberflächlich einer switch-Anweisung in C, Java oder JavaScript (und vielen anderen Sprachen), ist aber stärker der Mustererkennung in Sprachen wie Rust oder Haskell nachempfunden. Nur das erste Muster, das übereinstimmt, wird ausgeführt, und es können auch Komponenten (Sequenzelemente oder Objektattribute) aus dem Wert in Variablen extrahiert werden. Wenn kein Fall übereinstimmt, wird keiner der Zweige ausgeführt.

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"

Beachten Sie den letzten Block: Der „Variablenname“ _ fungiert als Wildcard und schlägt nie fehl.

Sie können mehrere Literale in einem einzigen Muster mit | („oder“) kombinieren

case 401 | 403 | 404:
    return "Not allowed"

Muster können wie Zuweisungen zum Entpacken aussehen und 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")

Studieren Sie dieses sorgfältig! Das erste Muster hat zwei Literale und kann als Erweiterung des oben gezeigten Literalmusters betrachtet werden. Die nächsten beiden Muster kombinieren jedoch ein Literal und eine Variable, und die Variable bindet einen Wert vom Subjekt (point). Das vierte Muster erfasst zwei Werte, was es konzeptionell der Entpackungszuweisung (x, y) = point ähnlich macht.

Wenn Sie Klassen zur Strukturierung Ihrer Daten verwenden, können Sie den Klassennamen gefolgt von einer Argumentliste verwenden, die einem Konstruktor ähnelt, jedoch mit der Möglichkeit, Attribute in Variablen zu binden

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 Positionsargumente bei einigen integrierten Klassen verwenden, die eine Reihenfolge für ihre Attribute bereitstellen (z. B. Dataklassen). Sie können auch eine spezifische Position für Attribute in Mustern festlegen, 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, ist, sie als erweiterte Form dessen zu betrachten, was Sie auf der linken Seite einer Zuweisung platzieren würden, um zu verstehen, welche Variablen mit was gebunden werden. Nur die eigenständigen Namen (wie var oben) werden von einer Match-Anweisung zugewiesen. GEPUNKTETE Namen (wie foo.bar), Attributnamen (die x= und y= oben) oder Klassennamen (erkannt durch die „(...)“ daneben, wie Point oben) werden niemals zugewiesen.

Muster können beliebig verschachtelt sein. Wenn wir zum Beispiel eine kurze Liste von Punkten haben, bei 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 eine if-Klausel zu einem Muster hinzufügen, bekannt als „Guard“. Wenn der Guard falsch ist, geht match weiter und versucht den nächsten Fallblock. Beachten Sie, dass die Wertemittlung 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")

Mehrere weitere Schlüsselfunktionen dieser Anweisung

  • Ähnlich wie bei Zuweisungen zum Entpacken haben Tupel- und Listenmuster genau die gleiche Bedeutung und gleichen tatsächlich beliebige Sequenzen ab. Eine wichtige Ausnahme ist, dass sie keine Iteratoren oder Zeichenketten abgleichen.

  • Sequenzmuster unterstützen die erweiterte Entpackung: [x, y, *rest] und (x, y, *rest) funktionieren ähnlich wie Zuweisungen zum Entpacken. Der Name nach * kann auch _ sein, also gleicht (x, y, *_) eine Sequenz von mindestens zwei Elementen ab, ohne die verbleibenden Elemente zu binden.

  • Mapping-Muster: {"bandwidth": b, "latency": l} erfasst die Werte "bandwidth" und "latency" aus einem Wörterbuch. Im Gegensatz zu Sequenzmustern werden zusätzliche Schlüssel ignoriert. Eine Entpackung wie **rest wird ebenfalls unterstützt. (Aber **_ wäre redundant, daher ist es nicht erlaubt.)

  • Unter-Muster können mit dem Schlüsselwort as erfasst werden

    case (Point(x1, y1), Point(x2, y2) as p2): ...
    

    erfasst das zweite Element der Eingabe als p2 (vorausgesetzt, die Eingabe ist eine Sequenz von zwei Punkten)

  • Die meisten Literale werden auf Gleichheit geprüft, die Singletons True, False und None werden jedoch auf Identität geprüft.

  • Muster können benannte Konstanten verwenden. Dies müssen gepunktete Namen sein, um zu verhindern, dass sie als Erfassungsnamen 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 detailliertere Erklärung und zusätzliche Beispiele können Sie sich PEP 636 ansehen, der im Tutorial-Format geschrieben ist.

4.8. Funktionen definieren

Wir können eine Funktion erstellen, die die Fibonacci-Reihe bis zu einer beliebigen Grenze schreibt

>>> def fib(n):    # write Fibonacci series less than n
...     """Print a Fibonacci series less than 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. Es muss der Funktionsname und die in Klammern stehende Liste der formalen Parameter folgen. Die Anweisungen, die den Körper der Funktion bilden, beginnen in der nächsten Zeile und müssen eingerückt sein.

Die erste Anweisung im Funktionskörper kann optional eine String-Literal sein; dieses String-Literal ist die Dokumentationszeichenkette der Funktion oder Docstring. (Mehr über Docstrings finden Sie im Abschnitt Documentation Strings.) Es gibt Werkzeuge, die Docstrings verwenden, um automatisch Online- oder gedruckte Dokumentation zu erstellen, oder um dem Benutzer die interaktive Durchsicht von Code zu ermöglichen; es ist eine gute Praxis, Docstrings in Code aufzunehmen, den Sie schreiben, also gewöhnen Sie sich daran.

Die *Ausführung* einer Funktion führt eine neue Symboltabelle ein, die für die lokalen Variablen der Funktion verwendet wird. Genauer gesagt, alle Variablenszuweisungen in einer Funktion speichern den Wert in der lokalen Symboltabelle; während Variablennamen zuerst in der lokalen Symboltabelle, dann in den lokalen Symboltabellen umschließender Funktionen, dann in der globalen Symboltabelle und schließlich in der Tabelle der eingebauten Namen gesucht werden. Globale Variablen und Variablen von umschließenden Funktionen können also nicht direkt einen Wert innerhalb einer Funktion zugewiesen bekommen (es sei denn, sie werden für globale Variablen in einer global-Anweisung benannt, oder für Variablen umschließender Funktionen in einer nonlocal-Anweisung), obwohl sie referenziert werden können.

Die tatsächlichen Parameter (Argumente) eines Funktionsaufrufs werden bei der Ausführung des Aufrufs in der lokalen Symboltabelle der aufgerufenen Funktion eingeführt; daher werden Argumente mittels 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 ordnet dem Funktionsnamen das Funktionsobjekt in der aktuellen Symboltabelle zu. Der Interpreter erkennt das Objekt, auf das dieser Name zeigt, als eine benutzerdefinierte Funktion. Andere Namen können auch auf dasselbe Funktionsobjekt verweisen und können auch 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

Aus anderen Sprachen kommend, könnten Sie einwenden, dass fib keine Funktion, sondern ein 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). Das Schreiben des Werts None wird normalerweise vom Interpreter unterdrückt, wenn es der einzige zu schreibende Wert wäre. Sie können ihn sehen, wenn Sie ihn wirklich wollen, indem Sie print() verwenden

>>> fib(0)
>>> print(fib(0))
None

Es ist einfach, eine Funktion zu schreiben, die eine Liste der Fibonacci-Zahlen zurückgibt, anstatt sie auszudrucken

>>> 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)    # see below
...         a, b = b, a+b
...     return result
...
>>> f100 = fib2(100)    # call it
>>> f100                # write the result
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Dieses Beispiel zeigt wie üblich einige neue Python-Funktionen

  • Die return-Anweisung gibt einen Wert aus einer Funktion zurück. return ohne Ausdruck gibt None zurück. Das Ende einer Funktion wird ebenfalls als Rückgabe von None interpretiert.

  • Die Anweisung result.append(a) ruft eine Methode des Listenobjekts result auf. Eine Methode ist eine Funktion, die zu einem Objekt „gehört“ und als obj.methodname benannt ist, wobei obj ein beliebiges Objekt ist (dies kann ein Ausdruck sein) und methodname der Name einer Methode ist, die vom Typ des Objekts definiert wird. Unterschiedliche Typen definieren unterschiedliche Methoden. Methoden unterschiedlicher Typen können denselben Namen haben, ohne Mehrdeutigkeit zu verursachen. (Es ist möglich, eigene Objekttypen und Methoden zu definieren, indem man Klassen verwendet, siehe Classes) Die im Beispiel gezeigte Methode append() ist für Listenobjekte definiert; sie fügt ein neues Element am Ende der Liste hinzu. In diesem Beispiel ist sie äquivalent zu result = result + [a], aber effizienter.

4.9. Weitere Informationen zu Funktionsdefinitionen

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. Standard-Argumentwerte

Die nützlichste Form ist die Angabe eines Standardwerts für ein oder mehrere Argumente. Dadurch wird eine Funktion erstellt, die mit weniger Argumenten aufgerufen werden kann, als sie zum Zulassen definiert ist. 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 mit dem obligatorischen Argument: ask_ok('Wirklich beenden?')

  • mit einem der optionalen Argumente: ask_ok('OK zum Überschreiben der Datei?', 2)

  • oder sogar mit allen Argumenten: ask_ok('OK zum Überschreiben der Datei?', 2, 'Komm schon, nur ja oder nein!')

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* Geltungsbereich ausgewertet, so dass

i = 5

def f(arg=i):
    print(arg)

i = 6
f()

druckt 5.

Wichtige Warnung: Der Standardwert wird nur einmal ausgewertet. Dies macht einen Unterschied, wenn der Standardwert ein veränderbares Objekt wie eine Liste, ein Wörterbuch oder Instanzen der meisten Klassen ist. Zum Beispiel sammelt die folgende Funktion die ihr bei nachfolgenden Aufrufen übergebenen Argumente an

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

Dies wird ausgeben

[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 wie folgt 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 mit Schlüsselwortargumenten der Form kwarg=value aufgerufen werden. Zum Beispiel die folgende Funktion

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 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

aber alle folgenden Aufrufe wären ungültig

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 Schlüsselwortargumente auf Positionsargumente folgen. Alle übergebenen Schlüsselwortargumente müssen einem der von der Funktion akzeptierten Argumente entsprechen (z. B. ist actor kein gültiges Argument für die Funktion parrot), und ihre Reihenfolge ist nicht wichtig. Dies schließt auch nicht-optionale Argumente ein (z. B. ist parrot(voltage=1000) ebenfalls 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 letzter formaler Parameter der Form **name vorhanden ist, empfängt er ein Wörterbuch (siehe Mapping Types — dict), das alle Schlüsselwortargumente enthält, außer denen, die einem formalen Parameter entsprechen. Dies kann mit einem formalen Parameter der Form *name (im nächsten Unterabschnitt beschrieben) kombiniert werden, der ein Tupel mit den Positionsargumenten jenseits der formalen Parameterliste empfängt. (*name muss vor **name stehen.) Wenn wir zum Beispiel 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 so 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 natürlich würde es 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

Beachten Sie, dass die Reihenfolge, in der die Schlüsselwortargumente gedruckt werden, garantiert der Reihenfolge entspricht, in der sie im Funktionsaufruf angegeben wurden.

4.9.3. Spezielle Parameter

Standardmäßig können Argumente entweder nach Position oder explizit nach Schlüsselwort an eine Python-Funktion übergeben werden. Zur Lesbarkeit und Leistung ist es sinnvoll, die Art und Weise, wie Argumente übergeben werden können, so einzuschränken, dass ein Entwickler nur die Funktionsdefinition ansehen muss, um festzustellen, ob Elemente nach Position, nach Position oder Schlüsselwort oder nach Schlüsselwort ü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, zeigen diese Symbole die Art des Parameters an, indem sie angeben, wie die Argumente an die Funktion übergeben werden können: nur positionsbezogen, positions- oder schlüsselwortbezogen und nur schlüsselwortbezogen. 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 nach Position oder nach Schlüsselwort übergeben werden.

4.9.3.2. Nur positionsbezogene Parameter

Wenn man dies etwas genauer betrachtet, ist es möglich, bestimmte Parameter als *nur positionsbezogen* zu markieren. Wenn sie *nur positionsbezogen* sind, ist die Reihenfolge der Parameter wichtig, und die Parameter können nicht per Schlüsselwort übergeben werden. Nur positionsbezogene Parameter werden vor einem / (Schrägstrich) platziert. Der / wird verwendet, um die nur positionsbezogenen Parameter logisch von den restlichen Parametern zu trennen. Wenn in der Funktionsdefinition kein / vorhanden ist, gibt es keine nur positionsbezogenen Parameter.

Parameter nach dem / können *positions- oder schlüsselwortbezogen* oder *nur schlüsselwortbezogen* sein.

4.9.3.3. Nur Schlüsselwortargumente

Um Parameter als *nur schlüsselwortbezogen* zu markieren, was bedeutet, dass die Parameter als Schlüsselwortargumente übergeben werden müssen, platzieren Sie ein * in der Argumentliste direkt vor dem ersten *nur schlüsselwortbezogenen* Parameter.

4.9.3.4. Funktionsbeispiele

Betrachten Sie die folgenden Beispiel-Funktionsdefinitionen, wobei Sie auf die Markierungen / und * achten

>>> 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 vertrauteste Form, legt keine Einschränkungen für die Aufrufkonvention fest und Argumente können nach Position oder Schlüsselwort übergeben werden

>>> standard_arg(2)
2

>>> standard_arg(arg=2)
2

Die zweite Funktion pos_only_arg ist darauf beschränkt, nur Positionsargumente zu verwenden, da sich in der Funktionsdefinition ein / befindet

>>> 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, was 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'

Betrachten Sie schließlich diese Funktionsdefinition, die eine mögliche Kollision zwischen dem Positionsargument name und **kwds aufweist, das name als Schlüssel hat

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 mit / (positionsweise exklusive Argumente) ist dies möglich, da es name als Positionsargument und 'name' als Schlüssel in den Schlüsselwortargumenten zulässt

>>> def foo(name, /, **kwds):
...     return 'name' in kwds
...
>>> foo(1, **{'name': 2})
True

Mit anderen Worten, die Namen von positionsweise exklusiven Parametern können ohne Mehrdeutigkeit in **kwds verwendet werden.

4.9.3.5. Zusammenfassung

Der Anwendungsfall bestimmt, welche Parameter in der Funktionsdefinition verwendet werden sollen

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

Als Leitfaden

  • Verwenden Sie positionsweise exklusive Argumente, wenn Sie nicht möchten, dass der Name der Parameter für den Benutzer verfügbar ist. Dies ist nützlich, wenn Parameternamen keine wirkliche Bedeutung haben, wenn Sie die Reihenfolge der Argumente beim Aufruf der Funktion erzwingen möchten oder wenn Sie einige Positionsargumente und beliebige Schlüsselwörter benötigen.

  • Verwenden Sie schlüsselwortweise exklusive Argumente, wenn Namen eine Bedeutung haben und die Funktionsdefinition durch explizite Namen verständlicher ist oder wenn Sie verhindern möchten, dass Benutzer sich auf die Position des übergebenen Arguments verlassen.

  • Verwenden Sie für eine API positionsweise exklusive Argumente, um zu verhindern, dass API-Änderungen zu Brüchen führen, wenn der Name des Parameters in Zukunft geändert wird.

4.9.4. Beliebige Argumentlisten

Schließlich ist die am seltensten verwendete Option die Angabe, 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 auftreten.

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

Normalerweise sind diese variadischen Argumente die letzten in der Liste der formalen Parameter, da sie alle verbleibenden Eingabeargumente aufnehmen, die an die Funktion übergeben werden. Alle formalen Parameter, die nach dem Parameter *args auftreten, sind „nur Schlüsselwortargumente“, 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 auf, wenn die Argumente bereits in einer Liste oder einem Tupel vorliegen, aber für einen Funktionsaufruf, der separate Positionsargumente erfordert, entpackt werden müssen. Zum Beispiel erwartet die eingebaute Funktion range() separate Start- und Stop-Argumente. Wenn diese nicht separat verfügbar sind, schreiben Sie den Funktionsaufruf mit dem *-Operator, um die Argumente aus einer Liste oder einem Tupel zu entpacken

>>> 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]

In gleicher 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 !

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 einzigen Ausdruck beschränkt. Semantisch sind sie nur syntaktischer Zucker für eine normale Funktionsdefinition. Wie verschachtelte Funktionsdefinitionen können Lambda-Funktionen Variablen aus dem enthaltenden Gültigkeitsbereich referenzieren

>>> 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. Ein weiterer Verwendungszweck ist die Übergabe einer kleinen Funktion als Argument. Zum Beispiel nimmt list.sort() eine Sortierschlüssel-Funktion key entgegen, die eine Lambda-Funktion sein 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. Dokumentationsstrings

Hier sind einige Konventionen für Inhalt und Formatierung von Dokumentationsstrings.

Die erste Zeile sollte immer eine kurze, prägnante Zusammenfassung des Zwecks des Objekts sein. Zur Kürze sollte sie nicht explizit den Namen oder Typ des Objekts angeben, da diese auf andere Weise verfügbar sind (es sei denn, der Name ist zufällig ein Verb, das die Operation einer Funktion beschreibt). Diese Zeile sollte mit einem Großbuchstaben beginnen und mit einem Punkt enden.

Wenn weitere Zeilen im Dokumentationsstring vorhanden sind, sollte die zweite Zeile leer sein und die Zusammenfassung visuell vom Rest der Beschreibung trennen. Die folgenden Zeilen sollten ein oder mehrere Absätze sein, die die Aufrufkonventionen des Objekts, seine Seiteneffekte usw. beschreiben.

Der Python-Parser entfernt die Einrückung nicht aus mehrzeiligen Zeichenkettenliteralen in Python, daher müssen Werkzeuge, die Dokumentation verarbeiten, die Einrückung nach Bedarf entfernen. Dies geschieht nach der folgenden Konvention. Die erste nicht-leere Zeile nach der ersten Zeile der Zeichenkette bestimmt den Einrückungsbetrag für den gesamten Dokumentationsstring. (Wir können die erste Zeile nicht verwenden, da sie generell an die öffnenden Anführungszeichen der Zeichenkette angrenzt, sodass ihre Einrückung im Zeichenkettenliteral nicht ersichtlich ist.) Einrücken, der "äquivalent" zu dieser Einrückung ist, wird dann vom Anfang aller Zeilen der Zeichenkette gestrippt. Zeilen, die weniger eingerückt sind, sollten nicht vorkommen, aber wenn sie vorkommen, sollte ihre gesamte führende Leerzeichen gestrippt werden. Die Gleichheit von Leerzeichen sollte nach der Expansion von Tabs (normalerweise auf 8 Leerzeichen) getestet werden.

Hier ist 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.

4.9.8. Funktionsannotationen

Funktionsannotationen sind optional zu metadaten über die Typen, die von benutzerdefinierten Funktionen verwendet werden (siehe PEP 3107 und PEP 484 für weitere Informationen).

Annotationen werden im Attribut __annotations__ der Funktion als Wörterbuch gespeichert und haben keine Auswirkungen auf andere Teile der Funktion. Parameterannotationen werden durch einen Doppelpunkt nach dem Parameternamen definiert, gefolgt von einem Ausdruck, der zu dem Wert der Annotation ausgewertet wird. Rückgabeannotationen werden durch ein Literal -> definiert, gefolgt von einem Ausdruck, der zwischen der Parameterliste und dem Doppelpunkt steht, der das Ende der def-Anweisung kennzeichnet. Das folgende Beispiel hat ein erforderliches Argument, ein optionales Argument und den Rückgabewert annotiert

>>> 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. Zwischenspiel: Codierungsstil

Da Sie nun längere, komplexere Python-Codeabschnitte schreiben werden, ist es an der Zeit, über den Codierungsstil zu sprechen. Die meisten Sprachen können in verschiedenen Stilen geschrieben (oder prägnanter gesagt, formatiert) werden; einige sind lesbarer als andere. Es ist immer eine gute Idee, Ihren Code für andere gut lesbar zu machen, und die Übernahme eines guten Codierungsstils hilft dabei enorm.

Für Python hat sich PEP 8 als Styleguide herauskristallisiert, an den sich die meisten Projekte halten; er fördert einen sehr lesbaren und ansprechenden Codierungsstil. Jeder Python-Entwickler sollte ihn irgendwann lesen; hier sind die wichtigsten Punkte für Sie zusammengefasst

  • Verwenden Sie 4 Leerzeichen Einrückung und keine Tabs.

    4 Leerzeichen sind ein guter Kompromiss zwischen kleiner Einrückung (ermöglicht größeren Verschachtelungsgrad) und großer Einrückung (leichter zu lesen). Tabs führen zu Verwirrung und sollten vermieden werden.

  • Zeilen umbrechen, sodass sie nicht mehr als 79 Zeichen lang sind.

    Dies hilft Benutzern mit kleinen Displays und ermöglicht es, mehrere Code-Dateien nebeneinander auf größeren Displays anzuzeigen.

  • Verwenden Sie Leerzeilen, um Funktionen und Klassen sowie größere Codeblöcke innerhalb von Funktionen zu trennen.

  • Wenn möglich, stellen Sie Kommentare auf eine eigene Zeile.

  • Verwenden Sie Dokumentationsstrings.

  • Verwenden Sie Leerzeichen um Operatoren und nach Kommas, aber nicht direkt innerhalb von Klammernkonstruktionen: a = f(1, 2) + g(3, 4).

  • Benennen Sie Ihre Klassen und Funktionen konsistent; die Konvention ist, UpperCamelCase für Klassen und lowercase_with_underscores für Funktionen und Methoden zu verwenden. Verwenden Sie immer self als Namen für das erste Methodenargument (siehe Ein erster Blick auf Klassen für mehr über Klassen und Methoden).

  • Verwenden Sie keine ausgefallenen Kodierungen, wenn Ihr Code für internationale Umgebungen bestimmt ist. Pythons Standard, UTF-8, oder sogar reines ASCII funktionieren in jedem Fall am besten.

  • Verwenden Sie ebenso keine Nicht-ASCII-Zeichen in Bezeichnern, wenn die geringste Chance besteht, dass Personen, die eine andere Sprache sprechen, den Code lesen oder warten.

Fußnoten