Usando super con un método de clase


82

Estoy tratando de aprender la función super () en Python.

Pensé que lo entendía hasta que encontré este ejemplo (2.6) y me encontré atascado.

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)
>>>

No era lo que esperaba cuando leí esta línea justo antes del ejemplo:

Si estamos usando un método de clase, no tenemos una instancia para llamar a super. Afortunadamente para nosotros, super funciona incluso con un tipo como segundo argumento. --- El tipo se puede pasar directamente a super como se muestra a continuación.

Que es exactamente lo que Python me dice que no es posible al decir que do_something () debería llamarse con una instancia de B.


Respuestas:


98

A veces, los textos deben leerse más por el sabor de la idea que por los detalles. Este es uno de esos casos.

En la página enlazada , los Ejemplos 2.5, 2.6 y 2.7 deben todo el método de un solo uso, do_your_stuff. (Es decir, do_somethingdebería cambiarse a do_your_stuff.)

Además, como señaló Ned Deily , A.do_your_stufftiene que ser un método de clase.

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 devuelve un método enlazado (ver nota al pie 2 ). Dado que clsse pasó como segundo argumento a super(), es el clsque se vincula al método devuelto. En otras palabras, clsse pasa como primer argumento al método do_your_stuff()de la clase A.

Para reiterar: super(B, cls).do_your_stuff()causas A's do_your_stuffmétodo para ser llamados con clspasado como primer argumento. Para que eso funcione, A's do_your_stufftiene que haber un método de clase. La página vinculada no menciona eso, pero ese es definitivamente el caso.

PD. do_something = classmethod(do_something)es la forma antigua de hacer un método de clase. La nueva (más) forma es usar el decorador @classmethod.


Tenga en cuenta que super(B, cls)no puede ser reemplazado por super(cls, cls). Hacerlo podría dar lugar a bucles infinitos. Por ejemplo,

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()

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

Si clses así C, super(cls, cls)busca C.mro()la clase que viene después C.

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

Dado que esa clase es B, cuando clses C, super(cls, cls).do_your_stuff() siempre llama B.do_your_stuff. Como super(cls, cls).do_your_stuff()se llama por dentro B.do_your_stuff, terminas llamando B.do_your_stuffen un bucle infinito.

En Python3, se agregó la forma de argumento 0 de, porsuper lo que super(B, cls)podría reemplazarse por super(), y Python3 averiguará a partir del contexto que super()en la definición de class Bdebería ser equivalente a super(B, cls).

Pero en ninguna circunstancia super(cls, cls)(o por razones similares super(type(self), self)) nunca es correcto.


¿Existen errores al usar super (cls, cls)?
George Moutsopoulos

2
@GeorgeMoutsopoulos: super(cls, cls)es casi seguro un error. Edité la publicación anterior para explicar por qué.
Unutbu

37

En Python 3, puede omitir la especificación de argumentos para 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."

Esta respuesta podría trasladarse a otra pregunta si esta pregunta en particular trata específicamente sobre Python 2 (es difícil saberlo ya que el sitio web vinculado, CafePy, ya no está disponible).
Tankobot

No me funciona en un proyecto mío. Está dandoRuntimeError: super(): no arguments
madtyn

4

Actualicé el artículo para que quede un poco más claro: Atributos y métodos de Python # Super

Su ejemplo de uso de método de clase anterior muestra qué es un método de clase: pasa la clase misma en lugar de la instancia como primer parámetro. Pero ni siquiera necesita una instancia para llamar al método, por ejemplo:

>>> class A(object):
...     @classmethod
...     def foo(cls):
...         print cls
... 
>>> A.foo() # note this is called directly on the class
<class '__main__.A'>

2

El ejemplo de la página web parece funcionar tal como se publicó. ¿ do_somethingCreó también un método para la superclase pero no lo convirtió en un método de clase? Algo como esto te dará ese error:

>>> 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)

No, mi clase A se ve así: clase A (objeto): def do_your_stuff (self): print "esto es A" ¿Es necesario tener la "clase A" como la publicaste? (con do_something = classmethod (do_something))? Siento que el documento no dice nada sobre esto ..
dza

1
El objetivo de la superllamada en el método B do_something es llamar a un método con ese nombre en una de sus superclases. Si no hay uno en A (o en Object), la llamada B (). Do_something () falla con super object has no attribute do_something. ~ unutbu señala con razón que el ejemplo del documento es defectuoso.
Ned Deily

0

Creo que he entendido el punto ahora gracias a este hermoso sitio y a esta hermosa comunidad.

Si no le importa, corríjame si me equivoco con los métodos de clase (que ahora estoy tratando de entender completamente):


# 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
>>>

Espero que esta ilustración muestre ...


Para obtener una explicación de los métodos de clase, tal vez le resulte útil: stackoverflow.com/questions/1669445/…
unutbu
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.