Polymorphie
Kurz gesagt: Polymorphie bedeutet, dass derselbe Aufruf je nach Objekt ein anderes Ergebnis liefert, weil jede Unterklasse die Methode der Basisklasse überschreibt (override).
Problem
Das Gehalt mit einem if für jede Art
Die Firma hat normale Mitarbeiter, Manager mit Bonus und Praktikanten in Teilzeit. Jemand hat eine Klasse Employee mit einem Feld „Rolle“ gemacht, und das Gehalt wird so berechnet:
def monthly_pay(e): if e.role == "Manager": return e.salary + e.bonus elif e.role == "Praktikant": return e.salary // 2 return e.salaryEs funktioniert. Aber dann kommt eine neue Art von Mitarbeiter dazu: eine Honorarkraft. Du brauchst ein neues elif hier, und in der Funktion für die Steuern, und in der Funktion für den Lohnzettel, und überall, wo jemand nach der Rolle gefragt hat. Und jeder Mitarbeiter trägt ein Feld für den Bonus mit sich, obwohl nur Manager einen Bonus bekommen.
Der Code für den Manager ist über das ganze Programm verstreut, statt im Manager zu stehen.
Die Idee
Jede Klasse entscheidet selbst
Mit der Vererbung aus der letzten Lektion sind Manager und Intern eigene Klassen. Lass jede selbst sagen, wie hoch ihr Monatsgehalt ist. Die Basisklasse hat eine allgemeine Version, und die Unterklasse schreibt ihre eigene Version derselben Methode:
class Employee: # ... __init__, name und salary
def monthly_pay(self): return self._salary
class Manager(Employee): # ... _bonus und __init__
def monthly_pay(self): return self.salary + self._bonusivan = Employee("Ivan", 2000)maria = Manager("Maria", 3000, 500)print(ivan.monthly_pay())print(maria.monthly_pay())20003500Das nennt man Überschreiben einer Methode (override). Die Unterklasse schreibt eine Methode mit demselben Namen. Für Objekte der Unterklasse ersetzt ihre Version die der Basisklasse.
Mehr brauchst du nicht: Es gibt kein Schlüsselwort und keine Prüfung. Jede Methode lässt sich überschreiben. Python schaut nur auf den Namen, nicht auf die Parameter. Das ist bequem, aber es bedeutet, dass nichts einen Fehler im Namen oder in den Parametern aufhält. Gleich siehst du, was dann passiert.
Genauer, für Neugierige
- Seit Python 3.12 gibt es
@overrideaus dem Modultyping. Du schreibst es über die überschreibende Methode. Python selbst prüft es nicht: Das Programm läuft mit und ohne es gleich. Ein Werkzeug wie mypy prüft es, indem es den Code liest, bevor du ihn startest. - Es gibt auch keine Möglichkeit, Unterklassen das Ändern einer Methode zu verbieten.
@finalaustypingist nur ein Hinweis für solche Werkzeuge.
Probier es aus
Was hat sich geändert?
Die Praktikantin überschreibt nur die Methode für das Monatsgehalt:
Was gibt das Programm aus?
class Intern(Employee): def monthly_pay(self): return self.salary // 2
ana = Intern("Ana", 1600)print(ana.monthly_pay())print(ana.salary)monthly_pay() der Praktikantin gibt das halbe Gehalt zurück. Das Gehalt selbst ändert sich nicht, und salary ist unverändert von Employee geerbt.
Die Idee
Eine Liste für alle
Ein Manager ist ein Mitarbeiter. Also können Mitarbeiter, Manager und Praktikanten in einer Liste stehen und in einer Schleife behandelt werden:
staff = [ Employee("Ivan", 2000), Manager("Maria", 3000, 500), Intern("Ana", 1600),]for e in staff: print(f"{e.name}: {e.monthly_pay()} €")Ivan: 2000 €Maria: 3500 €Ana: 800 €Die Schleife ruft dieselbe Methode auf, e.monthly_pay(), und das Ergebnis ist verschieden. Es läuft die Version des Objekts, nicht die des Typs der Variable. Das nennt man Polymorphie: griechisch für „Vielgestaltigkeit“. Ein Aufruf, und jedes Objekt antwortet auf seine Weise.
Jetzt ist eine neue Art von Mitarbeiter eine neue Klasse mit eigenem monthly_pay(). Die Schleife und der Rest des Programms bleiben, wie sie sind. Es gibt kein if auf die Rolle und kein Bonusfeld bei Mitarbeitern, die keinen Bonus bekommen.
Die Variable e hat keinen Typ, also kannst du über sie schreiben, was du willst, auch e.bonus. Ist das Objekt ein Manager, funktioniert es. Ist es ein normaler Mitarbeiter, bricht das Programm in dieser Zeile ab: AttributeError: 'Employee' object has no attribute 'bonus'. Python prüft nicht im Voraus. Wie du Objekte fragst, was sie können, steht in der Lektion „Typprüfungen“.
Sieh es dir an
Welche Methode läuft
staff = [ Employee("Ivan", 2000), Manager("Maria", 3000, 500), Intern("Ana", 1600),]for e in staff: print(f"{e.name}: {e.monthly_pay()} €")Schritt für Schritt, in Worten
- Die Liste enthält Objekte aus drei Klassen. In jedem Platz steht ein Pfeil.
- e zeigt auf Ivan, einen einfachen Employee: Es läuft monthly_pay von Employee.
- e zeigt auf Maria, einen Manager: Es läuft ihre eigene Methode, Gehalt plus Bonus.
- e zeigt auf Ana, eine Praktikantin (Intern): Sie bekommt das halbe Gehalt.
- Dieselbe Zeile, drei verschiedene Methoden. Es entscheidet das Objekt, auf das e zeigt.
Die Liste enthält drei Pfeile. Jeder zeigt auf ein Objekt einer anderen Klasse. In jeder Runde zeigt e auf das nächste Objekt, und der Aufruf geht an die Methode dieses Objekts: zuerst in Employee, dann in Manager, dann in Intern.
Die Wahl fällt, während das Programm läuft. In jeder Runde sucht Python monthly_pay zuerst in der Klasse des Objekts, dann in ihrer Basisklasse. Niemand prüft im Voraus, dass jedes Objekt so eine Methode hat. Hat eines keine, bricht das Programm in seiner Runde ab.
Probier es aus
Finde den Fehler
Das Programm ist dasselbe, aber die Praktikantin bekommt das volle Gehalt:
Ivan: 2000 €Maria: 3500 €Ana: 1600 €Hier ist die Klasse Intern. In welcher Zeile ist der Fehler?
Wähle die Zeile mit dem Fehler.
monthlypay, ohne Unterstrich. Das ist eine neue Methode, kein Überschreiben von monthly_pay. Deshalb wird die Version der Basisklasse aufgerufen. Python sagt nichts: Für Python ist es einfach eine Methode mehr. @override aus typing über der Methode würde das Programm nicht aufhalten, aber ein Werkzeug wie mypy würde es finden.Die Idee
Die Basisklasse ergänzen, nicht neu schreiben
Manchmal will die Unterklasse die Methode nicht ganz ersetzen, sondern nur etwas hinzufügen. Ein Manager wird wie ein Mitarbeiter beschrieben, plus der Bonus. Die Version der Basisklasse rufst du mit super().__str__() auf:
def __str__(self): return f"{super().__str__()}, Bonus {self._bonus} €"print(Employee("Ivan", 2000))print(Manager("Maria", 3000, 500))Ivan (2000 €)Maria (3000 €), Bonus 500 €Ändert Employee, wie es sich beschreibt, zieht der Manager von selbst mit.
Das ist auch das __str__ aus dem Modul „Klassen und Objekte“. Dort hast du schon eine Methode überschrieben: die Version in object.
Genauer, für Neugierige
super().method()kannst du aus jeder Methode der Unterklasse aufrufen, nicht nur aus der überschreibenden.
- Python vergleicht die Parameter nicht. Schreibst du
def monthly_pay(self, extra):inManager, ist es trotzdem ein Überschreiben, und die Schleife, diee.monthly_pay()aufruft, bricht beim Manager ab:TypeError: Manager.monthly_pay() missing 1 required positional argument: 'extra'. Deshalb nimmt die überschreibende Methode dieselben Parameter wie die der Basisklasse.
Probier es aus
Was fehlt?
Die Praktikantin wird wie ein Mitarbeiter beschrieben, plus das Wort „Praktikant“. Was steht in der Lücke?
class Intern(Employee):
def __str__(self):
return f"{.__str__()}, Praktikant"super().__str__() liefert „Ana (1600 €)“, und die Unterklasse fügt „, Praktikant“ hinzu. Mit self.__str__() stattdessen würde die Methode sich selbst aufrufen, endlos, bis Python das Programm mit RecursionError anhält.
Übung
Jeder auf seine Weise
Löse die Aufgaben in deinem Editor. Jede Aufgabe hat eine Liste mit Objekten mehrerer Klassen und eine Schleife.
1. Tierheim. Eine Klasse Animal mit einem Namen und einer Methode make_sound(), die „irgendein Laut“ ausgibt. Dog und Cat überschreiben sie. Leg von allen dreien je eins in eine Liste:
Gast: irgendein LautRex: Wau!Mieze: Miau, miau!2. Die Studentin stellt sich vor. Student aus der letzten Lektion hat schon einen Notendurchschnitt. Überschreib introduce(), sodass auch der Notendurchschnitt genannt wird. Benutze super().introduce(); kopier die Zeile nicht aus Person.
Ich bin Georg, 45 Jahre alt.Ich bin Elena, 19 Jahre alt.Notendurchschnitt: 5.53. Party. Eine Klasse Performance mit einem Titel und einer Methode start(). DanceMove, BreakDanceMove (erbt von DanceMove, fügt eine Schwierigkeit hinzu) und Song überschreiben sie. Eine Klasse Party enthält eine Liste von Auftritten und hat eine Methode start_party(), die alles der Reihe nach startet und am Ende sagt, dass die Party begonnen hat.
Wir tanzen moonwalkWir tanzen windmill Schwierigkeit 8 von 10Wir singen 'Bohemian Rhapsody'Es beginnt: TombolaDie Party hat begonnen!Weitere Aufgaben
- Honorarkraft. Füg den Mitarbeitern einen
Contractorhinzu, mit Stunden und einem Stundensatz. Sein Monatsgehalt ist Stunden × Satz. Berechne die Lohnsumme für Ivan, Maria, Ana und Peter (60 Stunden zu 25 €):
Lohnsumme: 7800 €- Lohnzettel. Gib
Employeeeine Methodepayslip(), die den Text „Name: Gehalt €“ mit dem Monatsgehalt zurückgibt. Schreib sie nur inEmployee. Warum funktioniert sie auch für den Manager und die Praktikantin richtig?
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Konstruktoren und self · Instanzmethoden · Kapselung · Vererbung
- Geht weiter in: Abstrakte Klassen · Interfaces als abstrakte Klassen · Typprüfungen · OOP-Prinzipien
- Aufgaben zum Thema: Vererbung und Polymorphie