2.16. Gültigkeitsbereich

Wenn Python einen Namen innerhalb einer Funktion nachschlägt, durchsucht es eine bestimmte Abfolge von Gültigkeitsbereichen. Das Verständnis dieser Abfolge erklärt, warum einige Zuweisungen äußere Namen überdecken, warum andere sie verändern und warum verschachtelte Funktionen sich Werte von dort merken können, wo sie definiert wurden.

Verschachtelte Kästchen, die einen lokalen Gültigkeitsbereich innerhalb eines Modul-Gültigkeitsbereichs innerhalb des eingebauten Gültigkeitsbereichs zeigen, mit einem Pfeil, der angibt, dass die Namenssuche vom innersten Frame nach außen verläuft.

Die Namenssuche beginnt im lokalen Funktions-Gültigkeitsbereich und verläuft nach außen zu Modul- und eingebauten Gültigkeitsbereichen, bis eine Übereinstimmung gefunden wird.

2.16.1. Lokaler und Modul-Gültigkeitsbereich

Namen, die innerhalb einer Funktion definiert werden, sind lokal zu dieser Funktion und verschwinden, wenn der Aufruf endet:

def f():
    x = 10
    print(x)

f()
print(x)              # NameError: x is not defined

Namen, die auf der obersten Ebene einer .py-Datei definiert werden, sind modulweit (manchmal auch global genannt) und überall in dieser Datei sichtbar, einschließlich innerhalb von Funktionen:

CAMERA = "OpenMV"

def banner():
    print("running on", CAMERA)

Das Lesen eines modulweiten Namens von innerhalb einer Funktion erfolgt automatisch. Das Zuweisen zu diesem Namen von innerhalb einer Funktion jedoch nicht – die Zuweisung erzeugt eine neue lokale Variable, die die modulweite für den Rest des Aufrufs überdeckt:

counter = 0

def bump():
    counter = counter + 1     # UnboundLocalError

Das counter auf der linken Seite macht counter zu einem lokalen Namen in bump, sodass der Lesevorgang auf der rechten Seite keinen Wert findet.

2.16.1.1. Das Schlüsselwort global

Um einen modulweiten Namen tatsächlich von innerhalb einer Funktion neu zuzuweisen, deklarieren Sie ihn zuerst als global:

counter = 0

def bump():
    global counter
    counter += 1

Greifen Sie sparsam auf global zurück. Funktionen, die verborgenen Zustand verändern, sind schwerer nachzuvollziehen als Funktionen, die Werte als Argumente entgegennehmen und neue Werte zurückgeben. Die übliche Lösung für „Ich muss Zustand teilen“ besteht darin, ein Objekt (eine Liste, ein Dict, eine Klasseninstanz) als Argument zu übergeben und dieses stattdessen zu verändern.

2.16.2. Lambdas

Ein lambda baut eine kleine anonyme Funktion in einem einzigen Ausdruck:

square = lambda x: x * x
square(7)             # 49

Es ist exakt gleichbedeutend mit

def square(x):
    return x * x

Der Rumpf eines lambda muss ein einzelner Ausdruck sein – keine Anweisungen, keine mehreren Zeilen. Die Hauptverwendung ist die Übergabe einer winzigen Funktion als Argument an etwas, das eine Funktion entgegennimmt:

pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]

Wenn der Rumpf über einen Ausdruck hinauswächst, wechseln Sie zu einem echten def. Das Benennen einer Funktion mit def gibt ihr außerdem einen Namen in Tracebacks, den ein lambda nicht hat.

2.16.3. Closures

Eine innerhalb einer anderen Funktion definierte Funktion kann Namen aus dem Gültigkeitsbereich der umgebenden Funktion lesen. Die innere Funktion fängt diese Namen ein und funktioniert weiter, selbst nachdem der äußere Aufruf zurückgekehrt ist:

def make_adder(n):
    def add(x):
        return x + n
    return add

add5 = make_adder(5)
add10 = make_adder(10)
print(add5(100), add10(100))

Ausgabe:

105 110

add5 und add10 sind zwei separate Funktionen, von denen sich jede ihr eigenes n merkt. Eine Funktion, die auf diese Weise eine angepasste innere Funktion baut und zurückgibt, wird Closure genannt. Sie ist der Hauptgrund, warum eine Sprache überhaupt verschachtelte Funktionen benötigt – eine Möglichkeit, etwas Zustand in einen Funktionswert einzubacken und diesen Wert dann als einzelnes aufrufbares Objekt weiterzugeben.

Das Lesen eingefangener Namen geschieht automatisch. Das Neuzuweisen eines solchen erfordert ein zusätzliches Schlüsselwort. Das folgende Beispiel tut, was richtig aussieht, und scheitert:

def make_counter():
    count = 0
    def tick():
        count = count + 1     # UnboundLocalError
        return count
    return tick

Die Zuweisung an count innerhalb von tick macht count lokal zu tick, genauso wie sie es in einer Funktion auf oberster Ebene lokal gemacht hätte. Das Schlüsselwort nonlocal teilt Python mit: „Dieser Name lebt in der umgebenden Funktion, weise ihn dort neu zu“:

def make_counter():
    count = 0
    def tick():
        nonlocal count
        count += 1
        return count
    return tick

c = make_counter()
print(c(), c(), c())

Ausgabe:

1 2 3

nonlocal verhält sich zum Gültigkeitsbereich der umgebenden Funktion so wie global zum Modul-Gültigkeitsbereich. Beachten Sie, dass das Verändern eines eingefangenen Objekts (Aufruf von some_list.append(...), some_dict[k] = v) kein nonlocal benötigt – der Name wird nicht neu zugewiesen, sondern nur das Objekt, auf das er zeigt, wird verändert.