web-dev-qa-db-fra.com

Utiliser super avec une méthode de classe

J'essaie d'apprendre la fonction super () en Python.

Je pensais l'avoir compris jusqu'à ce que je revienne sur cet exemple (2.6) et que je me retrouve coincé.

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

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 9, in do_something
    do_something = classmethod(do_something)
TypeError: unbound method do_something() must be called with B instance as first argument (got nothing instead)
>>>

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 avec laquelle appeler super. Heureusement pour nous, super fonctionne même avec un type comme deuxième argument. --- Le type peut être transmis directement à super comme indiqué ci-dessous.

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

65
dezza

Parfois, les textes doivent être lus davantage pour la saveur de l'idée plutôt que pour les détails. C'est l'un de ces cas.

Dans la page liée , les exemples 2.5, 2.6 et 2.7 doivent tous utiliser une seule méthode, do_your_stuff. (Autrement dit, do_something Doit être remplacé par do_your_stuff.)

De plus, comme Ned Deily l'a souligné , 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 renvoie une méthode bound (voir note 2 ). Puisque cls a été passé comme deuxième argument à super(), c'est cls qui est lié à la méthode retournée. En d'autres termes, cls est passé comme premier argument à la méthode do_your_stuff() de la classe A.

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

PS. do_something = classmethod(do_something) est l'ancienne façon de créer une méthode de classe. La nouvelle (er) façon consiste à utiliser le décorateur @classmethod.


Notez que super(B, cls) ne peut pas être remplacée par super(cls, cls). Cela pourrait conduire à 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()

augmentera RuntimeError: maximum recursion depth exceeded while calling a Python object.

Si cls est C, alors super(cls, cls) recherche C.mro() pour la classe qui vient après C.

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

Puisque cette classe est B, lorsque cls est C, super(cls, cls).do_your_stuff() toujours appelle B.do_your_stuff. Puisque super(cls, cls).do_your_stuff() est appelée dans B.do_your_stuff, Vous finissez par appeler B.do_your_stuff Dans une boucle infinie.

En Python3, la forme -argument de super a été ajoutée pour que super(B, cls) puisse être remplacée par super(), et Python3 trouvera contexte que super() dans la définition de class B devrait être équivalent à super(B, cls).

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

80
unutbu

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

class A:
    @classmethod
    def f(cls):
        return "A's f was called."

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

assert B.f() == "A's f was called."
14
Tankobot

J'ai mis à jour l'article pour le rendre un peu plus clair: Attributs et méthodes Python # Super

Votre exemple d'utilisation de la méthode de classe ci-dessus montre ce qu'est une méthode de classe - elle transmet la classe elle-même au lieu de l'instance comme 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() # note this is called directly on the class
<class '__main__.A'>
3
Shalabh

L'exemple de la page Web semble fonctionner tel que publié. Avez-vous créé un do_something méthode pour la superclasse aussi mais pas en faire une méthode de classe? Quelque chose comme ça 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 "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in do_something
TypeError: unbound method do_something() must be called with B instance as first argument (got nothing instead)
2
Ned Deily

Je pense que j'ai compris le point maintenant grâce à ce beau site et à cette belle communauté.

Si cela 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):


# EXAMPLE #1
>>> class A(object):
...     def foo(cls):
...             print cls
...     foo = classmethod(foo)
... 
>>> a = A()
>>> a.foo()
# THIS IS THE CLASS ITSELF (__class__)
class '__main__.A'

# EXAMPLE #2
# SAME AS ABOVE (With new @decorator)
>>> class A(object):
...     @classmethod
...     def foo(cls):
...             print cls
... 
>>> a = A()
>>> a.foo()
class '__main__.A'

# EXAMPLE #3
>>> class B(object):
...     def foo(self):
...             print self
... 
>>> b = B()
>>> b.foo()
# THIS IS THE INSTANCE WITH ADDRESS (self)
__main__.B object at 0xb747a8ec
>>>

J'espère que cette illustration montre ..

0
dezza