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.

A base class Shape pointing down to a subclass Square; Square overrides one method (area) and inherits the rest.

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. WorkerComposes legt nur run und logger offen. WorkerInherits legt zusätzlich log offen – Aufrufer können direkt worker.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 run definieren, 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 – Square ist ein Shape; MemoryError ist eine Exception.

  • 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.