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 6:40–7:30
Verlustmeldung
LOST-009
Gemeldet von
Han, Entwicklung für Luftfrachtvorschriften
Was fehlt
Die Gefahrgutprüfung des Powerbank-Bauplans PowerBank. Er erbt sowohl die Vorschrift „Handgepäck“ als auch die Vorschrift „Gefahrgut“, doch im Prüfergebnis fehlt der Gefahrgutpunkt.
Erledigt, wenn
Aufklären, in welcher Reihenfolge (MRO) PowerBank seine Vorschriften liest, und so korrigieren, dass beide Vorschriften laufen.
Internationaler Flughafen Haneulseom · Gepäckzentrum
Han hat einen neuen Bauplan für Powerbanks angelegt. Er erbt sowohl vom Handgepäck-Bauplan als auch vom Gefahrgut-Bauplan, also sollten auch beide Prüfungen laufen. Auf dem Prüfbogen steht aber nur die Größenprüfung, die Gefahrgutprüfung fehlt.
Schichtleiterin Do Miran legt die beiden Baupläne übereinander. „Gibt es zwei übergeordnete Baupläne, ist festgelegt, welcher zuerst gelesen wird. Sehen wir uns zuerst diese Reihenfolge an.“
class CarryOn(Item): bedeutet: „Was es in CarryOn nicht gibt, wird in Item gesucht.“ Diese Suchreihenfolge steht im __mro__ (Method Resolution Order) der Klasse.
class Item:
kind = "item"
def label(self):
return f"[{self.kind}]"
class CarryOn(Item):
kind = "carry-on"
b = CarryOn()
print(b.label())
# [carry-on]
print(CarryOn.__mro__)
# (<class '__main__.CarryOn'>, <class '__main__.Item'>, <class 'object'>)
print("label" in vars(CarryOn), "label" in vars(Item))
# False Trueb.label() wird genauso gelesen wie in Lektion 7. Im __dict__ der Instanz steht es nicht, also wird __mro__ von vorn durchsucht. In CarryOn gibt es kein label, gefunden wird es in Item. Das self.kind in label wird aber wieder ab der Instanz gesucht, deshalb kommt CarryOn.kind, also "carry-on", heraus.
Überschreiben ist keine besondere Syntax. Schreibt man denselben Namen in den abgeleiteten Bauplan, gewinnt, was in der MRO zuerst gefunden wird. Will man die Version der Elternklasse weiterverwenden, ruft man sie mit super() auf.
class Fragile(Item):
kind = "fragile"
def label(self):
return super().label() + "!"
print(Fragile().label())
# [fragile]!Die MRO jeder Klasse endet mit object. Von dort kommen alle Standardgriffe wie __repr__ und __eq__ (Lektion 8).
Der Bauplan aus der Meldung erbt von zwei Bauplänen. Beide erben von Item, gezeichnet ergibt das eine Raute (diamond).
class Item:
rule = "item"
tag = "plain"
class CarryOn(Item):
rule = "carry-on"
class Hazmat(Item):
rule = "hazmat"
tag = "red"
class PowerBank(CarryOn, Hazmat):
pass
print([c.__name__ for c in PowerBank.__mro__])
# ['PowerBank', 'CarryOn', 'Hazmat', 'Item', 'object']
print(PowerBank.rule, PowerBank.tag)
# carry-on redrule wurde zuerst in CarryOn gefunden. Die linke Elternklasse in der Klammer kommt zuerst.tag gibt es in CarryOn nicht, also wurde es im nächsten Feld, Hazmat, gefunden. Bis zum "plain" von Item geht es gar nicht hinunter.Item erscheint nur einmal, und zwar erst nachdem beide Kindklassen an der Reihe waren. Es ist keine Tiefensuche, die den linken Weg bis ganz unten verfolgt.Vertausch im Erkunder unten die Reihenfolge der Elternklassen und klick auch den letzten Bauplan an, bei dem sich keine Reihenfolge bilden lässt.
MRO-Erkunder
Attribut
Suchreihenfolge (__mro__)
Nirgends gefunden
super() ist nicht die Elternklasse, sondern das nächste Feld (10 min)Jetzt der eigentliche Code aus der Meldung. Jede Vorschrift gibt ihre Prüfpunkte zurück und hängt die Punkte der übergeordneten Vorschrift an.
class Item:
def check(self):
return ["item"]
class CarryOn(Item):
def check(self):
return ["size"] + Item.check(self)
class Hazmat(Item):
def check(self):
return ["fire"] + super().check()
class PowerBank(CarryOn, Hazmat):
pass
print(PowerBank().check())
# ['size', 'item']Wer CarryOn geschrieben hat, dachte „meine Elternklasse ist Item“ und hat Item.check(self) direkt aufgerufen. Für sich allein stimmt das. In der MRO von PowerBank folgt auf CarryOn aber nicht Item, sondern Hazmat. Durch den direkten Aufruf wurde Hazmat übersprungen.
super() ist nicht „meine Elternklasse“, sondern das Feld nach der aktuellen Klasse in der MRO von type(self). Selbst bei demselben CarryOn.check hängt das nächste Feld davon ab, wer es aufruft.
class CarryOn(Item):
def check(self):
return ["size"] + super().check()
class PowerBank(CarryOn, Hazmat):
pass
print(PowerBank().check())
# ['size', 'fire', 'item']
print(CarryOn().check())
# ['size', 'item']
print(super(CarryOn, PowerBank()).check())
# ['fire', 'item']PowerBank() läuft die Kette in der Reihenfolge CarryOn → Hazmat → Item.CarryOn() allein ist die MRO CarryOn, Item, object, und super() ist genau Item.super(CarryOn, obj) schreibt ausdrücklich hin: „Such in der MRO von obj ab dem Feld nach CarryOn.“ Ein super() ohne Argumente füllt diese beiden Werte selbst aus.Vergleich auf der Etikettentafel unten die beiden Varianten. Achte darauf, wie sich die Aufrufrahmen stapeln und welche Zeilennummer gerade läuft.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Die Kette hält nur, wenn alle super() aufrufen. Fällt auch nur einer aus, laufen die Felder danach nicht mehr. Bei __init__ ist es genauso. Nimmt jeder Bauplan andere Argumente, holt sich jeder nur seine eigenen heraus und reicht den Rest mit **kw an das nächste Feld weiter.
class Item:
pass
class CarryOn(Item):
def __init__(self, cm=55, **kw):
self.cm = cm
super().__init__(**kw)
class Hazmat(Item):
def __init__(self, wh=100, **kw):
self.wh = wh
super().__init__(**kw)
class PowerBank(CarryOn, Hazmat):
pass
print(vars(PowerBank(cm=20, wh=99)))
# {'cm': 20, 'wh': 99}CarryOn.__init__ holt nur cm heraus und reicht wh=99 weiter. Das nächste Feld, Hazmat, nimmt es entgegen, und am Ende läuft object.__init__() ohne Argumente.
Pin sagt
→ Rufst du in einer Methode die Elternklasse direkt beim Namen auf (Item.check(self)), überspringst du bei Mehrfachvererbung die Baupläne dazwischen. Zum Weiterrufen immer super() benutzen.
Python bildet die MRO mit der C3-Linearisierung. Sie hält drei Zusagen ein:
Für PowerBank(CarryOn, Hazmat) geht die Rechnung so. Man legt die MROs der Elternklassen und die Liste der Elternklassen nebeneinander.
| Verbleibende Listen | Gewählt | Grund |
|---|---|---|
[CarryOn, Item, object] [Hazmat, Item, object] [CarryOn, Hazmat] | CarryOn | erster Kopf, kommt in keinem Rest einer anderen Liste vor |
[Item, object] [Hazmat, Item, object] [Hazmat] | Hazmat | Item steht im Rest der zweiten Liste und wird zurückgestellt |
[Item, object] [Item, object] | Item | steht jetzt in keinem Rest mehr |
[object] [object] | object |
Das Ergebnis ist PowerBank, gefolgt von CarryOn, Hazmat, Item, object. Klappst du im Erkunder die „C3-Schritte“ auf, siehst du denselben Ablauf in vier Zeilen. Ganz vorn in jeder Zeile steht die Klasse, die in diesem Schritt gewählt wurde, dahinter die Listen, die zu diesem Zeitpunkt noch übrig waren.
Lassen sich nicht alle Zusagen einhalten, entsteht die Klasse nicht.
class Item:
pass
class Hazmat(Item):
pass
class Odd(Item, Hazmat):
pass
# TypeError: Cannot create a consistent method resolution order (MRO) for bases Item, HazmatNach der Reihenfolge in der Klammer müsste Item vor Hazmat stehen (Zusage 2), nach der Regel „Kind zuerst“ müsste Hazmat vor Item stehen (Zusage 1). Eine Reihenfolge, die beides erfüllt, gibt es nicht. Die Lösung ist einfach: die spezifischere Elternklasse nach links.
isinstance und Komposition (6 min)Ein Mixin ist eine kleine Klasse, die man nicht allein benutzt, sondern einer anderen Klasse als einzelne Funktion beimischt. Man hängt Mixin an den Namen und setzt es links in die Liste der Elternklassen. Nur dann wird es in der MRO zuerst gefunden.
class ReprMixin:
def __repr__(self):
fields = ", ".join(f"{k}={v!r}" for k, v in vars(self).items())
return f"{type(self).__name__}({fields})"
class Tag(ReprMixin):
def __init__(self, code, kg):
self.code, self.kg = code, kg
print([Tag("ICN", 12)])
# [Tag(code='ICN', kg=12)]MRO-Erkunder
Attribut
Suchreihenfolge (__mro__)
Nirgends gefunden
isinstance(obj, C) prüft, ob C in der MRO von type(obj) steht. issubclass fragt dasselbe für zwei Klassen. Damit ist es weiter gefasst als type(x) is C.
class Item:
pass
class Hazmat(Item):
pass
class PowerBank(Hazmat):
pass
pb = PowerBank()
print(isinstance(pb, Item), issubclass(PowerBank, Hazmat), isinstance(pb, (int, Hazmat)))
# True True True
print(type(pb) is Hazmat)
# FalseVererbung nimmt man, wenn gilt: „X ist eine Art von Y.“ Gilt dagegen „X hat ein Y“, ist Komposition besser (ein anderes Objekt als Attribut halten). Hielte man auch die Vorschriften aus der Meldung statt per Vererbung als Liste von Vorschriftsobjekten, wäre die Reihenfolge sichtbar, und man müsste sich nie mit der MRO befassen.
class Rule:
def __init__(self, name, test):
self.name, self.test = name, test
class Inspector:
def __init__(self, *rules):
self.rules = list(rules)
def failed(self, item):
return [r.name for r in self.rules if not r.test(item)]
size = Rule("size", lambda it: it["cm"] <= 55)
fire = Rule("fire", lambda it: it["wh"] <= 100)
print(Inspector(size, fire).failed({"cm": 20, "wh": 160}))
# ['fire']Erbt man von abc.ABC und setzt @abstractmethod davor, entsteht ein „Feld, das die Kindklasse unbedingt ausfüllen muss“. Von einer Kindklasse, die es nicht ausfüllt, lassen sich keine Instanzen erzeugen. __init_subclass__ ist ein Griff, der in der Elternklasse jedes Mal aufgerufen wird, wenn eine Kindklasse entsteht. Damit baut man zum Beispiel ein Register der Baupläne.
from abc import ABC, abstractmethod
class Rule(ABC):
registry = []
def __init_subclass__(cls, **kw):
super().__init_subclass__(**kw)
Rule.registry.append(cls.__name__)
@abstractmethod
def check(self, item): ...
class SizeRule(Rule):
def check(self, item):
return item["cm"] <= 55
class FireRule(Rule):
pass
print(Rule.registry, SizeRule().check({"cm": 40}))
# ['SizeRule', 'FireRule'] True
FireRule()
# TypeError: Can't instantiate abstract class FireRule without an implementation for abstract method 'check'Wir ersetzen in CarryOn das Item.check(self) durch super().check() und lassen den Prüfbogen zusammen mit der MRO noch einmal ausgeben.
class Item:
def check(self):
return ["item"]
class CarryOn(Item):
def check(self):
return ["size"] + super().check()
class Hazmat(Item):
def check(self):
return ["fire"] + super().check()
class PowerBank(CarryOn, Hazmat):
pass
print([c.__name__ for c in PowerBank.__mro__])
# ['PowerBank', 'CarryOn', 'Hazmat', 'Item', 'object']
print(PowerBank().check())
# ['size', 'fire', 'item']fire steht wieder auf dem Prüfbogen. Han nickt. „Ich hätte nicht die Elternklasse aufrufen sollen, sondern den Nächsten in der Reihe.“ Die Schichtleiterin heftet die beiden Baupläne wieder zusammen. „Die Reihenfolge legt Python fest. Wir müssen nur dafür sorgen, dass die Kette nicht reißt.“
Abschlussbericht — LOST-009. CarryOn rief die Elternklasse direkt beim Namen auf und übersprang so das nächste Feld der MRO, Hazmat. Gelöst, indem alle Baupläne super() aufrufen.
class A: pass, class B(A): pass, class C(A): pass, class D(C, B): pass zuerst mit einer C3-Tabelle und prüfe sie dann mit D.__mro__.Hazmat.check auf return ["fire"] änderst (also ohne super()-Aufruf).Inspector aus 9-5 um eine Gewichtsvorschrift, vertausch die Reihenfolge der Vorschriften und sieh nach, wie sich die Reihenfolge der Ergebnisliste ändert.Das Notizbuch der Schichtleiterin
Attribute werden in der Reihenfolge von Cls.__mro__ gesucht. Überschreiben heißt nur: Wer zuerst gefunden wird, gewinnt.
Bei Mehrfachvererbung kommt die gemeinsame Elternklasse nur einmal, nach allen Kindklassen (C3-Linearisierung).
super() ist nicht die Elternklasse, sondern das nächste Feld in der MRO von type(self). Die Kette hält nur, wenn alle es aufrufen.
In einer __init__-Kette holt jeder seine eigenen Argumente heraus und reicht den Rest mit **kw weiter.
Lässt sich keine MRO bilden, entsteht die Klasse gar nicht. Die spezifischere Elternklasse nach links.
Mixins nach links. isinstance schaut in die MRO. Bei „hat ein“ lieber Komposition als Vererbung.
← Zurück
Lektion 8 · Flughafen-Standardverfahren: Dunder-Methoden
Weiter →
Lektion 10 · Die Kontrolle: das Geheimnis des Attributzugriffs
Ü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.