super () plantea "TypeError: debe ser type, no classobj" para la clase de estilo nuevo

335

El siguiente uso de super()plantea un error de tipo: ¿por qué?

>>> from  HTMLParser import HTMLParser
>>> class TextParser(HTMLParser):
...     def __init__(self):
...         super(TextParser, self).__init__()
...         self.all_data = []
...         
>>> TextParser()
(...)
TypeError: must be type, not classobj

Hay una pregunta similar en StackOverflow: Python super () plantea TypeError , donde el error se explica por el hecho de que la clase de usuario no es una clase de estilo nuevo. Sin embargo, la clase anterior es una clase de nuevo estilo, ya que hereda de object:

>>> isinstance(HTMLParser(), object)
True

¿Qué me estoy perdiendo? ¿Cómo puedo usar super()aquí?

Usar en HTMLParser.__init__(self)lugar de super(TextParser, self).__init__()funcionaría, pero me gustaría entender el TypeError.

PD: Joachim señaló que ser una instancia de clase de estilo nuevo no es equivalente a ser un object. Leí lo contrario muchas veces, de ahí mi confusión (ejemplo de prueba de instancia de clase de nuevo estilo basada en objectprueba de instancia: https://stackoverflow.com/revisions/2655651/3 ).

Eric O Lebigot
fuente
3
Gracias por tu pregunta y respuesta. ¡Me pregunto por qué 2.7 super.__doc__no menciona nada sobre el estilo antiguo frente al nuevo!
Kelvin
Gracias. :) Las cadenas de documentos generalmente contienen menos información que la versión HTML completa de la documentación. El hecho de que super()solo funciona para clases (y objetos) de estilo nuevo se menciona en el documento HTML ( docs.python.org/library/functions.html#super ).
Eric O Lebigot
1
posible duplicado de python super () plantea TypeError! ¿Por qué?
usuario
Esto no es un duplicado (vea la pregunta actualizada y la respuesta aceptada).
Eric O Lebigot

Respuestas:

246

Muy bien, es lo habitual " super()no se puede usar con una clase de estilo antiguo".

Sin embargo, el punto importante es que la prueba correcta para "¿es esta una instancia de nuevo estilo (es decir, un objeto)?" es

>>> class OldStyle: pass
>>> instance = OldStyle()
>>> issubclass(instance.__class__, object)
False

y no (como en la pregunta):

>>> isinstance(instance, object)
True

Para las clases , la prueba correcta de "es una clase de estilo nuevo" es:

>>> issubclass(OldStyle, object)  # OldStyle is not a new-style class
False
>>> issubclass(int, object)  # int is a new-style class
True

El punto crucial es que con las clases de estilo antiguo, la clase de una instancia y su tipo son distintos. En este caso, OldStyle().__class__es OldStyleque no hereda de object, mientras que type(OldStyle())es el instancetipo, que no heredan de object. Básicamente, una clase de estilo antiguo solo crea objetos de tipo instance(mientras que una clase de estilo nuevo crea objetos cuyo tipo es la clase misma). Esta es probablemente la razón por la cual la instancia OldStyle()es object: type()hereda de object(el hecho de que su clase no herede de objectno cuenta: las clases de estilo antiguo simplemente construyen nuevos objetos de tipo instance). Referencia parcial:https://stackoverflow.com/a/9699961/42973 .

PD: La diferencia entre una clase de estilo nuevo y una de estilo antiguo también se puede ver con:

>>> type(OldStyle)  # OldStyle creates objects but is not itself a type
classobj
>>> isinstance(OldStyle, type)
False
>>> type(int)  # A new-style class is a type
type

(las clases de estilo antiguo no son tipos, por lo que no pueden ser el tipo de sus instancias).

Eric O Lebigot
fuente
11
Y esta es una de las razones por las que ahora tenemos Python 3.
Steven Rumbalski
2
Por cierto: (Oldstyle().__class__ is Oldstyle)esTrue
Tino
2
@Tino: de hecho, pero el punto OldStyle().__class__es mostrar cómo probar si un objeto ( OldStyle()) proviene de una clase de estilo antiguo. Con solo las clases de estilo nuevo en mente, uno podría verse tentado a hacer la prueba isinstance(OldStyle(), object).
Eric O Lebigot
27
Es absurdo cuánto de la biblioteca estándar de Python todavía en 2.7.x no hereda object, por lo que te atornilla por proxy.
Nick Bastin
2
@NickBastin: esto no es una coincidencia. Todo es para empujar a todos a Python 3. Donde "todo ya está bien". Pero, advertencia, solo es cebo y cambio.
Tomasz Gandor
204

super () solo se puede usar en las clases de estilo nuevo, lo que significa que la clase raíz debe heredar de la clase 'objeto'.

Por ejemplo, la clase superior debe ser así:

class SomeClass(object):
    def __init__(self):
        ....

no

class SomeClass():
    def __init__(self):
        ....

Entonces, la solución es llamar directamente al método init del padre , de esta manera:

class TextParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.all_data = []
Colin Su
fuente
8
Para mí, tuve que hacer esto: HTMLParser .__ init __ (self) Tengo curiosidad por saber si su último ejemplo funcionó.
Chaimp
1
@EOL ¿Qué significan? jeffp simplemente señaló que el código proporcionado en esta respuesta es incorrecto debido a la falta de selfparámetros en la HTMLParser.__init__()llamada.
Piotr Dobrogost
1
@PiotrDobrogost: Lo siento, mi comentario fue sobre la respuesta de LittleQ, no sobre el punto (bueno) de jeffp.
Eric O Lebigot
1
@jeffp lo siento, es un error tipográfico, solo lo escribo en SO pero no lo he probado, es mi culpa. gracias por corregir
Colin Su
1
Vota a favor de una solución que funcione con el código existente, como el registro. Formateador en python2.6
David Reynolds
28

También puedes usar class TextParser(HTMLParser, object):. Esto hace TextParseruna nueva clase de estilo , y super()se puede usar.

Valentin Lorentz
fuente
Un voto positivo de mi parte, ya que agregar la herencia del objeto es una buena idea. (Dicho esto, esta respuesta no aborda el problema de comprender el
error tipográfico
23

El problema es que supernecesita objectun antepasado:

>>> class oldstyle:
...     def __init__(self): self.os = True

>>> class myclass(oldstyle):
...     def __init__(self): super(myclass, self).__init__()

>>> myclass()
TypeError: must be type, not classobj

En un examen más detallado, uno encuentra:

>>> type(myclass)
classobj

Pero:

>>> class newstyle(object): pass

>>> type(newstyle)
type    

Entonces, la solución a su problema sería heredar del objeto y de HTMLParser. Pero asegúrese de que el objeto venga último en las clases MRO:

>>> class myclass(oldstyle, object):
...     def __init__(self): super(myclass, self).__init__()

>>> myclass().os
True
usuario2070206
fuente
Puntos válidos, pero ya están en respuestas anteriores. Además, en lugar de verificar type(myclass), lo que importa es si myclassheredar del objeto (es decir, si isinstance(myclass, object)es verdadero, es falso).
Eric O Lebigot
17

Si observa el árbol de herencia (en la versión 2.6), HTMLParserhereda de lo SGMLParserque hereda de ParserBaselo que no hereda object. Es decir, HTMLParser es una clase de estilo antiguo.

Sobre su verificación con isinstance, hice una prueba rápida en ipython:

En [1]: clase A:
   ...: pasar
   ...: 

En [2]: isinstance (A, objeto)
Fuera [2]: verdadero

Incluso si una clase es de estilo antiguo, sigue siendo una instancia de object.

Algún tipo programador
fuente
2
Creo que la prueba correcta debería ser isinstance(A(), object), no isinstance(A, object), ¿no? Con este último, está probando si la clase A es una object, mientras que la pregunta es si las instancias de Ason una object, ¿verdad?
Eric O Lebigot
55
PD: la mejor prueba parece ser issubclass(HTMLParser, object), que devuelve False.
Eric O Lebigot
5

la forma correcta de hacerlo será la siguiente en las clases de estilo antiguo que no hereda de 'objeto'

class A:
    def foo(self):
        return "Hi there"

class B(A):
    def foo(self, name):
        return A.foo(self) + name
Jacob Abraham
fuente
1
En la pregunta, este método ya se menciona: el problema es comprender por qué se produjo un mensaje de error, no hacer que desaparezca (en particular de esta manera).
Eric O Lebigot
Además, esta solución no funcionará en el caso de que queramos una llamada a una instancia de la clase Aque almacena un estado.
tashuhka
0

FWIW y aunque no soy un gurú de Python, me las arreglé con esto

>>> class TextParser(HTMLParser):
...    def handle_starttag(self, tag, attrs):
...        if tag == "b":
...            self.all_data.append("bold")
...        else:
...            self.all_data.append("other")
...     
...         
>>> p = TextParser()
>>> p.all_data = []
>>> p.feed(text)
>>> print p.all_data
(...)

Acabo de recibir los resultados del análisis según sea necesario.

qwerty_so
fuente