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
Verlustmeldung
LOST-006
Gemeldet von
Seo, zuständig für die Boarding-Durchsagen
Was fehlt
Drei Funktionen für Gate-Hinweise. In einer Schleife wurden sie für A1, B7 und C3 erzeugt, doch alle drei schicken die Fluggäste zu C3.
Erledigt, wenn
Nachsehen, was die drei Funktionen mit sich tragen, und dafür sorgen, dass sich jede Funktion ihr eigenes Gate merkt.
Internationaler Flughafen Haneulseom · Gepäckzentrum
Kurz nach der Morgendurchsage stehen drei Fluggäste, die zu verschiedenen Gates müssten, alle vor C3. Seo sagt, es gebe doch drei Hinweisfunktionen, also müsse es auch drei verschiedene Hinweise geben.
Die Schichtleiterin hält ein Stück Handgepäck hoch. „Auch Funktionen tragen einen Koffer mit sich. Die Frage ist, ob darin ein Wert steckt oder ein Namensschild.“ Heute öffnen wir den kleinen Koffer, den Funktionen mit sich herumtragen: die Closure.
Wie in Lektion 1 gesehen, sind auch Funktionen Objekte. Man kann sie an Namen binden, in Listen stecken, an andere Funktionen übergeben und zurückgeben.
def make_tag(owner):
def tag():
return "owner: " + owner
return tag
t = make_tag("kim")
print(t())
# owner: kim
print(t.__name__, t.__qualname__)
# tag make_tag.<locals>.tagmake_tag läuft def tag():, und es entsteht ein neues Funktionsobjekt.t() aufgerufen wird, ist make_tag längst beendet. Nach den Regeln aus Lektion 5 hätte der lokale Name owner von make_tag mitverschwinden müssen.t() an "kim". Wie hält die innere Funktion einen Namen einer Funktion fest, die schon beendet ist?__closure__ (9 min)Benutzt eine innere Funktion einen Namen der äußeren Funktion, legt Python diesen Namen in einen kleinen Koffer namens Zelle (cell). Der Rahmen der äußeren Funktion und die innere Funktion zeigen auf dieselbe Zelle. Endet die äußere Funktion und verschwindet ihr Rahmen, bleiben Zelle und Wert erhalten, weil die innere Funktion die Zelle festhält. Eine Funktion, die ein solches Bündel von Zellen mit sich trägt, ist eine Closure.
def make_tag(owner):
def tag():
return "owner: " + owner
return tag
t = make_tag("kim")
print(t.__code__.co_freevars)
# ('owner',)
print(type(t.__closure__[0]).__name__)
# cell
print(t.__closure__[0].cell_contents)
# kim
print(make_tag.__code__.co_cellvars)
# ('owner',)| Attribut | Bedeutung |
|---|---|
inner.__code__.co_freevars | die Namen, die sich die innere Funktion von außen borgt (freie Variablen) |
inner.__closure__ | ein Tupel mit einer Zelle pro solchem Namen |
cell.cell_contents | das Objekt, auf das die Zelle gerade zeigt |
outer.__code__.co_cellvars | die Namen, die die äußere Funktion als Zelle angelegt hat |
Solange make_tag läuft, zeigt die Etikettentafel owner im Rahmen als den Wert, der in der Zelle steckt. Achte darauf, ob die Zeile owner von function tag auch nach dem Verschwinden des Rahmens noch über cell an 'kim' festhält.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
def make_tag(owner):
notes = ["memo"] * 1000
def tag():
return owner
return tag
print(make_tag("kim").__code__.co_freevars)
# ('owner',)Pin sagt
→ Eine Closure trägt keine Kopie des Werts, sondern eine Zelle, also den Platz, an dem das Namensschild hängt. Hängt man den Namen draußen um, ändert sich auch der Wert, den die Closure sieht.
Das ist der Code aus der Meldung:
def make_guides(gates):
guides = []
for gate in gates:
guides.append(lambda: gate)
return guides
gs = make_guides(["A1", "B7", "C3"])
print([g() for g in gs])
# ['C3', 'C3', 'C3']
print(gs[0].__closure__[0] is gs[2].__closure__[0])
# Truegate ein einziger Name in make_guides, und es gibt auch nur eine Zelle.lambda nehmen diese eine Zelle mit. Am Ende der Schleife steckt darin "C3".gate im Rumpf des lambda schaut beim Aufruf in die Zelle. Beim Erzeugen wird der Wert nicht notiert. Das nennt man späte Bindung (late binding).Mit einer List Comprehension ist es genauso:
fs = [lambda: i for i in range(3)]
print([f() for f in fs])
# [2, 2, 2]
print(fs[0].__closure__[0] is fs[1].__closure__[0])
# TrueAuch ohne Schleife passiert dasselbe. Hängt man den Namen draußen um, nachdem die Funktion erzeugt wurde, ändert sich auch der Wert, den sie sieht.
def make():
gate = "A1"
def guide():
return gate
gate = "Z9"
return guide
print(make()())
# Z9Achte auf der Etikettentafel unten darauf, wie die Pfeile der drei Funktionen in einer einzigen Zelle zusammenlaufen. In der korrigierten Fassung hat jede Funktion ihr eigenes defaults-Tupel.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Die Lösung: Man legt „den Wert vom Moment der Erzeugung“ eigens in der Funktion ab.
fs = [lambda i=i: i for i in range(3)]
print([f() for f in fs])
# [0, 1, 2]
from functools import partial
def guide(gate):
return "go to " + gate
guides = [partial(guide, gate) for gate in ["A1", "B7", "C3"]]
print([g() for g in guides])
# ['go to A1', 'go to B7', 'go to C3']lambda i=i: i: Standardargumente werden einmal bei der Definition berechnet (Lektion 4). Das i dieses Moments landet in __defaults__ der Funktion.functools.partial(guide, gate): erzeugt ein neues aufrufbares Objekt, das die Funktion mit dem Argument dieses Moments bündelt. Die Absicht ist klarer als beim Trick mit dem Standardargument.f(5).nonlocal (7 min)Zellen kann man nicht nur lesen. Mit nonlocal (Lektion 5) kann die innere Funktion das Etikett in der Zelle umhängen. Dann hat die Funktion einen Zustand, der von Aufruf zu Aufruf erhalten bleibt.
def counter():
n = 0
def tick():
nonlocal n
n += 1
return n
return tick
c = counter()
c()
c()
print(c(), c.__closure__[0].cell_contents)
# 3 3
c2 = counter()
print(c2(), c())
# 1 4counter() legt eine neue Zelle an. c und c2 wissen nichts von der Zahl des anderen.nonlocal n würde n += 1 den Namen n lokal machen, und es gäbe einen UnboundLocalError (dieselbe Ursache wie in Lektion 5).Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Dasselbe geht auch mit einer Klasse. Dann steckt der Zustand in einem Attribut der Instanz (Lektion 7).
class Counter:
def __init__(self):
self.n = 0
def tick(self):
self.n += 1
return self.n
k = Counter()
k.tick()
print(k.tick(), k.n)
# 2 2| Closure | Klasse | |
|---|---|---|
| Wo der Zustand liegt | in der Zelle | im __dict__ der Instanz |
| Von außen ansehen | nur über __closure__ | direkt mit k.n |
| Passt gut, wenn | ein Verhalten, wenig Zustand | mehrere Verhalten, viel Zustand |
Eine Closure, die eine Funktion entgegennimmt und eine neue Funktion zurückgibt, heißt Dekorator. Die ursprüngliche Funktion steckt in der Zelle der inneren Funktion.
def log(func):
def wrapper(*args, **kwargs):
print("call", func.__name__)
return func(*args, **kwargs)
return wrapper
@log
def board(seat):
return "seat " + seat
print(board("12A"))
# call board
# seat 12A
print(board.__name__)
# wrapper@log ist dasselbe wie board = log(board). Der Name board zeigt jetzt auf die Funktion wrapper, und das ursprüngliche board steckt in deren Zelle func.board.__name__ jetzt wrapper. Beim Debuggen und in der Dokumentation ist das lästig.Eine einzige Zeile functools.wraps überträgt Name, Docstring und Ähnliches der ursprünglichen Funktion.
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("call", func.__name__)
return func(*args, **kwargs)
return wrapper
@log
def board(seat):
return "seat " + seat
print(board.__name__, board.__wrapped__.__name__)
# board boardWie man Dekoratoren Argumente mitgibt, Dekoratoren als Klassen baut und mehr: Das vertiefen wir im Kurs über Generatoren und Dekoratoren.
Es genügt, jeder Funktion das Gate vom Zeitpunkt ihrer Erzeugung mitzugeben.
from functools import partial
def announce(gate):
return "go to " + gate
def make_guides(gates):
return [partial(announce, gate) for gate in gates]
gs = make_guides(["A1", "B7", "C3"])
print([g() for g in gs])
# ['go to A1', 'go to B7', 'go to C3']
def make_guides_v2(gates):
return [lambda gate=gate: "go to " + gate for gate in gates]
print([g() for g in make_guides_v2(["A1", "B7", "C3"])])
# ['go to A1', 'go to B7', 'go to C3']Ab der nächsten Durchsage geht jeder der drei Fluggäste zu seinem eigenen Gate. Als Seo gegangen ist, blättert die Schichtleiterin durch die Aufzeichnungen von Band 13 und murmelt vor sich hin: „Auch die Sachen einer beendeten Funktion werden nicht entsorgt, wenn jemand sie im Handgepäck festhält. Vielleicht hält auch jemand diese zwei Koffer fest.“
Abschlussbericht — LOST-006. Die drei Hinweisfunktionen teilten sich eine Zelle der Schleifenvariable gate und lasen beim Aufruf den letzten Wert C3 (späte Bindung). Gelöst mit partial und Standardargumenten, die den Wert vom Zeitpunkt der Erzeugung festhalten.
[lambda: i for i in range(3)] zu [lambda: i * 10 for i in range(3)] änderst? Prüfe mit __closure__, wie viele Zellen es gibt.counter() so, dass es zwei Funktionen zurückgibt: tick() und reset(). Prüfe mit is, ob beide dieselbe Zelle benutzen.count_calls, der zählt, wie oft eine Funktion aufgerufen wurde. Kommt die Anzahl in ein Attribut von wrapper oder in eine Zelle? Probier beides aus.Das Notizbuch der Schichtleiterin
Auch Funktionen sind Objekte. Man kann Funktionen erzeugen und zurückgeben.
Äußere Namen, die eine innere Funktion benutzt, kommen in Zellen, und die Funktion trägt diese Zellen in __closure__ mit sich.
Eine Closure trägt keinen Wert, sondern eine Zelle (den Platz des Namensschilds). Geöffnet wird sie beim Aufruf: späte Bindung.
In einer Schleife erzeugte Funktionen teilen sich eine Zelle. Lösung: lambda i=i: i, functools.partial.
Hängt nonlocal das Etikett in der Zelle um, wird die Funktion zu einer Funktion mit Zustand. Bei viel Zustand: eine Klasse.
Ein Dekorator ist eine Closure, die eine Funktion nimmt und eine Funktion zurückgibt. functools.wraps nicht vergessen.
← Zurück
Lektion 5 · Zonen und Ausweise: Gültigkeitsbereiche
Weiter →
Lektion 7 · Der Koffer-Bauplan: Klassen und Instanzen
Ü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.