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.
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
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))
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())
Ü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)
Ü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__])
Üben · Aufgabe 5 von 7
Dog ist eine Unterklasse von Animal, und rex = Dog(). Ordnen Sie jedem Ausdruck seinen Wert zu.
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())
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
Hinweis 1
Beginnen Sie Book.__init__ mit super().__init__(name, price); setzen Sie danach self.author.
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.
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.
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
Hinweis 1
Ersetzen Sie in Save.run return log durch return super().run(log), wie in Validate.
Hinweis 2
cls.__mro__ ist ein Tupel von Klassen; jede hat einen __name__. Lassen Sie die weg, die object ist.
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.
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.
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.
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.