Zum Inhalt springen
aviral gupta

// I2.3 · ca. 35 Min. · Aufbau

Vererbung und die MRO

Nach dieser Lektion bauen Sie eine Unterklasse, die ihre Basisklasse nutzt und erweitert, sagen vorher, welche Methode in einer Klassenhierarchie läuft, und prüfen Typen mit isinstance und issubclass.

Lektion 3 von 6 in I2 Klassen

Danach können Sie

  • Eine Unterklasse schreiben, die Methoden erbt, überschreibt und mit super() erweitert
  • Vorhersagen, welche Methode läuft, entlang der Methodenauflösungsreihenfolge, auch bei Mehrfachvererbung
  • Typen mit isinstance und issubclass prüfen, wobei beide Unterklassen einschließen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Modul I1: Unterklassen haben Sie schon geschrieben. Jede eigene Exception ist eine. Was gibt das aus?

    class AppError(Exception):
        pass
    
    
    err = AppError("disk full")
    print(isinstance(err, Exception), str(err))
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: introduce ist nur in Animal definiert. Was gibt das aus?

    class Animal:
        def __init__(self, name):
            self.name = name
    
        def speak(self):
            return "..."
    
        def introduce(self):
            return f"{self.name} says {self.speak()}"
    
    
    class Dog(Animal):
        def speak(self):
            return "Woof"
    
    
    print(Dog("Rex").introduce())
  3. Üben · Aufgabe 3 von 7

    Füllen Sie die Lücke, damit Manager zuerst Employee den Namen einrichten lässt.

    class Employee:
        def __init__(self, name):
            self.name = name
    
    
    class Manager(Employee):
        def __init__(self, name, reports):
            ____.__init__(name)
            self.reports = reports
    .__init__(name)
  4. Üben · Aufgabe 4 von 7

    Eine Raute: B und C leiten beide von A ab. Was gibt das aus?

    class A:
        def hello(self):
            return "A"
    
    
    class B(A):
        pass
    
    
    class C(A):
        def hello(self):
            return "C"
    
    
    class D(B, C):
        pass
    
    
    print(D().hello(), [k.__name__ for k in D.__mro__])
  5. Üben · Aufgabe 5 von 7

    Dog ist eine Unterklasse von Animal, und rex = Dog(). Ordnen Sie jedem Ausdruck seinen Wert zu.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Jedes setup ruft super() auf. Was gibt das aus?

    class Base:
        def setup(self):
            return ["Base"]
    
    
    class Logged(Base):
        def setup(self):
            return ["Logged"] + super().setup()
    
    
    class Cached(Base):
        def setup(self):
            return ["Cached"] + super().setup()
    
    
    class Service(Logged, Cached):
        def setup(self):
            return ["Service"] + super().setup()
    
    
    print(Service().setup())
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie eine Basisklasse Shape mit einem Namen, einem area(), das 0.0 liefert, und describe(), das Namen und Fläche auf zwei Nachkommastellen liefert. Leiten Sie Circle(radius) und Square(side) ab: Beide rufen super().__init__ mit ihrem Namen auf und überschreiben area. Gehen Sie in einer Schleife über Circle(1) und Square(3) und geben Sie jeweils describe() aus.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Benachrichtiger mit gemeinsamem send

send steht einmal, in Notifier, und ruft self.format auf, das jede Unterklasse überschreibt. EmailNotifier erweitert __init__ mit super(). SmsNotifier baut auf dem format der Basis auf und kürzt den Text. LoudEmail erbt von zwei Klassen; seine MRO setzt Loud vor EmailNotifier, also erreicht super().format in Loud das EmailNotifier, das den Betreff ergänzt, bevor Loud alles in Großbuchstaben setzt.

main.py

class Notifier:
    """Sends a message; subclasses decide how it looks."""

    def __init__(self, recipient: str) -> None:
        self.recipient = recipient

    def format(self, text: str) -> str:
        return text

    def send(self, text: str) -> str:
        return f"to {self.recipient}: {self.format(text)}"


