Dynamische und späte Bindung

Mit dem Begriff Bindung bezeichnet man die Auswahl der Implementierung einer Methode zu einem gegebenen Namen. Da Unterklassen Methoden überschreiben können, ist nicht immer ohne weiteres klar, welche Implementierung anzuwenden ist, wie die folgende Diskussion zeigt.

Um die Bindung von Methoden zu verdeutlichen, definieren wir für jede Unterklasse von Shape eine Methode __repr__ zur Darstellung der entsprechenden Figur als Zeichenkette. Da jede Klasse eine __repr__-Methode der Klasse object erbt, überschreiben wir die geerbte Implementierung mit einer eigenen. Wir beginnen mit einer __repr__-Methode für die Shape-Klasse.

    # in class Shape
    def __repr__(self):
        return "Shape location=" + str(self._location)

Damit die im Attribut _location gespeicherte Point-Instanz in eine sinnvolle Zeichenkette umgewandelt wird, überschreiben wir die __str__-Methode in der Point-Klasse.

    # in class Point
    def __str__(self):
        return "(" + str(self._x) + "," + str(self._y) + ")"

Die drei folgenden Anweisungen erzeugen einen Kreis und ein Rechteck. Anschließend können wir uns in der interaktiven Python-Umgebung deren Darstellung anzeigen lassen.

>>> p = Point(100, 100)
>>> c = Circle(p, 50)
>>> r = Rect(p, 150, 100)
>>> c
Shape location=(100,100)
>>> r
Shape location=(100,100)

Wenn die Rect-Klasse Zugriff auf die linke obere Ecke mit top_left sowie auf Breite und Höhe mit Methoden width und height erlaubt, können wir die Methode __repr__ in der Klasse Rect wie folgt überschreiben.

    # in class Rect
    def __repr__(self):
        return (
            "Rect top_left=" + str(self.top_left()) +
            " width=" + str(self.width()) +
            " height=" + str(self.height())
        )

Analog dazu können wir die Methode __repr__ in Circle definieren, indem wir die gespeicherten Attributwerte zusammenfassen.

    # in class Circle
    def __repr__(self):
        return (
            "Circle center=" + str(self.center()) + 
            " radius=" + str(self.radius())
        )

Wenn wir nun die oben gemachten Eingaben wiederholen, werden diese beiden unterschiedlichen Implementierungen der __repr__-Methode aufgerufen, je nachdem, auf welchem Objekt __repr__ aufgerufen wird. Dies geschieht selbst dann, wenn wir die Aufrufe in einer Schleife zusammenfassen.

>>> p = Point(100, 100)
>>> c = Circle(p, 50)
>>> r = Rect(p, 150, 100)
>>> shapes = [c, r]
>>> for i in range(0, len(shapes)):
...     print(repr(shapes[i]))
... 
Circle center=(100,100) radius=50
Rect top_left=(100,100) width=150 height=100

Obwohl hier textuell nur ein einziger repr-Aufruf steht, werden in unterschiedlichen Schleifendurchläufen unterschiedliche Implementierungen der __repr__-Methode verwendet, je nachdem zu welcher Klasse das Element shapes[i] aus dem durchlaufenen Array gehört. Da hier erst zur Laufzeit feststeht, welche Implementierung verwendet wird, spricht man von dynamischer Bindung.

Eine Variante der dynamischen Bindung ist die sogenannte späte Bindung, die wir auch mit Hilfe der __repr__-Methode illustrieren. Dazu fügen wir der Klasse Shape eine Methode __str__ hinzu, die das erste von __repr__ gelieferte Wort zurück liefert.

    # in class Shape
    def __str__(self):
        return repr(self).split()[0]

Der Aufruf von repr verwendet hier die Implementierung derjenigen Unterklasse von Shape, auf der __str__ aufgerufen wurde, selbst wenn diese bei der Definition von __str__ gar nicht bekannt ist.

Da die Rect-Klasse die in Shape definierte Methode __str__ erbt, können wir sie auf dem Rechteck r aufrufen um seine Beschreibung zu generieren.

>>> p = Point(100, 100)
>>> r = Rect(p, 150, 100)
>>> print(r)
Rect

Ein Aufruf von print ruft indirekt __str__ auf.