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.