class EmailNotifier(Notifier):
    def __init__(self, recipient: str, subject: str) -> None:
        super().__init__(recipient)
        self.subject = subject

    def format(self, text: str) -> str:
        return f"[{self.subject}] {text}"


class SmsNotifier(Notifier):
    LIMIT = 20

    def format(self, text: str) -> str:
        text = super().format(text)
        return text if len(text) <= self.LIMIT else text[: self.LIMIT - 3] + "..."


class Loud(Notifier):
    def format(self, text: str) -> str:
        return super().format(text).upper()


class LoudEmail(Loud, EmailNotifier):
    pass


notifiers: list[Notifier] = [
    EmailNotifier("ada@example.com", "Invoice"),
    SmsNotifier("0151-000000"),
    LoudEmail("ops@example.com", "Alert"),
]
for notifier in notifiers:
    print(notifier.send("Your order has shipped"))

print([cls.__name__ for cls in LoudEmail.__mro__])
print(isinstance(notifiers[2], EmailNotifier), issubclass(SmsNotifier, EmailNotifier))

Ausführen mit

python main.py

Ausgabe

to ada@example.com: [Invoice] Your order has shipped
to 0151-000000: Your order has sh...
to ops@example.com: [ALERT] YOUR ORDER HAS SHIPPED
['LoudEmail', 'Loud', 'EmailNotifier', 'Notifier', 'object']
True False
  • SmsNotifier hat kein eigenes __init__, erbt also das von Notifier, das nur den Empfänger nimmt.
  • LoudEmail("ops@example.com", "Alert") verwendet das __init__ von EmailNotifier, gefunden über die MRO.
  • Loud leitet von Notifier ab, trotzdem führt sein Aufruf super().format die Methode EmailNotifier.format aus.
  • notifiers hat den Typ list[Notifier]: Jede Instanz einer Unterklasse ist ein Notifier.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Bücher im Shop

Book ist ein Product mit einem Autor. Vervollständigen Sie die Klasse: Ihr __init__ nimmt name, price und author, lässt Product mit super() name und price speichern und speichert author. Bücher haben einen Mehrwertsteuersatz von 0.07 statt 0.19: Überschreiben Sie die Klassenvariable VAT. label() eines Buchs hängt den Autor in Klammern an das label von Product an, etwa Dune: 10.70 EUR (Herbert). Nutzen Sie Product.label mit super().

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Beginnen Sie Book.__init__ mit super().__init__(name, price); setzen Sie danach self.author.

  2. Hinweis 2

    Schreiben Sie VAT = 0.07 in den Rumpf von Book. price_with_tax liest self.VAT und findet jetzt zuerst den Wert von Book.

  3. Hinweis 3

    def label(self) -> str: return f"{super().label()} ({self.author})"

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

class Product:
    VAT = 0.19

    def __init__(self, name: str, price: float) -> None:
        self.name = name
        self.price = price

    def price_with_tax(self) -> float:
        return round(self.price * (1 + self.VAT), 2)

    def label(self) -> str:
        return f"{self.name}: {self.price_with_tax():.2f} EUR"


class Book(Product):
    VAT = 0.07

    def __init__(self, name: str, price: float, author: str) -> None:
        super().__init__(name, price)
        self.author = author

    def label(self) -> str:
        return f"{super().label()} ({self.author})"
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

class Product:
    VAT = 0.19

    def __init__(self, name: str, price: float) -> None:
        self.name = name
        self.price = price

    def price_with_tax(self) -> float:
        return round(self.price * (1 + self.VAT), 2)

    def label(self) -> str:
        return f"{self.name}: {self.price_with_tax():.2f} EUR"


class Book(Product):
    def __init__(self, name: str, price: float, author: str) -> None:
        self.author = author

test_main.py

from main import Book, Product


def test_is_product():
    """Ein Book ist ein Product"""
    book = Book("Dune", 10.0, "Herbert")
    assert isinstance(book, Product), "Book muss von Product ableiten"


