¿Por qué la super () magia de Python 3.x?


159

En Python 3.x, super()se puede invocar sin argumentos:

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

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

Para que esto funcione, se realiza algo de magia en tiempo de compilación, una de las cuales es que el siguiente código (que se vuelve supera vincular super_) falla:

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

¿Por qué super()no se puede resolver la superclase en tiempo de ejecución sin la ayuda del compilador? ¿Existen situaciones prácticas en las que este comportamiento, o la razón subyacente del mismo, podría morder a un programador desprevenido?

... y, como una pregunta secundaria: ¿hay otros ejemplos en Python de funciones, métodos, etc. que puedan romperse volviéndolos a un nombre diferente?


66
Voy a dejar que haga lo que explica Armin en esta uno . Esta es también otra buena publicación
Games Brainiac el

Respuestas:


216

El nuevo super()comportamiento mágico se agregó para evitar violar el principio SECO (No repetir), ver PEP 3135 . Tener que nombrar explícitamente la clase al hacer referencia a ella como global también es propensa a los mismos problemas de enlace que descubrió consigo super()misma:

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

Spam = Foo
Foo = something_else()

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

Lo mismo se aplica al uso de decoradores de clases donde el decorador devuelve un nuevo objeto, que vuelve a vincular el nombre de la clase:

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

La super() __class__celda mágica evita estos problemas muy bien al darle acceso al objeto de clase original.

El PEP fue iniciado por Guido, quien inicialmente imaginó superconvertirse en una palabra clave , y la idea de usar una celda para buscar la clase actual también era suya . Ciertamente, la idea de convertirla en una palabra clave fue parte del primer borrador del PEP .

Sin embargo, fue el propio Guido quien se alejó de la idea de la palabra clave como "demasiado mágico" , proponiendo la implementación actual. Se prevé que el uso de un nombre diferente para super()podría ser un problema :

Mi parche utiliza una solución intermedia: se supone que necesita __class__ cuando usa una variable llamada 'super'. Por lo tanto, si (a nivel mundial) cambiar el nombre superde suppery el uso supperpero no super, no funcionará sin argumentos (pero todavía funcionará si pasarlo bien __class__ o la clase de objeto real); si tiene una variable no relacionada llamada super, las cosas funcionarán pero el método usará la ruta de llamada un poco más lenta utilizada para las variables de celda.

Entonces, al final, fue el propio Guido quien proclamó que usar un super palabra clave no se sentía bien, y que proporcionar una __class__celda mágica era un compromiso aceptable.

Estoy de acuerdo en que el comportamiento mágico e implícito de la implementación es algo sorprendente, pero super()es una de las funciones más mal aplicadas en el lenguaje. Solo eche un vistazo a todas las invocaciones super(type(self), self)o super(self.__class__, self)invocaciones erróneas que se encuentran en Internet; si alguna vez se llamara a ese código desde una clase derivada , terminaría con una excepción de recursión infinita . Como mínimo, la super()llamada simplificada , sin argumentos, evita ese problema.

En cuanto a los renombrados super_; simplemente haga referencia __class__en su método también y funcionará nuevamente. La celda se crea si hace referencia a los nombres super o __class__ en su método:

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

1
Buena redacción. Sin embargo, sigue siendo tan claro como el barro. ¿Está diciendo que super () es equivalente a una función instanciada automáticamente como una def super(of_class=magic __class__)especie de self.super(); def super(self): return self.__class__?
Charles Merriam

17
@CharlesMerriam: Esta publicación no trata sobre cómo funciona super()sin argumentos; se trata principalmente de por qué existe. super(), en un método de clase, es equivalente a super(ReferenceToClassMethodIsBeingDefinedIn, self), donde ReferenceToClassMethodIsBeingDefinedInse determina en tiempo de compilación, adjunto al método como un cierre denominado __class__, y super()buscará ambos desde el marco de llamada en tiempo de ejecución. Pero en realidad no necesitas saber todo esto.
Martijn Pieters

1
@CharlesMerriam: pero super()no está cerca de ser una función instanciada automáticamente , no.
Martijn Pieters

1
@ chris.leonard: la oración clave es La celda se crea si usa super () o usa __class__en su método . Usaste el nombre superen tu función. El compilador ve eso y agrega el __class__cierre.
Martijn Pieters

44
@Alexey: es no suficiente. type(self)da el tipo actual , que no es el mismo que el tipo en el que se define el método. Entonces, una clase Foocon baznecesidades de método super(Foo, self).baz(), porque podría subclasificarse como class Ham(Foo):, en ese punto type(self)es Hamy super(type(self), self).baz()le daría un bucle infinito. Vea la publicación a la que enlazo en mi respuesta: Al llamar a super () en una clase derivada, ¿puedo pasar en sí.
Martijn Pieters
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.