102 votes

En utilisant super avec une méthode de classe

Je suis en train d'apprendre la fonction super() en Python.

Je pensais avoir compris jusqu'à ce que je trouve cet exemple (2.6) et me retrouve bloqué.

http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html#super-with-classmethod-example

Traceback (most recent call last):
  File "", line 1, in 
  File "test.py", line 9, in do_something
    do_something = classmethod(do_something)
TypeError: la méthode non liée do_something() doit être appelée avec une instance de B comme premier argument (au lieu de rien)
>>>

Ce n'était pas ce à quoi je m'attendais en lisant cette ligne juste avant l'exemple :

Si nous utilisons une méthode de classe, nous n'avons pas d'instance à appeler super avec. Heureusement pour nous, super fonctionne même avec un type en deuxième argument. --- Le type peut être passé directement à super comme le montre l'exemple ci-dessous.

C'est exactement ce que Python me dit n'est pas possible en disant que do_something() devrait être appelé avec une instance de B.

123voto

unutbu Points 222216

Parfois, les textes doivent être lus davantage pour le sens de l'idée que pour les détails. C'est l'un de ces cas.

Sur la page liée, les exemples 2.5, 2.6 et 2.7 devraient tous utiliser une méthode, do_your_stuff. (C'est-à-dire que do_something devrait être changé en do_your_stuff.)

De plus, comme l'a souligné Ned Deily, A.do_your_stuff doit être une méthode de classe.

class A(object):
    @classmethod
    def do_your_stuff(cls):
        print 'This is A'

class B(A):
    @classmethod
    def do_your_stuff(cls):
        super(B, cls).do_your_stuff()

B.do_your_stuff()

super(B, cls).do_your_stuff retourne une méthode liée (voir note de bas de page 2). Comme cls a été passé en second argument à super(), c'est cls qui est lié à la méthode retournée. En d'autres termes, c'est cls qui est passé en premier argument à la méthode do_your_stuff() de la classe A.

Pour répéter : super(B, cls).do_your_stuff() fait que la méthode do_your_stuff de A est appelée avec cls passé en premier argument. Pour que cela fonctionne, la méthode do_your_stuff de A doit être une méthode de classe. La page liée ne mentionne pas cela, mais c'est définitivement le cas.

PS. do_something = classmethod(do_something) est l'ancienne méthode de faire une méthode de classe. La méthode plus récente est d'utiliser le décorateur @classmethod.


Il est important de noter que super(B, cls) ne peut pas être remplacé par super(cls, cls). Le faire pourrait entraîner des boucles infinies. Par exemple,

class A(object):
    @classmethod
    def do_your_stuff(cls):
        print('This is A')

class B(A):
    @classmethod
    def do_your_stuff(cls):
        print('This is B')
        # super(B, cls).do_your_stuff()  # CORRECT
        super(cls, cls).do_your_stuff()  # WRONG

class C(B):
    @classmethod
    def do_your_stuff(cls):
        print('This is C')
        # super(C, cls).do_your_stuff()  # CORRECT
        super(cls, cls).do_your_stuff()  # WRONG

C.do_your_stuff()

va déclencher une RuntimeError: maximum recursion depth exceeded while calling a Python object.

Si cls est C, alors super(cls, cls) va chercher dans C.mro() la classe qui suit C.

In [161]: C.mro()
Out[161]: [__main__.C, __main__.B, __main__.A, object]

Comme cette classe est B, lorsque cls est C, super(cls, cls).do_your_stuff() appelle toujours B.do_your_stuff. Puisque super(cls, cls).do_your_stuff() est appelée à l'intérieur de B.do_your_stuff, cela finit par appeler B.do_your_stuff en boucle infinie.

En Python3, la forme à 0 arguments de super a été ajoutée afin que super(B, cls) puisse être remplacé par super(), et Python3 déduira du contexte que super() dans la définition de class B doit être équivalent à super(B, cls).

Mais en aucun cas super(cls, cls) (ou pour des raisons similaires, super(type(self), self)) n'est correct.

69voto

Tankobot Points 617

En Python 3, vous pouvez ignorer la spécification des arguments pour super,

class A:
    @classmethod
    def f(cls):
        return "La fonction f de A a été appelée."

class B(A):
    @classmethod
    def f(cls):
        return super().f()

assert B.f() == "La fonction f de A a été appelée."

4voto

Shalabh Points 21

J'ai mis à jour l'article pour le rendre un peu plus clair : Python Attributes and Methods # Super

Votre exemple utilisant classmethod ci-dessus montre ce qu'est une méthode de classe - elle passe la classe elle-même au lieu de l'instance en tant que premier paramètre. Mais vous n'avez même pas besoin d'une instance pour appeler la méthode, par exemple :

>>> class A(object):
...     @classmethod
...     def foo(cls):
...         print cls
... 
>>> A.foo() # notez que ceci est appelé directement sur la classe

2voto

Ned Deily Points 40248

L'exemple de la page web semble fonctionner tel que publié. Avez-vous créé une méthode do_something pour la superclasse mais ne l'avez pas transformée en une classmethod? Quelque chose comme ceci vous donnera cette erreur :

>>> class A(object):
...     def do_something(cls):
...         print cls
... #   do_something = classmethod(do_something)
... 
>>> class B(A):
...     def do_something(cls):
...         super(B, cls).do_something()
...     do_something = classmethod(do_something)
... 
>>> B().do_something()
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in do_something
TypeError: unbound method do_something() must be called with B instance as first argument (got nothing instead)

0voto

dezza Points 317

Je pense avoir compris le point maintenant grâce à ce magnifique site et à cette charmante communauté.

Si ça ne vous dérange pas, veuillez me corriger si je me trompe sur les méthodes de classe (que j'essaie maintenant de comprendre pleinement):

# EXEMPLE #1
>>> class A(object):
...     def foo(cls):
...             print cls
...     foo = classmethod(foo)
... 
>>> a = A()
>>> a.foo()
# C'EST LA CLASSE ELLE-MÊME (__class__)
class '__main__.A'

# EXEMPLE #2
# SIMILAIRE AU DESSUS (Avec le nouveau @decorator)
>>> class A(object):
...     @classmethod
...     def foo(cls):
...             print cls
... 
>>> a = A()
>>> a.foo()
class '__main__.A'

# EXEMPLE #3
>>> class B(object):
...     def foo(self):
...             print self
... 
>>> b = B()
>>> b.foo()
# C'EST L'INSTANCE AVEC L'ADRESSE (self)
__main__.B object at 0xb747a8ec
>>>

J'espère que cette illustration montre ..

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X