def test_base_attributes():
    """Book behält name und price über super().__init__"""
    book = Book("Dune", 10.0, "Herbert")
    got = getattr(book, "name", None), getattr(book, "price", None), getattr(book, "author", None)
    assert got == ("Dune", 10.0, "Herbert"), f"name, price und author sind {got!r}"


def test_book_vat():
    """Bücher haben 7 % Mehrwertsteuer"""
    got = Book("Dune", 10.0, "Herbert").price_with_tax()
    assert got == 10.7, f"price_with_tax() eines Buchs für 10.00 lieferte {got!r}, erwartet: 10.7"


def test_product_unchanged():
    """Andere Produkte haben weiter 19 % Mehrwertsteuer"""
    got = Product("Mug", 10.0).price_with_tax()
    assert got == 11.9, f"price_with_tax() eines Produkts für 10.00 lieferte {got!r}, erwartet: 11.9"


def test_label():
    """Das label eines Buchs endet mit dem Autor"""
    got = Book("Dune", 10.0, "Herbert").label()
    assert got == "Dune: 10.70 EUR (Herbert)", f"label() lieferte {got!r}"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 2

Ein Job aus Schritten

Job(Validate, Save).run([]) soll ["validate", "save", "base"] liefern: Jede Klasse der MRO fügt ihr Wort einmal an. Reparieren Sie zuerst Save, das vergisst, das Protokoll mit super() weiterzugeben. Schreiben Sie dann mro_names(cls), das die Klassennamen aus cls.__mro__ ohne object liefert, und count_steps(items), das die Elemente zählt, die Instanzen von Step oder einer Unterklasse davon sind.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Ersetzen Sie in Save.run return log durch return super().run(log), wie in Validate.

  2. Hinweis 2

    cls.__mro__ ist ein Tupel von Klassen; jede hat einen __name__. Lassen Sie die weg, die object ist.

  3. Hinweis 3

    isinstance(item, Step) ist für Step und jede Unterklasse True. sum(1 for item in items if ...) zählt die Treffer.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

class Step:
    def run(self, log: list[str]) -> list[str]:
        log.append("base")
        return log


class Validate(Step):
    def run(self, log: list[str]) -> list[str]:
        log.append("validate")
        return super().run(log)


class Save(Step):
    def run(self, log: list[str]) -> list[str]:
        log.append("save")
        return super().run(log)


class Job(Validate, Save):
    pass


def mro_names(cls: type) -> list[str]:
    return [klass.__name__ for klass in cls.__mro__ if klass is not object]


def count_steps(items: list[object]) -> int:
    return sum(1 for item in items if isinstance(item, Step))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

class Step:
    def run(self, log: list[str]) -> list[str]:
        log.append("base")
        return log


class Validate(Step):
    def run(self, log: list[str]) -> list[str]:
        log.append("validate")
        return super().run(log)


class Save(Step):
    def run(self, log: list[str]) -> list[str]:
        log.append("save")
        return log


class Job(Validate, Save):
    pass


def mro_names(cls: type) -> list[str]:
    return []


def count_steps(items: list[object]) -> int:
    return 0

test_main.py

from main import Job, Save, Step, Validate, count_steps, mro_names


def test_job_runs_every_step():
    """Job führt validate, save und base je einmal aus"""
    got = Job().run([])
    assert got == ["validate", "save", "base"], f"Job().run([]) lieferte {got!r}"


def test_save_alone():
    """Auch Save allein erreicht den Basisschritt"""
    got = Save().run([])
    assert got == ["save", "base"], f"Save().run([]) lieferte {got!r}"


def test_mro_names():
    """mro_names listet die MRO ohne object"""
    got = mro_names(Job)
    assert got == ["Job", "Validate", "Save", "Step"], f"mro_names(Job) lieferte {got!r}"


def test_count_steps():
    """count_steps zählt Step-Instanzen, Unterklassen eingeschlossen"""
    got = count_steps([Job(), Save(), Step(), "save", 3, Validate])
    assert got == 3, f"count_steps lieferte {got!r}, erwartet: 3 (die Klasse Validate selbst ist keine Instanz)"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

