Laden Sie Ihre Klassen mit Python super auf ()

Laden Sie Ihre Klassen mit Python super auf ()

Python ist zwar keine rein objektorientierte Sprache, aber flexibel und leistungsfähig genug, um Ihre Anwendungen mithilfe des objektorientierten Paradigmas erstellen zu können. Python erreicht dies unter anderem durch die Unterstützung von Vererbung , was mit + super () + geschieht.

*In diesem Tutorial erfahren Sie Folgendes:*
  • Das Konzept der Vererbung in Python

  • Mehrfachvererbung in Python

  • Wie die Funktion + super () + funktioniert

  • Wie die Funktion "+ super () +" bei der Einzelvererbung funktioniert

  • Wie die Funktion + super () + bei Mehrfachvererbung funktioniert

    *Kostenloser Bonus:* Link: [5 Gedanken zur Python-Meisterschaft], ein kostenloser Kurs für Python-Entwickler, der Ihnen die Roadmap und die Denkweise zeigt, die Sie benötigen, um Ihre Python-Fähigkeiten auf die nächste Stufe zu bringen.

Ein Überblick über Pythons + super () + Funktion

Wenn Sie Erfahrung mit objektorientierten Sprachen haben, sind Sie möglicherweise bereits mit der Funktionalität von + super () + vertraut.

Wenn nicht, fürchte dich nicht! Während die https://docs.python.org/3/library/functions.html#super[official documentation ziemlich technisch ist, bietet Ihnen + super () + `auf hoher Ebene Zugriff auf Methoden in einer Superklasse aus dem Unterklasse, die davon erbt.

+ super () + allein gibt ein temporäres Objekt der Oberklasse zurück, mit dem Sie die Methoden dieser Oberklasse aufrufen können.

Warum sollten Sie etwas davon tun wollen? Während die Möglichkeiten durch Ihre Vorstellungskraft begrenzt sind, besteht ein häufiger Anwendungsfall darin, Klassen zu erstellen, die die Funktionalität zuvor erstellter Klassen erweitern.

Wenn Sie die zuvor erstellten Methoden mit + super () + aufrufen, müssen Sie diese Methoden in Ihrer Unterklasse nicht neu schreiben, und Sie können Superklassen mit minimalen Codeänderungen austauschen.

+ super () + in Einzelvererbung

Wenn Sie mit objektorientierten Programmierkonzepten nicht vertraut sind, ist Vererbung möglicherweise ein unbekannter Begriff. Vererbung ist ein Konzept in der objektorientierten Programmierung, bei dem eine Klasse Attribute und Verhaltensweisen von einer anderen Klasse ableitet (oder erbt ), ohne sie erneut implementieren zu müssen.

Zumindest für mich ist es einfacher, diese Konzepte zu verstehen, wenn ich Code betrachte. Schreiben wir also Klassen, die einige Formen beschreiben:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length *self.width

    def perimeter(self):
        return 2* self.length + 2 *self.width

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length* self.length

    def perimeter(self):
        return 4 *self.length

Hier gibt es zwei ähnliche Klassen: + Rechteck + und + Quadrat +.

Sie können sie wie folgt verwenden:

>>>

>>> square = Square(4)
>>> square.area()
16
>>> rectangle = Rectangle(2,4)
>>> rectangle.area()
8

In diesem Beispiel haben Sie zwei Formen, die miteinander in Beziehung stehen: Ein Quadrat ist eine spezielle Art von Rechteck. Der Code spiegelt diese Beziehung jedoch nicht wider und enthält daher Code, der im Wesentlichen wiederholt wird.

Durch die Verwendung der Vererbung können Sie die Menge an Code reduzieren, die Sie schreiben, und gleichzeitig die reale Beziehung zwischen Rechtecken und Quadraten widerspiegeln:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length* self.width

    def perimeter(self):
        return 2 *self.length + 2* self.width

# Here we declare that the Square class inherits from the Rectangle class
class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

Hier haben Sie + super () + verwendet, um das + init () + der + Rectangle + -Klasse aufzurufen, sodass Sie es in der + Square + -Klasse verwenden können, ohne den Code zu wiederholen. Im Folgenden bleibt die Kernfunktionalität nach Änderungen erhalten:

>>>

>>> square = Square(4)
>>> square.area()
16

