Usando super con un método de clase
-
08-07-2019 - |
Pregunta
Estoy tratando de aprender la función super () en Python.
Pensé que lo había entendido hasta que leí este ejemplo (2.6) y me quedé atrapado.
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 con la que llamar 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 diciendo que do_something () debería llamarse con una instancia de B.
Solución
A veces los textos tienen que leerse más por el sabor de la idea que por los detalles. Este es uno de esos casos.
En la página vinculada , Ejemplos 2.5 , 2.6 y 2.7 deberían usar un método, do_your_stuff
. (Es decir, do_something
debe cambiarse a do_your_stuff
).
Además, como Ned Deily señaló , A.do_your_stuff
tiene 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 (consulte nota al pie 2 ). Como cls
se pasó como segundo argumento a super ()
, es cls
el que se vincula al método devuelto. En otras palabras, cls
se pasa como el primer argumento para el método do_your_stuff ()
de la clase A.
Para reiterar: super (B, cls) .do_your_stuff ()
hace que el método A
do_your_stuff
sea
llamado con cls
pasado como primer argumento. Para que eso funcione, A
do_your_stuff
tiene que ser un método de clase. La página vinculada no menciona eso,
pero ese es definitivamente el caso.
PS. do_something = classmethod (do_something)
es la antigua forma de hacer un classmethod.
La nueva forma (er) es usar el decorador @classmethod.
Tenga en cuenta que super (B, cls)
no se puede reemplazar por super (cls, cls)
. Hacerlo podría conducir 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()
elevará RuntimeError: profundidad de recursión máxima excedida al llamar a un objeto Python
.
Si cls
es C
, entonces super (cls, cls)
busca C.mro ()
para la clase que viene después de C
.
In [161]: C.mro()
Out[161]: [__main__.C, __main__.B, __main__.A, object]
Dado que esa clase es B
, cuando cls
es C
, super (cls, cls) .do_your_stuff ()
siempre llama a B.do_your_stuff
. Como super (cls, cls) .do_your_stuff ()
se llama dentro de B.do_your_stuff
, terminas llamando a B.do_your_stuff
en un bucle infinito .
En Python3, la forma de 0 argumentos de super
se agregó para que super (B, cls)
se pueda reemplazar por super ()
, y Python3 descubrirá por contexto que super ( )
en la definición de clase B
debe ser equivalente a super (B, cls)
.
Pero en ninguna circunstancia es super (cls, cls)
(o por razones similares, super (type (self), self)
) siempre correcto.
Otros consejos
En Python 3, puede omitir los argumentos de especificación 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."
He actualizado el artículo para hacerlo un poco más claro: Atributos y métodos de Python # Super
Su ejemplo usando el método de clase anterior muestra qué es un método de clase: pasa la clase en sí 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'>
El ejemplo de la página web parece funcionar según lo publicado. ¿Creó también un método do_something
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)
Creo que he entendido el punto ahora gracias a este hermoso sitio y encantadora comunidad.
Si no le importa, corríjame si me equivoco en 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 ..