super().__init__() vergessen

class Animal:
    def __init__(self, name):
        self.name = name


class Dog(Animal):
    def __init__(self, name, breed):
        self.breed = breed


print(Dog("Rex", "collie").name)

Was Python ausgibt

AttributeError: 'Dog' object has no attribute 'name'

Warum, und die Lösung

Das __init__ von Dog überschreibt das von Animal, also läuft Animal.__init__ nie, und self.name wird nie gesetzt. Python ruft das __init__ der Basis nicht von selbst auf. Beginnen Sie das __init__ der Unterklasse mit super().__init__(name) und ergänzen Sie dann das Neue.

self an super() übergeben

class Animal:
    def __init__(self, name):
        self.name = name


class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(self, name)
        self.breed = breed


Dog("Rex", "collie")

Was Python ausgibt

TypeError: Animal.__init__() takes 2 positional arguments but 3 were given

Warum, und die Lösung

super() ist schon an das Objekt gebunden, wie bei self.method(); die Instanz wird also für Sie übergeben. self noch einmal zu übergeben, ist ein Argument zu viel. Schreiben Sie super().__init__(name). Nur die alte Form über die Klasse, Animal.__init__(self, name), nimmt self ausdrücklich.

Basisklassen in unmöglicher Reihenfolge

class A:
    pass


class B(A):
    pass


class C(A, B):
    pass

Was Python ausgibt

TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B

Warum, und die Lösung

C(A, B) verlangt A vor B, aber B ist eine Unterklasse von A und muss davor stehen. Keine Reihenfolge erfüllt beides, also scheitert die class-Anweisung. Nennen Sie Unterklassen vor ihren Basen, class C(B, A), oder einfach class C(B), denn B bringt A schon mit.

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Erben, überschreiben, erweitern

class Dog(Animal): macht Dog zu einer Unterklasse von Animal. Findet Python ein Attribut oder eine Methode nicht in Dog, sucht es in Animal weiter, und so fort die Kette hinauf. Eine gleichnamige Methode in Dog überschreibt die aus Animal. Um sie zu erweitern statt zu ersetzen, rufen Sie die Basisversion mit super() auf: super().__init__(name) lässt Animal seinen Teil einrichten, bevor Dog eigene Attribute ergänzt. Fehlt dieser Aufruf, entstehen die Attribute der Basisklasse nie. Methoden werden auf der Klasse des Objekts gesucht; eine Basismethode, die self.speak() aufruft, erreicht also die Überschreibung der Unterklasse.

Die Methodenauflösungsreihenfolge

Jede Klasse hat __mro__ (Method Resolution Order): das Tupel der Klassen, die Python nach einem Attribut durchsucht, beginnend mit der Klasse selbst und endend mit object. Bei einer Basisklasse ist das eine gerade Linie. Bei mehreren, class C(A, B), geht Python grob in die Tiefe und von links nach rechts und besucht eine gemeinsame Basisklasse nur einmal, nach all ihren Unterklassen. super() heißt nicht „meine Elternklasse“, sondern „die nächste Klasse in der MRO der Klasse dieses Objekts“. Darum läuft in einer Raute jede Klasse genau einmal, wenn jede Methode super() aufruft.

isinstance und issubclass

isinstance(obj, Animal) ist True, wenn obj ein Animal ist oder eine Instanz irgendeiner Unterklasse von Animal. issubclass(Dog, Animal) fragt dasselbe für zwei Klassen, und eine Klasse gilt als Unterklasse ihrer selbst. Beide nehmen auch ein Tupel, etwa isinstance(x, (int, float)). type(obj) is Animal ist strenger: Es ist nur für die exakte Klasse True und lehnt einen Dog ab. Nehmen Sie lieber isinstance, damit Unterklassen überall funktionieren, wo die Basisklasse es tut. Auch eingebaute Typen bilden eine Hierarchie: bool ist eine Unterklasse von int.

Quellen

Zuletzt geprüft am 29. September 2026