super () déclenche "TypeError: doit être de type, pas classobj" pour une classe de nouveau style

335

L'utilisation suivante de super()déclenche une TypeError: pourquoi?

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

Il y a une question similaire sur StackOverflow: Python super () déclenche TypeError , où l'erreur est expliquée par le fait que la classe utilisateur n'est pas une classe de nouveau style. Cependant, la classe ci-dessus est une classe de nouveau style, car elle hérite de object:

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

Qu'est-ce que je rate? Comment puis-je utiliser super(), ici?

Utiliser HTMLParser.__init__(self)au lieu de super(TextParser, self).__init__()fonctionnerait, mais je voudrais comprendre le TypeError.

PS: Joachim a souligné qu'être une instance de classe de nouveau style n'est pas équivalent à être un object. J'ai lu l'opposé plusieurs fois, d'où ma confusion (exemple de test d'instance de classe de nouveau style basé sur le objecttest d'instance: https://stackoverflow.com/revisions/2655651/3 ).

Eric O Lebigot
la source
3
Merci pour votre question et réponse. Je me demande pourquoi 2.7 super.__doc__ne mentionne rien de l'ancien contre le nouveau style!
Kelvin
Merci. :) Les docstrings contiennent généralement moins d'informations que la version HTML complète de la documentation. Le fait que cela super()ne fonctionne que pour les classes (et les objets) de nouveau style est mentionné dans le document HTML ( docs.python.org/library/functions.html#super ).
Eric O Lebigot
1
la duplication possible de python super () déclenche TypeError! Pourquoi?
utilisateur
Ce n'est pas un doublon (voir la question mise à jour et la réponse acceptée).
Eric O Lebigot

Réponses:

246

D'accord, c'est l'habituel " super()ne peut pas être utilisé avec une classe à l'ancienne".

Cependant, le point important est que le test correct pour "est-ce une instance de nouveau style (c'est-à-dire un objet)?" est

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

et non (comme dans la question):

>>> isinstance(instance, object)
True

Pour les classes , le test correct "est-ce une classe de nouveau style" est:

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