In diesem Beispiel ist "+ Rechteck " die Oberklasse und " Quadrat +" die Unterklasse.

Da die Methoden + Square + und + Rectangle + + . init () + so ähnlich sind, können Sie einfach die Methode + . init () + der Oberklasse aufrufen (+ Rectangle . init () +) von dem von + Square + mit + super () +. Dadurch werden die Attribute "+ .length " und " .width " festgelegt, obwohl Sie dem Konstruktor " Square " nur einen einzigen Parameter " length +" angeben mussten.

Wenn Sie dies ausführen, wird beim Aufruf von "+ .area () " die Methode " .area () " in der Oberklasse verwendet und "" gedruckt, obwohl Ihre Klasse "+ Square " dies nicht explizit implementiert 16 + `. Die Klasse " Square " *hat* " .area () " von der Klasse " Rectangle +" geerbt.

*Hinweis:* Weitere Informationen zu Vererbung und objektorientierten Konzepten in Python finden Sie unter https://realpython.com/python3-object-oriented-programming/[Object-Oriented Programming (OOP) in Python 3] .

Was kann + super () + für Sie tun?

Was kann + super () + bei Einzelvererbung für Sie tun?

Wie in anderen objektorientierten Sprachen können Sie Methoden der Oberklasse in Ihrer Unterklasse aufrufen. Der primäre Anwendungsfall besteht darin, die Funktionalität der geerbten Methode zu erweitern.

Im folgenden Beispiel erstellen Sie eine Klasse "+ Cube ", die von " Square " erbt und die Funktionalität von " .area () " (geerbt von der Klasse " Rectangle " über " Square ") auf erweitert Berechnen Sie die Oberfläche und das Volumen einer ` Cube +` Instanz:

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

class Cube(Square):
    def surface_area(self):
        face_area = super().area()
        return face_area *6

    def volume(self):
        face_area = super().area()
        return face_area* self.length

Nachdem Sie die Klassen erstellt haben, sehen wir uns die Oberfläche und das Volumen eines Würfels mit einer Seitenlänge von "+ 3 +" an:

>>>

>>> cube = Cube(3)
>>> cube.surface_area()
54
>>> cube.volume()
27
*Achtung* : Beachten Sie, dass in unserem obigen Beispiel `+ super () +` allein die Methodenaufrufe nicht für Sie ausführt: Sie müssen die Methode für das Proxy-Objekt selbst aufrufen.

Hier haben Sie zwei Methoden für die Klasse "+ Cube " implementiert: " .surface_area () " und " .volume () ". Beide Berechnungen basieren auf der Berechnung der Fläche einer einzelnen Fläche. Anstatt die Flächenberechnung erneut zu implementieren, verwenden Sie ` super () +`, um die Flächenberechnung zu erweitern.

Beachten Sie auch, dass die Klassendefinition + Cube + kein + . init () + enthält. Da "+ Cube " von " Square " und " . init () " erbt, ist für " Cube " nichts anderes als für " Square ". Sie können die Definition überspringen ` . init () ` der Oberklasse (` Square +`) wird automatisch aufgerufen.

+ super () + gibt ein Delegatenobjekt an eine übergeordnete Klasse zurück, sodass Sie die gewünschte Methode direkt darauf aufrufen: + super (). area () +.

Dies erspart uns nicht nur, die Flächenberechnungen neu schreiben zu müssen, sondern ermöglicht uns auch, die interne Logik + .area () + an einem einzigen Ort zu ändern. Dies ist besonders praktisch, wenn Sie mehrere Unterklassen haben, die von einer Oberklasse erben.

Ein + super () + Deep Dive

Bevor wir uns der Mehrfachvererbung zuwenden, machen wir einen kurzen Abstecher in die Mechanik von + super () +.

Während die obigen Beispiele (und unten) "+ super () " ohne Parameter aufrufen, kann " super () +" auch zwei Parameter annehmen: Der erste ist die Unterklasse und der zweite Parameter ist ein Objekt, das eine Instanz ist dieser Unterklasse.

Schauen wir uns zunächst zwei Beispiele an, die zeigen, wie die erste Variable mithilfe der bereits gezeigten Klassen bearbeitet werden kann:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length *self.width

    def perimeter(self):
        return 2* self.length + 2 *self.width

class Square(Rectangle):
    def __init__(self, length):
        super(Square, self).__init__(length, length)

In Python 3 entspricht der Aufruf + super (Square, self) + dem parameterlosen Aufruf + super () +. Der erste Parameter bezieht sich auf die Unterklasse "+ Square ", während der zweite Parameter auf ein " Square " - Objekt verweist, das in diesem Fall " self " ist. Sie können ` super () +` auch mit anderen Klassen aufrufen:

class Cube(Square):
    def surface_area(self):
        face_area = super(Square, self).area()
        return face_area* 6

    def volume(self):
        face_area = super(Square, self).area()
        return face_area * self.length

In diesem Beispiel setzen Sie "+ Square " als Unterklassenargument auf " super () " anstelle von " Cube ". Dies führt dazu, dass ` super () ` nach einer passenden Methode (in diesem Fall ` .area () `) auf einer Ebene über ` Square ` in der Instanzhierarchie sucht, in diesem Fall ` Rectangle +` .

In diesem speziellen Beispiel ändert sich das Verhalten nicht. Stellen Sie sich jedoch vor, dass "+ Square " auch eine " .area () " - Funktion implementiert hat, mit der Sie sicherstellen möchten, dass " Cube " nicht verwendet wird. Wenn Sie auf diese Weise ` super () +` aufrufen, können Sie dies tun.

*Achtung:* Während wir viel an den Parametern von "+ super () +" herumspielen, um herauszufinden, wie es unter der Haube funktioniert, würde ich davor warnen, dies regelmäßig zu tun.

Der parameterlose Aufruf von "+ super () +" wird für die meisten Anwendungsfälle empfohlen und ist ausreichend. Wenn die Suchhierarchie regelmäßig geändert werden muss, kann dies auf ein größeres Designproblem hinweisen.

Was ist mit dem zweiten Parameter? Denken Sie daran, dass dies ein Objekt ist, das eine Instanz der Klasse ist, die als erster Parameter verwendet wird. Zum Beispiel muss + isinstance (Cube, Square) + + True + zurückgeben.

Durch Einschließen eines instanziierten Objekts gibt + super () + eine gebundene Methode zurück: eine Methode, die an das Objekt gebunden ist und der Methode den Kontext des Objekts gibt, z. B. alle Instanzattribute. Wenn dieser Parameter nicht enthalten ist, ist die zurückgegebene Methode nur eine Funktion, die nicht mit dem Kontext eines Objekts verknüpft ist.

Weitere Informationen zu gebundenen Methoden, ungebundenen Methoden und Funktionen finden Sie in der Python-Dokumentation https://docs.python.org/3.7/howto/descriptor.html [auf dem Deskriptorsystem].

*Hinweis:* Technisch gesehen gibt `+ super () +` keine Methode zurück. Es gibt ein *Proxy-Objekt* zurück. Dies ist ein Objekt, das Aufrufe an die richtigen Klassenmethoden delegiert, ohne dafür ein zusätzliches Objekt zu erstellen.

+ super () + in Mehrfachvererbung

Nachdem Sie eine Übersicht und einige Beispiele für "+ super () " und Einzelvererbung durchgearbeitet haben, werden Sie in eine Übersicht und einige Beispiele eingeführt, die zeigen, wie Mehrfachvererbung funktioniert und wie " super () +" aktiviert diese Funktionalität.

Übersicht über Mehrfachvererbung

Es gibt einen anderen Anwendungsfall, in dem + super () + wirklich glänzt, und dieser ist nicht so häufig wie das Einzelvererbungsszenario. Zusätzlich zur Einzelvererbung unterstützt Python die Mehrfachvererbung, bei der eine Unterklasse von mehreren Oberklassen erben kann, die nicht unbedingt voneinander erben müssen (auch als Geschwisterklassen bezeichnet).

Ich bin eine sehr visuelle Person und finde Diagramme unglaublich hilfreich, um solche Konzepte zu verstehen. Das folgende Bild zeigt ein sehr einfaches Szenario mit Mehrfachvererbung, bei dem eine Klasse von zwei nicht verwandten (Geschwister-) Oberklassen erbt:

Um die Mehrfachvererbung in Aktion besser zu veranschaulichen, können Sie hier einen Code ausprobieren, der zeigt, wie Sie eine rechte Pyramide (eine Pyramide mit quadratischer Basis) aus einem "+ Dreieck " und einem " Quadrat +" erstellen können:

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 *self.base* self.height

class RightPyramid(Triangle, Square):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 *perimeter* self.slant_height + base_area
*Hinweis:* Der Begriff *Schräghöhe* ist möglicherweise nicht bekannt, insbesondere wenn Sie vor einiger Zeit eine Geometrieklasse belegt oder an Pyramiden gearbeitet haben.

Die Neigungshöhe ist die Höhe von der Mitte der Basis eines Objekts (wie einer Pyramide) bis zur Spitze dieses Objekts. Weitere Informationen zu Schräghöhen finden Sie unter WolframMathWorld.

In diesem Beispiel werden eine Klasse "+ Triangle " und eine Klasse " RightPyramid " deklariert, die sowohl von " Square " als auch von " Triangle +" erben.

Sie sehen eine andere + .area () + Methode, die + super () + wie bei der Einzelvererbung verwendet, mit dem Ziel, die + .perimeter () + und + .area ( ) + `Methoden, die in der Klasse + Rectangle + `vollständig definiert sind.

*Hinweis:* Möglicherweise stellen Sie fest, dass der obige Code noch keine geerbten Eigenschaften aus der Klasse "+ Triangle +" verwendet. Spätere Beispiele werden die Vererbung sowohl von "+ Dreieck +" als auch von "+ Quadrat +" voll ausnutzen.

Das Problem ist jedoch, dass beide Oberklassen (+ Dreieck + und + Quadrat +) ein + .area () + definieren. Nehmen Sie sich eine Sekunde Zeit und überlegen Sie, was passieren könnte, wenn Sie "+ .area () " auf " RightPyramid +" aufrufen. Versuchen Sie dann, es wie folgt aufzurufen:

>>>

>> pyramid = RightPyramid(2, 4)
>> pyramid.area()
Traceback (most recent call last):
  File "shapes.py", line 63, in <module>
    print(pyramid.area())
  File "shapes.py", line 47, in area
    base_area = super().area()
  File "shapes.py", line 38, in area
    return 0.5 *self.base* self.height
AttributeError: 'RightPyramid' object has no attribute 'height'

Haben Sie vermutet, dass Python versuchen wird, "+ Triangle.area () +" aufzurufen? Dies liegt an der sogenannten Methodenauflösungsreihenfolge .

*Hinweis:* Wie haben wir festgestellt, dass `+ Triangle.area () +` aufgerufen wurde und nicht, wie wir gehofft hatten, `+ Square.area () +`? Wenn Sie sich die letzte Zeile des Tracebacks ansehen (vor dem `+ AttributeError +`), sehen Sie einen Verweis auf eine bestimmte Codezeile:
return 0.5 *self.base* self.height

Sie können dies an der Geometrieklasse als Formel für die Fläche eines Dreiecks erkennen. Andernfalls, wenn Sie wie ich sind, haben Sie möglicherweise zu den Klassendefinitionen "+ Dreieck " und " Rechteck " gescrollt und denselben Code in " Triangle.area () +" gesehen.

Reihenfolge der Methodenauflösung

Die Reihenfolge der Methodenauflösung (oder MRO ) teilt Python mit, wie nach geerbten Methoden gesucht werden soll. Dies ist praktisch, wenn Sie "+ super () " verwenden, da der MRO Ihnen genau sagt, wo Python nach einer Methode sucht, die Sie mit " super () +" aufrufen, und in welcher Reihenfolge.

Jede Klasse hat ein Attribut "+ . mro +", mit dem wir die Reihenfolge überprüfen können. Gehen wir also folgendermaßen vor:

>>>

>>> RightPyramid.__mro__
(<class '__main__.RightPyramid'>, <class '__main__.Triangle'>,
 <class '__main__.Square'>, <class '__main__.Rectangle'>,
 <class 'object'>)

Dies sagt uns, dass Methoden zuerst in "+ Rechte Pyramide ", dann in " Dreieck ", dann in " Quadrat ", dann " Rechteck " und dann, wenn nichts gefunden wird, in " Objekt +" von gesucht werden welche alle Klassen entstehen.

Das Problem hierbei ist, dass der Interpreter nach "+ .area () " in " Triangle " vor " Square " und " Rectangle " sucht und " .area () " in " Triangle " findet Python nennt es anstelle des gewünschten. Da ` Triangle.area () ` erwartet, dass es ein ` .height ` und ein ` .base ` Attribut gibt, löst Python einen ` AttributeError +` aus.

Glücklicherweise haben Sie eine gewisse Kontrolle darüber, wie der MRO aufgebaut ist. Durch einfaches Ändern der Signatur der Klasse "+ RightPyramid +" können Sie in der gewünschten Reihenfolge suchen, und die Methoden werden korrekt aufgelöst:

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height
        super().__init__(self.base)

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 *perimeter* self.slant_height + base_area

Beachten Sie, dass "+ RightPyramid " teilweise mit " . init () " aus der Klasse " Square " initialisiert wird. Dadurch kann ` .area () ` die ` .length +` für das Objekt wie vorgesehen verwenden.

Jetzt können Sie eine Pyramide bauen, die MRO überprüfen und die Oberfläche berechnen:

>>>

>>> pyramid = RightPyramid(2, 4)
>>> RightPyramid.__mro__
(<class '__main__.RightPyramid'>, <class '__main__.Square'>,
<class '__main__.Rectangle'>, <class '__main__.Triangle'>,
<class 'object'>)
>>> pyramid.area()
20.0

Sie sehen, dass die MRO jetzt Ihren Erwartungen entspricht, und Sie können dank + .area () + und + .perimeter () + auch den Bereich der Pyramide inspizieren.

Hier gibt es jedoch immer noch ein Problem. Der Einfachheit halber habe ich in diesem Beispiel einige Dinge falsch gemacht: Die erste und wohl wichtigste war, dass ich zwei separate Klassen mit demselben Methodennamen und derselben Signatur hatte.

Dies führt zu Problemen bei der Methodenauflösung, da die erste Instanz von "+ .area () +" aufgerufen wird, die in der MRO-Liste auftritt.

Wenn Sie "+ super () +" mit Mehrfachvererbung verwenden, müssen Sie Ihre Klassen unbedingt so gestalten, dass sie zusammenarbeiten . Ein Teil davon besteht darin, sicherzustellen, dass Ihre Methoden eindeutig sind, damit sie im MRO aufgelöst werden, indem sichergestellt wird, dass Methodensignaturen eindeutig sind - ob mithilfe von Methodennamen oder Methodenparametern.

In diesem Fall können Sie die Methode "+ .area () " der Klasse " Triangle " in " .tri_area () +" umbenennen, um eine vollständige Überarbeitung Ihres Codes zu vermeiden. Auf diese Weise können die Bereichsmethoden weiterhin Klasseneigenschaften verwenden, anstatt externe Parameter zu verwenden:

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height
        super().__init__()

    def tri_area(self):
        return 0.5 *self.base* self.height

Lassen Sie uns dies auch in der Klasse "+ RightPyramid +" verwenden:

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height
        super().__init__(self.base)

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 *perimeter* self.slant_height + base_area

    def area_2(self):
        base_area = super().area()
        triangle_area = super().tri_area()
        return triangle_area * 4 + base_area

Das nächste Problem hierbei ist, dass der Code kein delegiertes "+ Triangle " - Objekt hat, wie dies bei einem " Square " - Objekt der Fall ist. Wenn Sie also " .area_2 () " aufrufen, erhalten Sie einen " AttributeError +" seit " + .base + und + .height + `haben keine Werte.

Sie müssen zwei Dinge tun, um dies zu beheben:

  1. Alle Methoden, die mit "+ super () " aufgerufen werden, müssen die Version dieser Methode ihrer Oberklasse aufrufen. Dies bedeutet, dass Sie ` super () . init () ` zu den ` . init () ` Methoden von ` Triangle ` und ` Rectangle +` hinzufügen müssen.

  2. Entwerfen Sie alle Aufrufe von + . init () + neu, um ein Schlüsselwortwörterbuch zu verwenden. Siehe den vollständigen Code unten.

Es gibt eine Reihe wichtiger Unterschiede in diesem Code:

  • + kwargs + wurde an einigen Stellen geändert (z. B. + RightPyramid . init () +): Dadurch können Benutzer dieser Objekte sie nur mit den Argumenten instanziieren, die für dieses bestimmte Objekt sinnvoll sind.

  • Einrichten benannter Argumente vor + kwargs +