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

Resuelto Zero Piraeus asked hace 10 años • 1 respuestas

En Python 3.x, super()se puede llamar 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 consecuencia de la cual es que el siguiente código (que se vuelve a vincular supera 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 afectar a un programador desprevenido?

... y, como pregunta paralela: ¿hay otros ejemplos en Python de funciones, métodos, etc. que puedan romperse volviendo a vincularlos con un nombre diferente?

Zero Piraeus avatar Oct 26 '13 21:10 Zero Piraeus
Aceptado

El nuevo super()comportamiento mágico se agregó para evitar violar el principio DRY (No repetirse), consulte PEP 3135 . Tener que nombrar explícitamente la clase haciendo referencia a ella como global también es propenso a sufrir los mismos problemas de vinculación 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 celda mágica super() __class__evita estos problemas 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 fue suya . Ciertamente, la idea de convertirlo en una palabra clave formaba parte del primer borrador del PEP .

Sin embargo, fue el propio Guido quien se alejó de la idea de la palabra clave por considerarla "demasiado mágica" y propuso en su lugar la implementación actual. Anticipó que usar un nombre diferente super()podría ser un problema :

Mi parche usa una solución intermedia: asume que necesitas __class__ cada vez que usas una variable llamada 'super'. Por lo tanto, si cambia el nombre (globalmente) supera suppery usa supperbut not super, no funcionará sin argumentos (pero seguirá funcionando si lo pasa __class__o el objeto de clase real); Si tiene una variable no relacionada llamada super, todo funcionará, pero el método utilizará la ruta de llamada ligeramente más lenta que se utiliza para las variables de celda.

Entonces, al final, fue el propio Guido quien proclamó que usar una superpalabra clave no parecía correcto 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. Basta con echar un vistazo a todos los mal aplicados super(type(self), self)o super(self.__class__, self) invocaciones que se encuentran en Internet; Si alguna vez se llamara algo de ese código desde una clase derivada, terminaría con una excepción de recursividad infinita . Como mínimo la super()convocatoria simplificada, sin argumentos, evita ese problema.

En cuanto al renombrado super_; simplemente haga referencia __class__a 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
Martijn Pieters avatar Oct 26 '2013 16:10 Martijn Pieters