Le point crucial est qu'avec les classes à l'ancienne, la classe d'une instance et son type sont distincts. , Ici OldStyle().__class__est OldStyle, qui ne hérite de object, tout type(OldStyle())est le instancetype, qui ne hérite de object. Fondamentalement, une classe à l'ancienne crée simplement des objets de type instance(alors qu'une classe à nouveau style crée des objets dont le type est la classe elle-même). Ceci est probablement la raison pour laquelle l'instance OldStyle()est object: ses type()hérite de object(le fait que sa classe ne pas hériter de objectne compte pas: les classes de style ancien construisent simplement de nouveaux objets de type instance). Référence partielle:https://stackoverflow.com/a/9699961/42973 .

PS: La différence entre une classe de style nouveau et une classe de style ancien peut également être vue avec:

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

(les classes à l'ancienne ne sont pas des types, elles ne peuvent donc pas être le type de leurs instances).

Eric O Lebigot
la source
11
Et c'est l'une des raisons pour lesquelles nous avons maintenant Python 3.
Steven Rumbalski
2
BTW: (Oldstyle().__class__ is Oldstyle)estTrue
Tino
2
@Tino: en effet, mais le but de OldStyle().__class__est de montrer comment tester si un objet ( OldStyle()) provient d'une classe à l'ancienne. Avec seulement des classes de style nouveau à l'esprit, on pourrait être tenté de faire le test avec à la isinstance(OldStyle(), object)place.
Eric O Lebigot
27
Il est absurde combien de la bibliothèque standard de python encore dans 2.7.x ne pas hériter de object, ainsi vous vissant par procuration.
Nick Bastin
2
@NickBastin - ce n'est pas une coïncidence. C'est tout pour pousser tout le monde dans Python 3. Où "tout va bien déjà." Mais - caveat emptor - c'est seulement un appât et un interrupteur.
Tomasz Gandor
204

super () ne peut être utilisé que dans les classes de nouveau style, ce qui signifie que la classe racine doit hériter de la classe 'object'.

Par exemple, la classe supérieure doit être comme ceci:

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

ne pas

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

Donc, la solution est d'appeler directement la méthode init du parent , comme ceci:

class TextParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.all_data = []
Colin Su
la source
8
Pour moi, je devais faire ceci: HTMLParser .__ init __ (auto) Je suis curieux de savoir si votre dernier exemple a fonctionné?
chaimp
1
@EOL Qu'est-ce que cela signifie? jeffp vient de souligner que le code donné dans cette réponse est erroné en raison d'un manque de selfparamètre dans l' HTMLParser.__init__()appel.
Piotr Dobrogost
1
@PiotrDobrogost: Désolé, ma remarque concernait la réponse de LittleQ, pas le (bon) point de Jeffff.
Eric O Lebigot
1
@jeffp désolé, c'est une faute de frappe, je viens de le taper SO mais je ne l'ai pas testé, ma faute. merci pour la correction
Colin Su
1
Votez pour un correctif qui fonctionne avec le code existant, tel que logging.Formatter en python2.6
David Reynolds
28

Vous pouvez également utiliser class TextParser(HTMLParser, object):. Cela crée TextParserune classe de nouveau style et super()peut être utilisé.

Valentin Lorentz
la source
Un vote positif de ma part, car ajouter l'héritage de l'objet est une bonne idée. (Cela dit, cette réponse n'aborde pas la question de la compréhension de l'erreur de type de la question.)
Eric O Lebigot
23

Le problème est qu'il a superbesoin d' objectun ancêtre:

>>> 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 y regardant de plus près, on constate:

>>> type(myclass)
classobj

Mais:

>>> class newstyle(object): pass

>>> type(newstyle)
type    

Ainsi, la solution à votre problème serait d'hériter de l'objet ainsi que de HTMLParser. Mais assurez-vous que l'objet arrive en dernier dans les classes MRO:

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

>>> myclass().os
True
user2070206
la source
Points valides, mais ils figurent déjà dans les réponses précédentes. De plus, au lieu de vérifier type(myclass), ce qui importe est de savoir si myclasshériter de l'objet (c'est-à-dire si isinstance(myclass, object)c'est vrai, c'est faux).
Eric O Lebigot
17

Si vous regardez l'arbre d'héritage (dans la version 2.6), HTMLParserhérite de SGMLParserqui hérite de ParserBasequi n'hérite pas de object. Ie HTMLParser est une classe à l'ancienne.

À propos de votre vérification avec isinstance, j'ai fait un test rapide en ipython:

Dans [1]: classe A:
   ...: passer
   ...: 

Dans [2]: isinstance (A, objet)
Sortie [2]: Vrai

Même si une classe est une classe à l'ancienne, c'est toujours une instance de object.

Un mec de programmeur
la source
2
Je crois que le bon test devrait être isinstance(A(), object), non isinstance(A, object), non? Avec ce dernier, vous testez si la classe A est un object, alors que la question est de savoir si les instances de Asont un object, non?
Eric O Lebigot
5
PS: le meilleur test semble être issubclass(HTMLParser, object), ce qui renvoie Faux.
Eric O Lebigot
5

la bonne façon de faire sera comme suit dans les classes à l'ancienne qui n'hérite pas de 'object'

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

class B(A):
    def foo(self, name):
        return A.foo(self) + name
Jacob Abraham
la source
1
Dans la question, cette méthode est déjà évoquée: le problème est de comprendre pourquoi un message d'erreur a été produit, pas de le faire disparaître (notamment de cette façon).
Eric O Lebigot
En outre, cette solution ne fonctionnera pas dans le cas où nous voulons appeler une instance de la classe Aqui stocke un état.
tashuhka
0

FWIW et bien que je ne sois pas un gourou de Python, je m'en suis sorti

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

Je viens de récupérer les résultats de l'analyse au besoin.

qwerty_so
la source