Pourquoi la super () magie de Python 3.x?

159

En Python 3.x, super()peut être appelé sans arguments:

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

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

Afin de faire ce travail, une certaine magie lors de la compilation est effectuée, dont une conséquence est que le code suivant (qui se lie superà super_) échoue:

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

Pourquoi est-il super()impossible de résoudre la superclasse au moment de l'exécution sans l'aide du compilateur? Existe-t-il des situations pratiques dans lesquelles ce comportement, ou la raison sous-jacente de celui-ci, pourrait mordre un programmeur imprudent?

... et, en guise de question secondaire: y a-t-il d'autres exemples en Python de fonctions, méthodes, etc. qui peuvent être brisés en les reliant à un nom différent?

Zéro Pirée
la source
6
Je vais laisser Armin faire l'explication sur celui- ci . C'est aussi un autre bon article
Games Brainiac

Réponses:

217

Le nouveau super()comportement magique a été ajouté pour éviter de violer le principe DRY (Don't Repeat Yourself), voir PEP 3135 . Devoir nommer explicitement la classe en la référençant comme un global est également sujet aux mêmes problèmes de reliure que vous avez découverts avec super()lui-même:

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

Spam = Foo
Foo = something_else()

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

La même chose s'applique à l'utilisation de décorateurs de classe où le décorateur renvoie un nouvel objet, qui relie le nom de la classe:

@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__cellule magique évite ces problèmes en vous donnant accès à l'objet de classe d'origine.

Le PEP a été lancé par Guido, qui envisageait initialement de superdevenir un mot-clé , et l'idée d'utiliser une cellule pour rechercher la classe actuelle était également la sienne . Certes, l'idée d'en faire un mot-clé faisait partie de la première ébauche du PEP .

Cependant, c'est en fait Guido lui-même qui s'est ensuite éloigné de l'idée de mot-clé comme «trop magique» , proposant plutôt l'implémentation actuelle. Il prévoyait que l'utilisation d'un nom différent pour super()pourrait être un problème :

Mon patch utilise une solution intermédiaire: il suppose que vous en avez besoin __class__ chaque fois que vous utilisez une variable nommée 'super'. Ainsi, si vous (globalement) renommer superà supperet de l' utilisation suppermais non super, cela ne fonctionnera pas sans arguments (mais cela fonctionnera si vous passez soit __class__ou l'objet de classe réelle); si vous avez une variable non liée nommée super, les choses fonctionneront mais la méthode utilisera le chemin d'appel légèrement plus lent utilisé pour les variables de cellule.

Donc, à la fin, c'est Guido lui-même qui a proclamé que l'utilisation d'un supermot-clé ne se sentait pas bien, et que fournir une __class__cellule magique était un compromis acceptable.

Je conviens que le comportement magique et implicite de l'implémentation est quelque peu surprenant, mais super()c'est l'une des fonctions les plus mal appliquées du langage. Jetez un œil à toutes les invocations mal appliquées super(type(self), self)ou super(self.__class__, self)trouvées sur Internet; si l'un de ces codes était appelé à partir d'une classe dérivée, vous vous retrouveriez avec une exception de récursivité infinie . À tout le moins, l' super()appel simplifié , sans arguments, évite ce problème.

Quant à la renommée super_; simplement référence __class__dans votre méthode aussi bien et ça va fonctionner à nouveau. La cellule est créée si vous référencez les noms super ou __class__ dans votre méthode:

>>> 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
la source
1
Bonne rédaction. C'est toujours aussi clair que la boue. Vous dites que super () équivaut à une fonction automatiquement instanciée comme un def super(of_class=magic __class__)peu comme un self.super(); def super(self): return self.__class__?
Charles Merriam
17
@CharlesMerriam: Ce message ne porte pas sur le fonctionnement super()sans arguments; il traite principalement du pourquoi il existe. super(), dans une méthode de classe, équivaut à super(ReferenceToClassMethodIsBeingDefinedIn, self), où ReferenceToClassMethodIsBeingDefinedInest déterminé au moment de la compilation, attaché à la méthode en tant que fermeture nommée __class__, et super()recherchera les deux dans le cadre appelant au moment de l'exécution. Mais vous n'avez pas vraiment besoin de savoir tout cela.
Martijn Pieters
1
@CharlesMerriam: mais super()est loin d'être une fonction automatiquement instanciée , non.
Martijn Pieters
1
@ chris.leonard: la phrase clé est La cellule est créée si vous utilisez super () ou utilisez __class__dans votre méthode . Vous avez utilisé le nom superdans votre fonction. Le compilateur voit cela et ajoute la __class__fermeture.
Martijn Pieters
4
@Alexey: ce n'est pas assez. type(self)donne le type actuel , qui n'est pas le même que le type sur lequel la méthode est définie. Donc, une classe Fooavec des bazbesoins de méthode super(Foo, self).baz(), car elle pourrait être sous-classée comme class Ham(Foo):, à quel point type(self)est Hamet super(type(self), self).baz()vous donnerait une boucle infinie. Voir le message auquel je renvoie
Martijn Pieters