Interfaces als abstrakte Klassen
Kurz gesagt: Python hat kein interface: Der Vertrag ist eine abstrakte Klasse mit lauter abstrakten Methoden, die jede Klasse schreiben muss, die ihn annimmt.
Problem
Gehälter und Rechnungen
Am Monatsende bezahlt die Firma zwei Arten von Dingen: die Gehälter der Mitarbeiter und die Rechnungen der Lieferanten. Du willst eine Liste und eine Schleife: wer bezahlt wird, wie viel, und am Ende die Summe.
Eine Rechnung ist kein Mitarbeiter. Sie von Employee erben zu lassen ist falsch. Der Satz „eine Rechnung ist ein Mitarbeiter“ stimmt nicht, und die Rechnung bekäme auch einen Namen, ein Gehalt und eine Erhöhung.
Eine gemeinsame Basisklasse Payment mit Feldern und Code ist auch nicht die richtige Antwort: Mitarbeiter und Rechnungen haben keine gemeinsamen Daten und keinen gemeinsamen Code. Gemeinsam ist ihnen nur, dass sie bezahlt werden können.
Die Idee
Ein Interface: ein Vertrag „kann etwas“
Ein Interface ist eine Liste von Methoden ohne Rumpf: was eine Klasse können muss, aber nicht wie. Python hat kein eigenes Wort dafür. Du schreibst den Vertrag als abstrakte Klasse, in der alle Methoden abstrakt sind:
from abc import ABC, abstractmethod
class Payable(ABC): @abstractmethod def amount_due(self): pass
@abstractmethod def payee(self): passclass Invoice(Payable): def __init__(self, supplier, amount): self._supplier = supplier self._amount = amount
def amount_due(self): return self._amount
def payee(self): return self._supplier
power = Invoice("Stromanbieter", 340)print(f"{power.payee()}: {power.amount_due()} €")Stromanbieter: 340 €Payable steht in den Klammern wie eine Basisklasse. Python unterscheidet nicht zwischen einer Basisklasse mit Code und einer Vertrags-Basisklasse: Der Unterschied ist, dass Payable keine Felder und keine Rümpfe von Methoden hat. Die Prüfung funktioniert wie bei einer abstrakten Klasse: Von Payable erzeugt man keine Objekte. Payable() bricht ab mit
TypeError: Can't instantiate abstract class Payable without an implementation for abstract methods 'amount_due', 'payee'Sieh es dir an
Eine Schleife für alles, was bezahlt wird
Auch der Mitarbeiter nimmt den Vertrag an. Sein Betrag ist das Monatsgehalt:
class Employee(Payable): # ... _name, _salary, __init__, monthly_pay()
def amount_due(self): return self.monthly_pay()
def payee(self): return self._nameJetzt sind sowohl die Mitarbeiter als auch die Rechnungen Payable: Beide Klassen haben es als Basisklasse. Die Liste hat keinen Typ, aber alles darin hält den Vertrag ein, also kann die Schleife bei jedem amount_due() und payee() aufrufen.
payments = [ Employee("Ivan", 2000), Manager("Maria", 3000, 500), Invoice("Stromanbieter", 340), Invoice("Vermieter", 1800),]total = 0for p in payments: print(f"{p.payee()}: {p.amount_due()} €") total += p.amount_due()print(f"Gesamt: {total} €")Ivan: 2000 €Maria: 3500 €Stromanbieter: 340 €Vermieter: 1800 €Gesamt: 7640 €Der Manager schreibt nichts Neues. Von Employee erbt er sowohl den Vertrag als auch amount_due(). Und amount_due() ruft monthly_pay() auf, das der Manager überschrieben hat. Deshalb bekommt Maria 3500 €.
payments = [ Employee("Ivan", 2000), Manager("Maria", 3000, 500), Invoice("Stromanbieter", 340),]total = 0for p in payments: total += p.amount_due()print(f"Gesamt: {total} €")Schritt für Schritt, in Worten
- Die Liste enthält drei Objekte aus drei Klassen. Alle sind Payable, und die Pfeile erreichen nur ihren Vertrag.
- total beginnt bei 0.
- p zeigt auf den Mitarbeiter: amount_due() gibt das Gehalt zurück, 2000.
- Der Manager ist ein Mitarbeiter, also kann auch er bezahlt werden. Gehalt plus Bonus: 3500.
- Die Rechnung ist kein Mitarbeiter, aber sie hält denselben Vertrag ein: 340.
- Die Schleife kennt die Klassen nicht. Sie weiß nur, dass jedes Objekt bezahlt werden kann.
Probier es aus
Wie hoch ist der Betrag?
Was gibt das Programm aus? Die Klassen sind aus der Karte oben.
p = Manager("Maria", 3000, 500)print(p.amount_due())Manager ist ein Employee, und Employee implementiert den Vertrag. Also implementiert ihn auch der Manager, ohne es zu schreiben. Der Aufruf geht an die überschriebene Methode des Managers: 3000 + 500.
Die Idee
Eine Basisklasse, viele Verträge
Die Buchhaltung will die Rechnungen auch im CSV-Format: je eine Zeile „Lieferant;Betrag“. Das ist ein zweiter Vertrag. In Python kann eine Klasse beliebig viele Basisklassen haben, durch Kommas getrennt, also sind Verträge einfach weitere Basisklassen:
class Exportable(ABC): @abstractmethod def to_csv(self): pass
class Invoice(Payable, Exportable): # ... wie vorher
def to_csv(self): return f"{self._supplier};{self._amount}"for_accounting = [Invoice("Stromanbieter", 340), Invoice("Vermieter", 1800)]for e in for_accounting: print(e.to_csv())Stromanbieter;340Vermieter;1800Hat die Klasse sowohl eine Basisklasse als auch Verträge, kommt die Basisklasse zuerst:
class Manager(Employee, Exportable):Der Manager schreibt dann auch to_csv() und ist gleichzeitig ein Employee, ein Payable und ein Exportable. Die Reihenfolge in den Klammern ist wichtig: Wenn zwei Basisklassen eine Methode mit demselben Namen haben, gewinnt die erste.
Basisklassen kann es viele geben, aber am besten hat nur eine Code und Daten. Die übrigen sind Verträge: nur Versprechen, keine Felder.
Probier es aus
Finde den Fehler
Invoice("Stromanbieter", 340) bricht mit TypeError ab. In welcher Zeile ist der Fehler?Wähle die Zeile mit dem Fehler.
amount_du statt amount_due. Für Python ist das einfach eine weitere Methode, und die versprochene ist immer noch nicht geschrieben. Deshalb kannst du keine Rechnung erzeugen: TypeError: Can't instantiate abstract class Invoice without an implementation for abstract method 'amount_due'.Probier es aus
Was fehlt?
class Invoice():
# ...Payable: Der Vertrag steht in den Klammern wie eine Basisklasse. Ohne ihn hat die Rechnung zwar die Methoden, ist aber kein Payable, und Python prüft nicht, dass alle geschrieben sind.Die Idee
Abstrakte Klasse oder Interface?
In Python sind beide abstrakte Klassen: Ein Vertrag ist eine abstrakte Klasse ohne Felder und ohne Code. Der Unterschied liegt nicht in der Sprache, sondern darin, was sie sagen:
| Abstrakte Klasse mit Code | Vertrag | |
|---|---|---|
| Sagt | „X ist ein Y“ | „X kann“ |
| Wie viele pro Klasse | beliebig viele, aber am besten eine | beliebig viele |
Felder und __init__ |
ja | nein |
| Gemeinsamer Code | ja | kaum |
- Eine abstrakte Klasse, wenn die Klassen darunter eine Art von Ding sind und Code und Daten teilen: Figuren haben einen Namen und eine Beschreibung, Mitarbeiter einen Namen und ein Gehalt.
- Ein Vertrag, wenn die Klassen verschieden sind, aber eine gemeinsame Fähigkeit brauchen: Ein Mitarbeiter und eine Rechnung „können bezahlt werden“, ein Vogel und ein Flugzeug „können fliegen“.
Employee ist die Basisklasse von Manager und Praktikant und implementiert gleichzeitig Payable.Genauer, für Neugierige
- Auch ein Vertrag kann eine Standardmethode haben: eine normale Methode mit Rumpf neben den abstrakten. Die Klassen bekommen sie fertig und können sie auch überschreiben. So kannst du dem Vertrag eine neue Methode hinzufügen, ohne die Klassen kaputt zu machen, die ihn schon erfüllen:
class Flyable(ABC): @abstractmethod def fly(self): pass
def glide(self): print("Gleitet durch die Luft")
class Bird(Flyable): def fly(self): print("Der Vogel schlägt mit den Flügeln")
class Plane(Flyable): def fly(self): print("Das Flugzeug hebt ab")
flyers = [Bird(), Plane()]for f in flyers: f.fly() f.glide()Der Vogel schlägt mit den FlügelnGleitet durch die LuftDas Flugzeug hebt abGleitet durch die Luft- Es gibt einen zweiten Weg, einen Vertrag zu schreiben:
typing.Protocol. Die Klasse schreibt den Vertrag dann nicht in ihre Klammern. Solange sie die Methoden hat, passt sie, genau wie in JavaScript. Python selbst prüft das Protokoll nicht: Das Ticket ohneamount_due()geht alsPayabledurch. Ein eigener Typprüfer prüft es, bevor das Programm läuft. Mit@runtime_checkableundisinstance(die Lektion „Typprüfungen“) kannst du fragen, ob das Objekt die Methoden hat:
from typing import Protocol, runtime_checkable
@runtime_checkableclass Payable(Protocol): def amount_due(self): ...
def payee(self): ...
class Invoice: def __init__(self, supplier, amount): self._supplier = supplier self._amount = amount
def amount_due(self): return self._amount
def payee(self): return self._supplier
class Ticket: def payee(self): return "Kino"
p: Payable = Ticket()print(isinstance(Invoice("Vermieter", 1800), Payable))print(isinstance(p, Payable))TrueFalseÜbung
Verträge
Löse die Aufgaben in deinem Editor. In Python ist jedes „Interface“ eine abstrakte Klasse (ABC) mit lauter abstrakten Methoden.
1. Hupe. Ein Interface Honkable mit einer Methode honk(). SimpleCar aus dem Modul „Klassen und Objekte“ implementiert es.
Tut-tut!2. Angaben. Ein Interface Printable mit einer Methode print_details(). Implementiere es in Person, und lass Student (eine Unterklasse von Person) es überschreiben und den Notendurchschnitt hinzufügen. Leg beide in eine Liste.
Name: Georg, Alter: 45Name: Elena, Alter: 19Notendurchschnitt: 5.53. Tanz. Ein Interface Danceable mit einer Methode dance(). DanceMove (ein Name und eine Dauer) implementiert es.
Ich tanze Salsa für 30 Sekunden4. DJ. Eine Klasse Song mit einem Titel. Ein Interface Playable mit einer Methode play(). DJ implementiert es und hat eine Methode play_song(song), die sich das Lied merkt und es abspielt. Gibt es noch kein Lied, sagt play() das.
Kein Lied ausgewähltIch spiele 'Bohemian Rhapsody'5. Der Teich. Interfaces Swimmable mit swim() und Flyable mit fly(). Fish schwimmt. Duck schwimmt und fliegt.
Der Fisch schwimmt.Die Ente schwimmt.Die Ente fliegt.Weitere Aufgaben
- Rabatt. Ein Interface Discountable mit einer Methode, die den Preis nach dem Rabatt zurückgibt. Implementiere es in
Product(10% Rabatt) und inSubscription(25% Rabatt für Studierende). Berechne die Summe eines Warenkorbs mit einem Produkt für 50 € und einem Studentenabo für 12 €: 45 € + 9 € = 54 €. - CSV für alle. Lass auch
EmployeeExportable implementieren, mit einer Zeile „Name;Monatsgehalt“. Leg Mitarbeiter und Rechnungen in eine Liste.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Kapselung · Vererbung · Polymorphie · Abstrakte Klassen
- Geht weiter in: Typprüfungen · OOP-Prinzipien · Werfen und eigene Exceptions · Dateien
- Aufgaben zum Thema: Vererbung und Polymorphie