R

ReflexKR

Reflex-Dokumentation

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

Der Etiketten-Flughafen: Pythons Objektmodell
Lektion 9

Zeit 6:40–7:30

Der Bauplan hinter dem Bauplan: Vererbung und MRO

Verlustmeldung

LOST-009

Eine der beiden Vorschriften ist verschwunden

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.“

9-1. Übergeordnete Baupläne: Vererbung und Überschreiben (7 min)

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 True

b.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).

9-2. Mehrfachvererbung und die Raute (8 min)

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 red
  • rule 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

C3-Schritte
Bei (CarryOn, Hazmat) wird rule in CarryOn gefunden, bei (Hazmat, CarryOn) in Hazmat. Bei (Item, Hazmat) lässt sich keine MRO bilden, die Klasse entsteht gar nicht erst.

9-3. 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']
  • Bei PowerBank() läuft die Kette in der Reihenfolge CarryOn → Hazmat → Item.
  • Bei 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)

Bei super() wandert die Zeilenanzeige 6 → 9 → 3, drei check-Rahmen stapeln sich, und das self aller drei Rahmen ist derselbe PowerBank-Koffer. Bei Item.check(self) geht es direkt 6 → 3, und es gibt nur zwei Rahmen. Die __class__-Zelle an Funktionen, die super() benutzen, verrät, „in welcher Klasse man gerade ist“.

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.

9-4. C3-Linearisierung: die Regel für die Reihenfolge (6 min)

Python bildet die MRO mit der C3-Linearisierung. Sie hält drei Zusagen ein:

  1. Ein Kind steht immer vor seinen Eltern.
  2. Die Reihenfolge der Elternklassen in der Klammer bleibt erhalten.
  3. Auch die MRO-Reihenfolge jeder Elternklasse bleibt erhalten.

Für PowerBank(CarryOn, Hazmat) geht die Rechnung so. Man legt die MROs der Elternklassen und die Liste der Elternklassen nebeneinander.

Verbleibende ListenGewähltGrund
[CarryOn, Item, object] [Hazmat, Item, object] [CarryOn, Hazmat]CarryOnerster Kopf, kommt in keinem Rest einer anderen Liste vor
[Item, object] [Hazmat, Item, object] [Hazmat]HazmatItem steht im Rest der zweiten Liste und wird zurückgestellt
[Item, object] [Item, object]Itemsteht 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, Hazmat

Nach 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.

9-5. Mixins, 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

C3-Schritte
Steht LabelMixin links, wird describe im Mixin gefunden. Steht es rechts, wird zuerst das von Item gefunden, und das describe des Mixins ist verdeckt. prefix kommt nur einmal vor und wird in beiden Fällen 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)
# False

Vererbung 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']

9-6. Abstrakte Baupläne und die Registrierung abgeleiteter Baupläne (3 min)

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'

Fall abgeschlossen (10 min)

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.

Übungen

  1. Berechne die MRO von 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__.
  2. Sag voraus, was auf dem Prüfbogen steht, wenn du im Code des Fallabschlusses nur Hazmat.check auf return ["fire"] änderst (also ohne super()-Aufruf).
  3. Ergänze den 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.

Erhoben: deine E-Mail-Adresse · Zweck: Hinweise auf neue Lektionen oder den Start dieses Kurses · Speicherdauer: bis du die Löschung verlangst. Keine andere Nutzung, keine Weitergabe. Antworte einfach auf eine Benachrichtigung, dann löschen wir sie sofort.

Fragen & Antworten

Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.

Wird als reiner Text angezeigt, ohne Links oder Formatierung.

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
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex