web-dev-qa-db-fra.com

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

Dans 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

Pour que cela fonctionne, une magie au moment de la compilation est effectuée, ce qui a pour conséquence que le code suivant (qui rebinde 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 super() ne parvient-il pas à résoudre la super-classe 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, pourrait mordre le programmeur imprudent?

... et, en guise de question de côté: y at-il d’autres exemples dans Python de fonctions, méthodes, etc.) qui peuvent être cassés en les rapprochant sous un nom différent?

150
Zero Piraeus

Le nouveau comportement magique super() a été ajouté pour éviter de violer le code D.R.Y. Principe (ne vous répétez pas), voir PEP 3135 . Le fait de nommer explicitement la classe en la référençant comme globale est également sujet aux mêmes problèmes de ré-association que vous avez découverts avec super()

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 retourne un nouvel objet, qui renoue 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 cellule magique super()__class__ évite ces problèmes en vous donnant accès à l'objet de classe d'origine.

Le PEP a été lancé par Guido, qui initialement envisagé super devenir un mot clé , et l’idée d’utiliser une cellule pour rechercher la classe actuelle était aussi le sien =. Certes, l'idée d'en faire un mot-clé faisait partie du première version du PEP .

Cependant, c’est en fait Guido lui-même qui alors s’éloignant de l’idée de mot clé comme étant "trop magique" , proposant à la place 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 ayez besoin de __class__ à chaque fois que vous utilisez une variable nommée 'super'. Ainsi, si vous renommez (globalement) super en supper et utilisez supper mais pas super, cela ne fonctionnera pas sans arguments (mais cela restera travaillez si vous passez soit __class__ ou l’objet de classe réel); si vous avez une variable non liée nommée super, tout fonctionnera, mais la méthode utilisera le chemin d'appel légèrement plus lent utilisé pour les variables de cellule.

En fin de compte, c'est Guido lui-même qui a déclaré que l'utilisation d'un mot clé super ne se sentait pas bien, et que fournir une cellule magique __class__ constituait un compromis acceptable.

Je conviens que le comportement magique et implicite de l'implémentation est quelque peu surprenant, mais super() est l'une des fonctions les plus mal appliquées du langage. Il suffit de jeter un coup d'œil à tous les appels mal appliqués super(type(self), self) ou super(self.__class__, self) invocations trouvées sur Internet; si l'un de ces codes a déjà été appelé à partir d'une classe dérivée vous obtiendrez une exception de récursion infinie . À tout le moins, l'appel simplifié super(), sans argument, évite ça problème.

Quant au renommé super_; il suffit de référencer __class__ dans votre méthode aussi et ça fonctionnera à nouveau. La cellule est créée si vous référencez les noms super ou __class__ de 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
209
Martijn Pieters