19.4 Namensräume
Bisher wurde ein Funktionskörper als abgekapselter Bereich betrachtet, der ausschließlich über Parameter bzw. den Rückgabewert Informationen mit dem Hauptprogramm austauschen kann. Das ist zunächst auch gar keine schlechte Sichtweise, denn so hält man seine Schnittstelle »sauber«. In manchen Situationen ist es aber sinnvoll, eine Funktion über ihren lokalen Namensraum hinaus wirken zu lassen, was in diesem Kapitel thematisiert werden soll.
19.4.1 Zugriff auf globale Variablen – global
Zunächst einmal müssen zwei Begriffe unterschieden werden. Wenn wir uns im Kontext einer Funktion, also im Funktionskörper, befinden, dann können wir dort selbstverständlich Referenzen und Instanzen erzeugen und verwenden. Diese haben jedoch nur unmittelbar in der Funktion selbst Gültigkeit. Sie existieren im lokalen Namensraum. Im Gegensatz dazu existieren Referenzen des Hauptprogramms im globalen Namensraum. Begrifflich wird auch zwischen globalen Referenzen und lokalen Referenzen unterschieden. Dazu folgendes Beispiel:
def f():
a = "lokaler String"
b = "globaler String"
Die Unterscheidung zwischen globalem und lokalem Namensraum wird anhand des folgenden Beispiels deutlich:
>>> def f(a):
... print(a)
>>> a = 10
>>> f(100)
100
In diesem Beispiel existiert sowohl im globalen als auch im lokalen Namensraum eine Referenz namens a. Im globalen Namensraum referenziert sie die ganze Zahl 10 und im lokalen Namensraum der Funktion den übergebenen Parameter, in diesem Fall die ganze Zahl 100. Es ist wichtig zu verstehen, dass diese beiden Referenzen nichts miteinander zu tun haben, da sie in verschiedenen Namensräumen existieren. Abbildung 19.2 fasst das Konzept der Namensräume zusammen.
19.4.2 Zugriff auf den globalen Namensraum
Im lokalen Namensraum eines Funktionskörpers kann jederzeit lesend auf eine globale Referenz zugegriffen werden, solange keine lokale Referenz gleichen Namens existiert:
>>> def f():
... print(s)
...
>>> s = "globaler String"
>>> f()
globaler String
Sobald versucht wird, schreibend auf eine globale Referenz zuzugreifen, wird stattdessen eine entsprechende lokale Referenz erzeugt:
>>> def f():
... print(s)
...
>>> s = "globaler String"
>>> f()
lokaler String
>>> s
'lokaler String'
Eine Funktion kann dennoch mithilfe der global-Anweisung schreibend auf eine globale Referenz zugreifen. Dazu muss im Funktionskörper das Schlüsselwort global geschrieben werden, gefolgt von einer oder mehreren globalen Referenzen:
>>> def f():
... global s
... s = "lokaler String"
... print(s)
...
>>> s = "globaler String"
>>> f()
lokaler String
>>> s
'globaler String'
Im Funktionskörper von f wird s explizit als globale Referenz gekennzeichnet und kann fortan als solche verwendet werden.
19.4.3 Lokale Funktionen
Es ist möglich, lokale Funktionen zu definieren. Das sind Funktionen, die im lokalen Namensraum einer anderen Funktion angelegt werden und nur dort gültig sind. Das folgende Beispiel zeigt eine solche Funktion:
>>> def globale_funktion(n):
... def lokale_funktion(n):
... return n**2
... return lokale_funktion(n)
...
>>> globale_funktion(2)
4
Innerhalb der globalen Funktion globale_funktion wurde eine lokale Funktion namens lokale_funktion definiert. Beachten Sie, dass der jeweilige Parameter n trotz des gleichen Namens nicht zwangsläufig denselben Wert referenziert. Die lokale Funktion kann im Namensraum der globalen Funktion völlig selbstverständlich wie jede andere Funktion auch aufgerufen werden.
Da sie einen eigenen Namensraum besitzt, hat die lokale Funktion keinen Zugriff auf lokale Referenzen der globalen Funktion. Um dennoch einige ausgewählte Referenzen an die lokale Funktion durchzuschleusen, bedient man sich eines Tricks mit vorbelegten Funktionsparametern:
>>> def globale_funktion(n):
... def lokale_funktion(n=n):
... return n**2
... return lokale_funktion()
...
>>> globale_funktion(2)
4
Wie Sie sehen, muss der lokalen Funktion der Parameter n beim Aufruf nicht mehr explizit übergeben werden. Er wird vielmehr implizit in Form eines vorbelegten Parameters übergeben.
19.4.4 Zugriff auf übergeordnete Namensräume – nonlocal
Im letzten Abschnitt haben wir von den zwei existierenden Namensräumen, dem globalen und dem lokalen, gesprochen. Diese Unterteilung ist richtig, unterschlägt aber einen interessanten Fall, denn laut Abschnitt 19.3.3, »Beliebige Anzahl von Parametern«, dürfen auch lokale Funktionen innerhalb von Funktionen definiert werden. Lokale Funktionen bringen natürlich wieder ihren eigenen lokalen Namensraum im lokalen Namensraum der übergeordneten Funktion mit. Bei verschachtelten Funktionsdefinitionen kann man die Welt der Namensräume also nicht in eine lokale und eine globale Ebene unterteilen. Dennoch stellt sich auch hier die Frage, wie eine lokale Funktion auf Referenzen zugreifen kann, die im lokalen Namensraum der übergeordneten Funktion liegen.
Das Schlüsselwort global hilft dabei nicht weiter, denn es erlaubt nur den Zugriff auf den äußersten, globalen Namensraum. Für diesen Zweck existiert seit Python 3.0 das Schlüsselwort nonlocal. Betrachten wir dazu einmal folgendes Beispiel:
>>> def funktion1():
... def funktion2():
... nonlocal res
... res += 1
... res = 1
... funktion2()
... print(res)
...
>>> funktion1()
2
Innerhalb der Funktion funktion1 wurde eine lokale Funktion funktion2 definiert, die die Referenz res aus dem lokalen Namensraum von funktion1 inkrementieren soll. Dazu muss res innerhalb von funktion2 als nonlocal gekennzeichnet werden. Die Schreibweise lehnt sich an den Zugriff auf Referenzen aus dem globalen Namensraum via global an.
Nachdem funktion2 definiert wurde, wird res im lokalen Namensraum von funktion1 definiert und mit dem Wert 1 verknüpft. Schließlich wird die lokale Funktion funktion2 aufgerufen und der Wert von res ausgegeben. Im Beispiel gibt funktion1 den Wert 2 aus.
Das Schlüsselwort nonlocal lässt sich auch bei mehreren, ineinander verschachtelten Funktionen verwenden, wie folgende Erweiterung unseres Beispiels zeigt:
>>> def funktion1():
... def funktion2():
... def funktion3():
... nonlocal res
... res += 1
... nonlocal res
... funktion3()
... res += 1
... res = 1
... funktion2()
... print(res)
...
>>> funktion1()
3
Nun wurde eine zusätzliche lokale Funktion im lokalen Namensraum von funktion2 definiert. Auch aus dem lokalen Namensraum von funktion3 heraus lässt sich res mithilfe von nonlocal inkrementieren. Die Funktion funktion1 gibt in diesem Beispiel den Wert 3 aus.
Allgemein funktioniert nonlocal bei tieferen Funktionsverschachtelungen so, dass es in der Hierarchie der Namensräume aufsteigt und die erste Referenz mit dem angegebenen Namen in den Namensraum des nonlocal-Schlüsselworts einbindet.