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 5:00–5:50
Verlustmeldung
LOST-007
Gemeldet von
Moon, Transportleitung einer Orchestertournee
Was fehlt
Auf einen einzigen Instrumentenkoffer kam ein Aufkleber „Zerbrechlich“. Jetzt klebt er an allen Koffern, die nach demselben Bauplan gebaut wurden.
Erledigt, wenn
Herausfinden, wo die Aufkleberliste wirklich liegt, und jedem Koffer seine eigene Aufkleberliste geben.
Internationaler Flughafen Haneulseom · Gepäckzentrum
In der Schreibtischschublade von Schichtleiterin Do Miran liegt ein Stapel Koffer-Baupläne. Mit einem einzigen Bauplan lassen sich beliebig viele Koffer derselben Form herstellen. „Der Bauplan ist ein Blatt Papier, die Koffer sind einzelne Stücke. Beides sind Dinge.“
Moon hat nur auf den Geigenkoffer „Zerbrechlich“ geklebt, sieht denselben Aufkleber aber auch am Cellokoffer. Die Schichtleiterin schlägt nicht den Koffer auf, sondern den Bauplan. Dort klebte der Aufkleber.
Die class-Anweisung erzeugt ein Klassenobjekt und bindet es an einen Namen, ist also eine Zuweisung (genau wie def in Lektion 1). Auch eine Klasse ist ein Objekt mit einem Typ, und dieser Typ ist type.
class Case:
size = "M"
def __init__(self, owner):
self.owner = owner
print(type(Case), isinstance(Case, type))
# <class 'type'> True
b = Case("cello")
print(type(b).__name__, type(b) is Case)
# Case True
print(vars(b))
# {'owner': 'cello'}
print([k for k in vars(Case) if not k.startswith("__")])
# ['size']
print(b.size, "size" in vars(b))
# M False| Was darin steckt | So siehst du es | |
|---|---|---|
| Klasse (Bauplan) | Namen aus dem Klassenrumpf: Klassenattribute, Methoden | vars(Case), Case.__dict__ |
| Instanz (Koffer) | Attribute nur dieses Koffers, meist in __init__ per self.x = ... | vars(b), b.__dict__ |
__dict__ von b steht nur owner. Trotzdem wird b.size als "M" gelesen.__dict__ der Instanz nachgesehen und, wenn es dort fehlt, in der Klasse. Fehlt etwas im Koffer, schlägt man den Bauplan auf.__dict__ einer Klasse ist ein schreibgeschütztes Fenster namens mappingproxy. Um ein Klassenattribut zu ändern, weist du es direkt der Klasse zu, etwa Case.size = "L".Das ist der Bauplan aus der Meldung:
class Case:
stickers = []
def __init__(self, owner):
self.owner = owner
a = Case("violin")
b = Case("cello")
a.stickers.append("FRAGILE")
print(b.stickers, Case.stickers)
# ['FRAGILE'] ['FRAGILE']
print(a.stickers is b.stickers, "stickers" in vars(a))
# True Falsestickers = [] läuft im Klassenrumpf ein einziges Mal. Die Liste hängt nur einmal am Bauplan.a.stickers steht nicht im __dict__ von a, wird also in der Klasse gefunden, und append verändert diese eine Liste (Koffer verändern).Was jede Instanz für sich haben soll, hängt man in __init__ an self. Vergleich unten die drei Varianten.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Weist man einer Instanz wie in der dritten Variante etwas unter demselben Namen zu, bleibt das Klassenattribut unangetastet, und im __dict__ der Instanz entsteht ein neuer Name. Für diese Instanz ist das Klassenattribut dann verdeckt (dieselbe Form wie das Verdecken eingebauter Namen in Lektion 5).
class Case:
size = "M"
a = Case()
b = Case()
a.size = "L"
print(a.size, b.size, vars(a))
# L M {'size': 'L'}
Case.size = "S"
print(a.size, b.size)
# L S
del a.size
print(a.size)
# Sb).del a.size den Namen der Instanz ab, ist wieder der Wert der Klasse zu sehen.Vergiss nicht, dass auch += eine Zuweisung ist. Wendet man += auf ein Klassenattribut mit einer Zahl an, entsteht in der Instanz ein neuer Name.
class Gate:
count = 0
g = Gate()
g.count += 1
print(g.count, Gate.count, vars(g))
# 1 0 {'count': 1}g.count += 1 ist g.count = g.count + 1. Rechts wird die 0 aus der Klasse gelesen, links landet die 1 im __dict__ von g. Soll die Zahl für alle Instanzen gemeinsam zählen, musst du wie bei Gate.count += 1 direkt der Klasse zuweisen.
Pin sagt
→ Eine Liste oder ein dict im Klassenrumpf teilen sich alle Instanzen. Leg in die Klasse nur, was wirklich gemeinsam ist, wie Konstanten oder Standardwerte. Was von Koffer zu Koffer verschieden ist, hängst du in __init__ an self.
Den Weg, den Python beim Lesen von b.x geht, zeigt das Ablaufdiagramm der Kontrolle. In dieser Lektion genügen Schritt 2 (das __dict__ der Instanz) und Schritt 4 (ein normales Attribut der Klasse). Die Schritte 1, 3 und 5 behandeln wir in Lektion 10.
Das b unten hat size sowohl in der Klasse als auch im eigenen __dict__.
Kontrollablauf
1
class Case:
2
size = "M"
3
stickers = []
4
def __init__(self, owner):
5
self.owner = owner
6
def label(self):
7
return "case of " + self.owner
8
9
b = Case("cello")
10
b.size = "L"
b.
Daten-Deskriptor in der Klasse (__get__ + __set__)?
Im __dict__ der Instanz?
Nicht-Daten-Deskriptor in der Klasse (nur __get__, wie eine Funktion)?
Ein normales Klassenattribut?
Gibt es __getattr__?
AttributeError
Ergebnis: b. →
size steht auch in der Klasse, wird aber zuerst in Schritt 2 gefunden und ist deshalb 'L'. Das Case.size in Klammern in der Ergebniszeile bedeutet: „Die Klasse hat ebenfalls einen Namen dieser Art.“label ist eine Funktion in der Klasse. Funktionen sind besondere Objekte, die in Schritt 3 hängen bleiben. Holt man sie heraus, bekommt man keine einfache Funktion, sondern eine bound method. Darum geht es im nächsten Abschnitt.Eine Methode ist eine ganz normale Funktion in der Klasse. Holt man sie über eine Instanz heraus, erzeugt Python ein neues Objekt, das Funktion und Instanz zusammenbindet: eine gebundene Methode.
class Bag:
def __init__(self, owner):
self.owner = owner
def label(self):
return "bag of " + self.owner
bag = Bag("kim")
print(bag.label is bag.label, bag.label == bag.label)
# False True
print(bag.label.__self__ is bag, bag.label.__func__ is Bag.label)
# True True
print(bag.label(), Bag.label(bag))
# bag of kim bag of kim
print(type(bag.label).__name__, type(Bag.label).__name__)
# method functionbag.label schreibst, entsteht eine neue gebundene Methode. Deshalb ergibt is False. Weil dieselbe Funktion an dieselbe Instanz gebunden ist, ergibt == True.__self__ ist die gebundene Instanz, __func__ die ursprüngliche Funktion der Klasse.bag.label() ist dasselbe wie Bag.label(bag). self ist nur das erste Argument, das die gebundene Methode für dich ausfüllt.Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
vars, getattr, setattr, hasattr (5 min)Manchmal steht der Attributname erst zur Laufzeit als String fest, zum Beispiel als Schlüssel aus einer Konfigurationsdatei oder als Spaltenname, den jemand auf dem Bildschirm ausgewählt hat.
class Bag:
def __init__(self, owner):
self.owner = owner
bag = Bag("kim")
setattr(bag, "color", "red")
print(getattr(bag, "color"), hasattr(bag, "weight"), getattr(bag, "weight", 0))
# red False 0
print(vars(bag))
# {'owner': 'kim', 'color': 'red'}
vars(bag)["tag"] = "T1"
print(bag.tag, vars(bag) is bag.__dict__)
# T1 True
bag.weight
# AttributeError: 'Bag' object has no attribute 'weight'setattr(obj, "x", v) entspricht obj.x = v, getattr(obj, "x") entspricht obj.x. Das dritte Argument von getattr ist der Standardwert, falls das Attribut fehlt.hasattr probiert tatsächlich ein getattr und entscheidet danach, ob ein AttributeError kommt.vars() einer Instanz ist keine Momentaufnahme, sondern das echte __dict__. Das ist anders als locals() in einer Funktion aus Lektion 5.bag.colour = ...) wird stillschweigend zu einem neuen Attribut.__slots__ und weitere neue Funktionen (5 min)Schreibt man Attributnamen in __slots__, bekommen Instanzen kein __dict__, und nur die aufgeführten Namen sind erlaubt. In CPython spart das außerdem Speicher, weil nicht jede Instanz ein eigenes Dictionary hat.
class Slim:
__slots__ = ("owner",)
def __init__(self, owner):
self.owner = owner
s = Slim("kim")
print(s.owner, hasattr(s, "__dict__"))
# kim False
s.color = "red"
# AttributeError: 'Slim' object has no attribute 'color' and no __dict__ for setting new attributesSeit 3.13 haben Klassen __static_attributes__. Darin stehen sortiert die Namen, denen in den Methoden der Klasse per self.name = ... ein Wert zugewiesen wird.
class Trunk:
def __init__(self, owner):
self.owner = owner
self.tags = []
def pack(self):
self.weight = 10
print(Trunk.__static_attributes__)
# ('owner', 'tags', 'weight')Zum Schluss die verzögerte Auswertung von Annotationen in 3.14 (PEP 649, 749). Annotationen im Klassenrumpf werden nicht beim Erzeugen der Klasse ausgewertet, sondern erst beim ersten Lesen von __annotations__. Deshalb entsteht die Klasse auch dann, wenn in einer Annotation ein Name steht, der noch gar nicht definiert ist. Bis 3.13 gab es in der Zeile friend: Missing einen NameError.
class Partner:
friend: Missing
print("created", Partner.__name__)
# created Partner
class Missing:
pass
print(Partner.__annotations__)
# {'friend': <class '__main__.Missing'>}Die Aufkleberliste kommt weg vom Bauplan und an jeden einzelnen Koffer. Gemeinsame Werte (die Standardgröße) bleiben in der Klasse.
class Case:
size = "M"
def __init__(self, owner):
self.owner = owner
self.stickers = []
a = Case("violin")
b = Case("cello")
a.stickers.append("FRAGILE")
print(a.stickers, b.stickers)
# ['FRAGILE'] []
print(vars(a))
# {'owner': 'violin', 'stickers': ['FRAGILE']}
print(Case.__static_attributes__)
# ('owner', 'stickers')Jetzt klebt „Zerbrechlich“ nur noch am Geigenkoffer. Als Moon gegangen ist, holt Pin die Aufzeichnungen zu den Geisterkoffern von Band 13 auf den Bildschirm. Beide Koffer wurden nach demselben Bauplan gebaut, und das Attribut partner des einen zeigt auf den anderen, das partner des anderen wiederum auf den ersten. Die Schichtleiterin zieht mit dem Stift einen Kreis um diese Zeile.
Abschlussbericht — LOST-007. Die Aufkleberliste war ein Klassenattribut, also teilten sich alle Koffer dieselbe Liste. Gelöst mit self.stickers = [] in __init__, sodass jeder Koffer eine neue Liste bekommt.
Case.stickers = [] in der Klasse stehen und führe a.stickers += ["FRAGILE"] aus. Was wird aus b.stickers und vars(a)? (Tipp: Das += aus Lektion 2 und das += aus 7-2 passieren gleichzeitig.)__slots__ = ("owner",) bekommt das Klassenattribut size = "M". Kann man in einer Instanz s.size lesen? Und was ist mit s.size = "L"?getattr die drei Attribute ["owner", "color", "weight"] nacheinander holt und ausgibt, wobei fehlende Attribute als "-" erscheinen.Das Notizbuch der Schichtleiterin
Auch Klassen sind Objekte. type(Case) ist type. Auch die class-Anweisung ist eine Zuweisung, die einen Namen bindet.
Instanzen haben ihr eigenes __dict__. Gelesen wird in der Reihenfolge Instanz → Klasse.
Klassenattribute teilen sich alle Instanzen. Steckt darin ein veränderliches Objekt, sieht jeder eine Änderung an einer Stelle.
Eine Zuweisung unter demselben Namen in der Instanz verdeckt das Klassenattribut. Auch += ist eine Zuweisung.
bag.m erzeugt bei jedem Zugriff eine neue gebundene Methode (is ist False, == ist True). __self__, __func__.
Mit getattr / setattr / hasattr behandelt man Namen als Strings. vars(obj) ist das echte __dict__.
__slots__ verhindert neue Attribute. __static_attributes__ (3.13), verzögerte Auswertung von Annotationen (3.14).
← Zurück
Lektion 6 · Handgepäck: Closures
Weiter →
Lektion 8 · Flughafen-Standardverfahren: Dunder-Methoden
Ü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.