Warum ist Python 3.x super () magisch?

159

super()Kann in Python 3.x ohne Argumente aufgerufen werden:

class A(object):
    def x(self):
         print("Hey now")

class B(A):
    def x(self):
        super().x()
>>> B().x()
Hey now

Um diese Arbeit zu machen, wird einige Kompilierung-Magie durchgeführt wird , eine Folge davon ist , dass der folgende Code (die erneut bindet superan super_) fehlschlägt:

super_ = super

class A(object):
    def x(self):
        print("No flipping")

class B(A):
    def x(self):
        super_().x()
>>> B().x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in x
RuntimeError: super(): __class__ cell not found

Warum kann super()die Superklasse zur Laufzeit nicht ohne Unterstützung des Compilers aufgelöst werden? Gibt es praktische Situationen, in denen dieses Verhalten oder der zugrunde liegende Grund einen unachtsamen Programmierer beißen könnte?

... und als Nebenfrage: Gibt es in Python noch andere Beispiele für Funktionen, Methoden usw., die durch erneutes Binden an einen anderen Namen beschädigt werden können?

Null Piräus
quelle
6
Ich lasse Armin auf diese die Erklärung tut ein . Dies ist auch ein weiterer guter Beitrag
Games Brainiac

Antworten:

216

Das neue magische super()Verhalten wurde hinzugefügt, um eine Verletzung des DRY-Prinzips (Don't Repeat Yourself) zu vermeiden (siehe PEP 3135) . Wenn Sie die Klasse explizit benennen müssen, indem Sie sie als global bezeichnen, treten dieselben Probleme auf, die Sie bei sich super()selbst festgestellt haben :

class Foo(Bar):
    def baz(self):
        return super(Foo, self).baz() + 42

Spam = Foo
Foo = something_else()

Spam().baz()  # liable to blow up

Gleiches gilt für die Verwendung von Klassendekoratoren, bei denen der Dekorator ein neues Objekt zurückgibt, das den Klassennamen neu bindet:

@class_decorator_returning_new_class
class Foo(Bar):
    def baz(self):
        # Now `Foo` is a *different class*
        return super(Foo, self).baz() + 42

Die magische super() __class__Zelle umgeht diese Probleme, indem sie Ihnen Zugriff auf das ursprüngliche Klassenobjekt gewährt.

Der PEP wurde von Guido ins Leben gerufen, der sich ursprünglich vorstellte super, ein Schlüsselwort zu werden , und die Idee, eine Zelle zum Nachschlagen der aktuellen Klasse zu verwenden, war auch seine . Die Idee, daraus ein Schlüsselwort zu machen, war sicherlich Teil des ersten Entwurfs des PEP .

Tatsächlich war es jedoch Guido selbst, der sich dann von der Keyword-Idee als "zu magisch" zurückzog und stattdessen die aktuelle Implementierung vorschlug. Er rechnete damit, dass die Verwendung eines anderen Namens super()ein Problem sein könnte :

Mein Patch verwendet eine Zwischenlösung: Es wird davon ausgegangen, dass Sie diese benötigen, __class__ wenn Sie eine Variable mit dem Namen verwenden 'super'. Wenn Sie (global) So benennen Sie superzu supperund Verwendung supperaber nicht super, es wird nicht funktionieren ohne Argumente (aber es wird immer noch funktionieren , wenn Sie übergeben es entweder __class__oder das tatsächliche Klassenobjekt); Wenn Sie eine nicht verwandte Variable mit dem Namen haben super, funktionieren die Dinge, aber die Methode verwendet den etwas langsameren Aufrufpfad, der für Zellenvariablen verwendet wird.

Am Ende war es also Guido selbst, der verkündete, dass sich die Verwendung eines superSchlüsselworts nicht richtig anfühlte und dass die Bereitstellung einer magischen __class__Zelle ein akzeptabler Kompromiss war.

Ich stimme zu, dass das magische, implizite Verhalten der Implementierung etwas überraschend ist, aber super()eine der am häufigsten falsch angewendeten Funktionen in der Sprache ist. Schauen Sie sich einfach alle falsch angewendeten super(type(self), self)oder super(self.__class__, self) aufgerufenen Aufrufe im Internet an. Wenn einer dieser Codes jemals von einer abgeleiteten Klasse aufgerufen wurde , würde dies zu einer unendlichen Rekursionsausnahme führen . Zumindest super()vermeidet der vereinfachte Aufruf ohne Argumente dieses Problem.

Wie für die umbenannten super_; Referenz nur __class__in Ihrer Methode als auch und es wird wieder funktionieren. Die Zelle wird erstellt, wenn Sie in Ihrer Methode entweder auf die super oder auf die __class__ Namen verweisen :

>>> super_ = super
>>> class A(object):
...     def x(self):
...         print("No flipping")
... 
>>> class B(A):
...     def x(self):
...         __class__  # just referencing it is enough
...         super_().x()
... 
>>> B().x()
No flipping
Martijn Pieters
quelle
1
Gutes Schreiben. Es ist jedoch immer noch so klar wie Schlamm. Sie sagen, dass super () einer automatisch instanziierten Funktion entspricht, die einer def super(of_class=magic __class__)Art ähnelt self.super(); def super(self): return self.__class__?
Charles Merriam
17
@CharlesMerriam: In diesem Beitrag geht es nicht darum, wie super()ohne Argumente funktioniert. es geht hauptsächlich um das Warum es existiert. super()ist in einer Klassenmethode äquivalent zu super(ReferenceToClassMethodIsBeingDefinedIn, self), wo ReferenceToClassMethodIsBeingDefinedInzur Kompilierungszeit bestimmt, als geschlossener Abschluss an die Methode angehängt __class__und super()wird zur Laufzeit vom aufrufenden Frame nachgeschlagen. Aber das alles muss man eigentlich nicht wissen.
Martijn Pieters
1
@ CharlesMerriam: ist aber bei weitem super()nicht annähernd eine automatisch instanziierte Funktion , nein.
Martijn Pieters
1
@ chris.leonard: Der Schlüsselsatz lautet Die Zelle wird erstellt, wenn Sie super () oder __class__in Ihrer Methode verwenden . Sie haben den Namen superin Ihrer Funktion verwendet. Der Compiler sieht das und fügt den __class__Abschluss hinzu.
Martijn Pieters
4
@ Alexander: Es ist nicht genug. type(self)gibt den aktuellen Typ an, der nicht mit dem Typ identisch ist, für den die Methode definiert ist. So eine Klasse Foomit der Methode bazBedürfnissen super(Foo, self).baz(), weil es als unterklassiert werden könnte class Ham(Foo):, an welchem Punkt type(self)ist Hamund super(type(self), self).baz()würden Ihnen eine Endlosschleife geben. Siehe den Beitrag, auf den ich in meiner Antwort verweise
Martijn Pieters