2.22. Vererbung¶
Vererbung lässt eine Klasse eine andere Klasse erweitern – man beginnt mit all ihren Methoden und Attributen und fügt dann hinzu oder überschreibt, was anders ist. Das Original wird Basis (oder Elternklasse) genannt; die Erweiterung wird Unterklasse genannt.
Square verwendet jede Methode von Shape wieder und überschreibt diejenigen, die es spezialisieren muss.¶
2.22.1. Eine Unterklasse definieren¶
Die Basisklasse steht in den Klammern in der class-Zeile:
class Shape:
def __init__(self, name):
self.name = name
def describe(self):
return self.name + " with area " + str(self.area())
def area(self):
return 0
class Square(Shape):
def __init__(self, side):
super().__init__("square")
self.side = side
def area(self):
return self.side * self.side
s = Square(4)
print(s.describe())
Ausgabe:
square with area 16
Square erbt describe unverändert von Shape und überschreibt area, um einen echten Wert zurückzugeben. Das describe der Basis funktioniert weiterhin, weil es self.area() aufruft – was zur Laufzeit auf die Überschreibung aufgelöst wird.
2.22.2. super()¶
super() gibt einen Proxy zurück, der es einer Methode ermöglicht, die Basisklassen-Version einer Methode aufzurufen. Die häufigste Verwendung ist der Aufruf des Basis-__init__ aus dem __init__ einer Unterklasse, damit die Basis ihre eigenen Attribute initialisieren kann:
class Square(Shape):
def __init__(self, side):
super().__init__("square") # run Shape.__init__
self.side = side
Das Vergessen von super().__init__ ist eine häufige Fehlerquelle: Attribute, die die Basis gesetzt hätte, werden nie gesetzt, und die geerbten Methoden, die sich darauf verlassen, stürzen später ab.
2.22.2.1. Die implizite Basisklasse¶
Jede Klasse erbt implizit von object, der Wurzel von Pythons Typhierarchie. Methoden wie __repr__, __eq__ und die Mechanik hinter dem Attributzugriff stammen alle von dort, selbst wenn eine Klasse keine Basis deklariert. class Foo(object): explizit zu schreiben und class Foo: zu schreiben ist im modernen Python äquivalent; Letzteres ist die übliche Form.
2.22.3. Wann Vererbung hilft – und wann nicht¶
Verwenden Sie Vererbung, wenn eine Klasse wirklich eine spezifischere Art einer anderen ist und den Großteil des Verhaltens teilt. Der klassische Test ist die „ist-ein“-Prüfung: ein Square ist ein Shape.
Wenn zwei Klassen nur zufällig ein paar Helfer teilen, ist Vererbung überzogen. Greifen Sie stattdessen zur Komposition: Lassen Sie eine Klasse eine Instanz der anderen als Attribut halten und nutzen Sie sie über dieses Attribut. Die Form ist „hat-ein“, nicht „ist-ein“:
class Logger:
def log(self, msg):
print("[log]", msg)
# inheritance: Worker IS-A Logger
class WorkerInherits(Logger):
def run(self):
self.log("starting")
# composition: Worker HAS-A Logger
class WorkerComposes:
def __init__(self):
self.logger = Logger()
def run(self):
self.logger.log("starting")
Beides funktioniert. Die komponierende Variante ist in der Regel besser, weil sie:
Schnittstellen klein hält.
WorkerComposeslegt nurrunundloggeroffen.WorkerInheritslegt zusätzlichlogoffen – Aufrufer können direktworker.log(...)schreiben, ob das beabsichtigt war oder nicht.Lebensdauern entkoppelt. Der Logger kann ausgetauscht, zwischen Workern geteilt oder pro Worker unterschiedlich konstruiert werden, ohne die
Worker-Klasse anzufassen. Eine Unterklasse ist fest mit ihrer Basis verschraubt.Methodennamenskollisionen vermeidet. Zwei Basen, die beide
rundefinieren, kollidieren, wenn eine Klasse versucht, von beiden zu erben; zwei Attribute kollidieren nie.
Eine praktische Faustregel:
Verwenden Sie Vererbung, wenn die Unterklasse wirklich eine Art der Basis ist und jede Methode der Basis auch auf sie sinnvoll anwendbar ist –
Squareist einShape;MemoryErrorist eineException.Verwenden Sie Komposition für alles andere – wenn Sie nur das Verhalten einer anderen Klasse wollen, nicht ihre Identität. Der meiste reale Code verwendet Komposition weitaus häufiger als Vererbung.