Der Etiketten-Flughafen: Pythons Objektmodell
·
Kursüberblick
1
Das Etikett ist nicht der Koffer: Namen und Objekte
2
Versiegelte Koffer: veränderlich und unveränderlich
3
Koffer im Koffer: Container und Kopien
4
Wenn ein Koffer übergeben wird: Funktionen und Argumente
5
Zonen und Ausweise: Gültigkeitsbereiche
6
Handgepäck: Closures
7
Der Koffer-Bauplan: Klassen und Instanzen
8
Flughafen-Standardverfahren: Dunder-Methoden
9
Der Bauplan hinter dem Bauplan: Vererbung und MRO
10
Die Kontrolle: das Geheimnis des Attributzugriffs
11
Herrenlose Koffer: Speicher und GC
12
Schließfachnummern: Hash, Gleichheit und Gesamtbild
Zeit 3:20–4:10
Verlustmeldung
LOST-005
Gemeldet von
Han, Betriebsteam der Sicherheitskontrolle
Was fehlt
Ein Zähler für die durchleuchteten Koffer. Ganz oben steht eindeutig scanned = 0, doch sobald scan() aufgerufen wird, kommt ein UnboundLocalError.
Erledigt, wenn
Erklären, warum die Funktion den Namen draußen nicht sieht, und den Zähler so reparieren, dass er richtig zählt.
Internationaler Flughafen Haneulseom · Gepäckzentrum
Schichtleiterin Do Miran tippt auf den Flughafenplan an der Wand. „Gate, Terminal, Flughafen, Land. Ausweise werden von innen nach außen geprüft. Wer innen gefunden wird, für den schaut man außen gar nicht mehr nach.“
Die heutige Meldung betrifft den Zähler an der Sicherheitskontrolle. Draußen steht scanned = 0, und die Funktion sollte den Wert jeweils um eins erhöhen. Doch schon in der ersten Zeile kommt ein Fehler, so einen Wert gebe es nicht. Die Schichtleiterin zeigt auf die unterste Zeile der Funktion. „Hier ist der Täter. Die Zeile, die einen Wert zuweist.“
Wenn Python einen Namen liest, sucht es in vier Bereichen der Reihe nach von innen nach außen. Beim ersten Treffer hört es auf.
| Reihenfolge | Bereich | Was | Am Flughafen wäre das |
|---|---|---|---|
| L | Lokal (Local) | in der gerade laufenden Funktion | das Gate |
| E | Umschließend (Enclosing) | die äußeren Funktionen, die diese Funktion umschließen | das Terminal |
| G | Global | das Modul, in dem die Funktion definiert ist | der Flughafen |
| B | Eingebaut (Built-in) | Grundnamen wie len, print, list | das Land |
zone = "G"
def terminal():
gate = "E"
def boarding():
seat = "L"
print(seat, gate, zone, len("abc"))
boarding()
terminal()
# L E G 3seat wird in boarding selbst (L) gefunden, gate in der umschließenden Funktion terminal (E).zone wird im Modul (G) gefunden, len bei den eingebauten Namen (B).NameError.boarding von woanders aufgerufen würde, käme gate aus terminal.Die Gültigkeitskarte unten führt den Code nicht aus, sondern liest ihn nur und zeigt für jeden Namen, in welchem Bereich er gefunden wird. Die letzte Funktion broken ist die Hauptfigur des nächsten Abschnitts.
Gültigkeitskarte
1
zone = "G"
2
3
def terminal():
4
gate = "E"
5
def boarding():
6
seat = "L"
7
print(seat, gate, zone, len("abc"))
8
boarding()
9
10
def broken():
11
print(zone)
12
zone = "L"
Name
seat @ terminal. seat ist ein lokaler Name der inneren Funktion boarding und ist deshalb in der äußeren Funktion terminal nicht sichtbar. Gesucht wird immer von innen nach außen.zone @ broken wird in L mit „Hier gefunden“ markiert. Das heißt nur, dass der Name als lokaler Name dieser Funktion festgelegt ist, nicht, dass er schon einen Wert hat.Das ist der Code aus der Meldung:
scanned = 0
def scan(bag):
print(bag, scanned)
scanned = scanned + 1
scan("T1")
# UnboundLocalError: cannot access local variable 'scanned' where it is not associated with a valueDer Fehler kommt nicht in Zeile 5, sondern schon beim print in Zeile 4. Zeile 5 ist noch gar nicht gelaufen. Trotzdem sucht Python das scanned in Zeile 4 nicht draußen.
Es gibt genau eine Regel: Wird einem Namen irgendwo in einer Funktion ein Wert zugewiesen, ist dieser Name in der gesamten Funktion lokal. Diese Entscheidung fällt nicht bei der Ausführung, sondern beim Kompilieren der Funktion.
def scan(bag):
print(bag, scanned)
scanned = scanned + 1
print(scan.__code__.co_varnames)
# ('bag', 'scanned')
print(issubclass(UnboundLocalError, NameError))
# Trueco_varnames ist die Liste der lokalen Namen, die der Compiler festgelegt hat. scanned steht schon darin.scanned“, und dieses Etikett hängt noch an keinem Koffer. Genau das sagt die Fehlermeldung.UnboundLocalError ist eine Unterart von NameError.Auch Zeilen, die nie ausgeführt werden, zählen mit:
x = 10
def f():
print(x)
if False:
x = 1
f()
# UnboundLocalError: cannot access local variable 'x' where it is not associated with a value„Einen Wert zuweisen“ heißt nicht nur =. Auch +=, for x in ..., import x, def x():, class x:, with ... as x, except ... as x und del x machen einen Namen in dieser Funktion lokal.
Vergleich auf der Etikettentafel unten die drei Varianten. Die erste ist der Code aus der Meldung, im letzten Schritt wird der Fehler angezeigt.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Pin sagt
→ Siehst du einen UnboundLocalError, schau unterhalb der fehlerhaften Zeile nach. Irgendwo in derselben Funktion wird diesem Namen ein Wert zugewiesen.
global und nonlocal (7 min)Willst du einem Namen in einem äußeren Bereich einen Wert zuweisen, musst du das vorher erklären.
scanned = 0
def scan(bag):
global scanned
scanned = scanned + 1
scan("T1")
scan("T2")
print(scanned)
# 2def terminal():
boarded = 0
def board():
nonlocal boarded
boarded += 1
board()
board()
return boarded
print(terminal())
# 2global x: In dieser Funktion ist x der Name aus dem Modul. Eine Zuweisung hängt das Etikett im Modul um.nonlocal x: In dieser Funktion ist x der Name aus der nächsten umschließenden Funktion. Für Modulnamen geht das nicht.nonlocal in keiner umschließenden Funktion, kommt schon vor der Ausführung SyntaxError: no binding for nonlocal 'x' found.global x schreibt, bekommt SyntaxError: name 'x' is assigned to before global declaration.Zum bloßen Lesen braucht man keine Erklärung. boarding in 5-1 hat gate und zone ohne jede Erklärung gelesen. Nötig ist sie nur beim Zuweisen.
global ändert heimlich Zustand außerhalb der Funktion und macht es dadurch schwer, die Funktion für sich zu testen. Meist ist es besser, den Wert als Argument zu übergeben und den neuen Wert zurückzugeben. nonlocal kommt in Lektion 6 bei den Closures wieder vor.
In Python erzeugen Funktionen (def, lambda), Comprehensions und Klassenrümpfe einen neuen Bereich. Blöcke mit if, for, while, with und try tun das nicht.
for i in range(3):
pass
print(i)
# 2
if True:
inside = "if"
print(inside)
# if
sq = [n * n for n in range(3)]
print(sq)
# [0, 1, 4]
print(n)
# NameError: name 'n' is not defined| Erzeugt einen Bereich | Erzeugt keinen Bereich |
|---|---|
def, lambda, List-, Set- und Dict-Comprehensions, Generatorausdrücke, Rumpf von class | if, for, while, with, try |
for bleibt die Schleifenvariable i mit ihrem letzten Wert (2) bestehen. Für Namen, die in der Schleife entstehen, gilt dasselbe.n der Comprehension sickert nicht nach draußen. Seit 3.12 baut CPython Comprehensions nicht mehr als eigene Funktion, sondern führt sie eingebettet aus (PEP 709). Die Regel, dass ihre Namen nicht nach außen dringen, gilt aber weiterhin.Die eingebauten Namen liegen ganz außen (B). Legst du global einen gleichnamigen Namen an, wird von da an zuerst der globale gefunden.
list = ["T1", "T2"]
print(list("ab"))
# TypeError: 'list' object is not callableDas Etikett list hängt global an einem Listenkoffer, also versucht list("ab"), diese Liste aufzurufen, und scheitert. Das eingebaute list ist nicht verschwunden, es ist nur verdeckt.
list = ["T1", "T2"]
del list
print(list("ab"))
# ['a', 'b']
import builtins
print(builtins.list is list, builtins.len("abc"))
# True 3list ab, ist das eingebaute list wieder sichtbar.builtins. Auch wenn sie verdeckt sind, kommst du über builtins.list jederzeit an sie heran.shout = print den Befehl del print aus, kommt NameError: name 'print' is not defined. Global gibt es kein Etikett print, also gibt es auch nichts abzunehmen.Verwende Namen wie list, dict, str, id, type, input, max oder sum nicht als Variablen. Sonst verdeckst du in dieser Datei die eingebauten Funktionen.
locals(), globals(), vars(), dir() (4 min)globals(): das Dictionary des Modulbereichs. Im Modul aufgerufen, ist es der echte Speicher selbst.locals(): die Namen im aktuellen Bereich. Im Modul ist es dasselbe wie globals().vars(obj): das __dict__ eines Objekts. Ohne Argument entspricht es locals() (Lektion 7).dir(): gibt nur die Liste der Namen sortiert zurück.Seit 3.13 ist die Bedeutung von locals() in Funktionen eindeutig festgelegt (PEP 667). Jeder Aufruf liefert eine Momentaufnahme als neues Dictionary. Änderst du dieses Dictionary, ändern sich die lokalen Variablen nicht.
def check():
y = 1
locals()["y"] = 99
print(y, locals()["y"])
snap = locals()
y = 2
print(snap["y"], locals()["y"])
check()
# 1 1
# 1 2
def where(a):
b = 2
return dir(), vars()
print(where(1))
# (['a', 'b'], {'a': 1, 'b': 2})locals()["y"] = 99 ändert nur die gerade erzeugte Momentaufnahme. Die Variable y bleibt 1.snap behält den Wert vom Moment der Aufnahme (1). Erst ein erneuter Aufruf von locals() zeigt die 2.locals().Für den Zähler gibt es zwei Lösungen: das Etikett draußen direkt umhängen (global) oder den Wert entgegennehmen und den neuen Wert zurückgeben.
scanned = 0
def scan(bag):
global scanned
print(bag, scanned)
scanned = scanned + 1
scan("T1")
scan("T2")
print(scanned)
# T1 0
# T2 1
# 2
def scan_count(bag, count):
print(bag, count)
return count + 1
total = 0
total = scan_count("T1", total)
total = scan_count("T2", total)
print(total)
# T1 0
# T2 1
# 2Han entscheidet sich für die zweite Variante. Selbst wenn es später mehrere Kontrollstellen gibt, kann so jede für sich zählen. Die Schichtleiterin fügt hinzu: „Jede Zone hat ihre eigenen Etiketten. Wer ein Etikett in einer fremden Zone umhängen will, zeigt zuerst seinen Ausweis.“
Abschlussbericht — LOST-005. Durch scanned = ... weiter unten war scanned in der ganzen Funktion lokal, daher der UnboundLocalError gleich in der ersten Zeile. Gelöst, indem der Wert als Argument übergeben und zurückgegeben wird.
boarding aus 5-1 unterhalb des print die Zeile zone = "L" einfügst? Vergleich das mit zone @ broken auf der Gültigkeitskarte.nonlocal einen Zähler, der per append an eine Liste der äußeren Funktion zählt. Warum funktioniert das ohne Erklärung? (Tipp: Etikett umhängen oder Koffer verändern.)sum = 0 steht und du dann sum([1, 2, 3]) aufrufst? Behebe ihn mit builtins.Das Notizbuch der Schichtleiterin
Namen werden in der Reihenfolge L → E → G → B gesucht, Schluss ist beim ersten Treffer.
Der Bereich hängt nicht davon ab, wo aufgerufen wird, sondern davon, wo geschrieben wurde.
Wird irgendwo in einer Funktion zugewiesen, ist der Name in der ganzen Funktion lokal. Daher der UnboundLocalError.
global und nonlocal nur zum Umhängen äußerer Etiketten. Zum Lesen braucht man sie nicht.
Schleifen und if erzeugen keinen Bereich, Funktionen und Comprehensions schon.
list = … verdeckt das eingebaute list. Eingebaute Namen stehen in builtins.
locals() in einer Funktion ist eine Momentaufnahme (3.13, PEP 667). Änderungen daran ändern die Variablen nicht.
← Zurück
Lektion 4 · Wenn ein Koffer übergeben wird: Funktionen und Argumente
Weiter →
Lektion 6 · Handgepäck: Closures
Über neue Lektionen informiert werden
Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.
R
ReflexKR
Python und Reflex zum Anfassen. Alle Beispiele wurden mit Reflex 0.10.0 ausgeführt und geprüft.
ReflexKR ist kostenlos und ohne Werbebanner. Wenn es dir geholfen hat, kannst du es mit einem Kaffee unterstützen. ☕ Spendier uns einen Kaffee
Hinweis zu Affiliate-Links© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Fragen & Antworten
Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.