super () löst "TypeError: muss type sein, nicht classobj" für eine Klasse neuen Stils aus

335

Die folgende Verwendung von super()löst einen TypeError aus: Warum?

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

Bei StackOverflow gibt es eine ähnliche Frage: Python super () löst TypeError aus , wobei der Fehler durch die Tatsache erklärt wird, dass die Benutzerklasse keine Klasse neuen Stils ist. Die obige Klasse ist jedoch eine Klasse neuen Stils, da sie erbt von object:

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

Was vermisse ich? Wie kann ich super()hier verwenden?

Verwenden HTMLParser.__init__(self)statt super(TextParser, self).__init__()würde funktionieren, aber ich würde gerne den TypeError verstehen.

PS: Joachim wies darauf hin, dass es nicht gleichbedeutend ist, eine Instanz einer Klasse neuen Stils zu sein object. Ich habe das Gegenteil oft gelesen, daher meine Verwirrung (Beispiel für einen neuen Klasseninstanztest basierend auf dem objectInstanztest: https://stackoverflow.com/revisions/2655651/3 ).

Eric O Lebigot
quelle
3
Vielen Dank für Ihre Frage und Antwort. Ich frage mich, warum 2.7's super.__doc__nichts über alten oder neuen Stil erwähnt!
Kelvin
Vielen Dank. :) Docstrings enthalten normalerweise weniger Informationen als die vollständige HTML-Version der Dokumentation. Die Tatsache, dass super()nur für Klassen (und Objekte) neuen Stils funktioniert, wird im HTML-Dokument ( docs.python.org/library/functions.html#super ) erwähnt.
Eric O Lebigot
1
Mögliches Duplikat von Python Super () löst TypeError aus! Warum?
Benutzer
Dies ist kein Duplikat (siehe die aktualisierte Frage und die akzeptierte Antwort).
Eric O Lebigot

Antworten:

246

In Ordnung, es ist das übliche " super()kann nicht mit einer Klasse alten Stils verwendet werden".

Der wichtige Punkt ist jedoch, dass der richtige Test für "Ist dies eine Instanz neuen Stils (dh ein Objekt)?" ist

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

und nicht (wie in der Frage):

>>> isinstance(instance, object)
True

Für Klassen lautet der richtige Test "Ist dies eine Klasse neuen Stils?":

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

Der entscheidende Punkt ist, dass bei Klassen alten Stils die Klasse einer Instanz und ihr Typ unterschiedlich sind. Hier OldStyle().__class__ist OldStyle, was nicht von nicht erben object, während type(OldStyle())die ist instanceArt, die sich aus erben object. Grundsätzlich erstellt eine Klasse im alten Stil nur Objekte vom Typ instance(während eine Klasse im neuen Stil Objekte erstellt, deren Typ die Klasse selbst ist). Dies ist wahrscheinlich der Grund, warum die Instanz OldStyle()eine object: ihre type()erbt von object(die Tatsache, dass ihre Klasse nicht erbt von objectnicht zählt: Klassen alten Stils konstruieren lediglich neue Objekte vom Typ instance). Teilreferenz:https://stackoverflow.com/a/9699961/42973 .

PS: Der Unterschied zwischen einer Klasse neuen Stils und einer Klasse alten Stils kann auch gesehen werden mit:

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

(Klassen alten Stils sind keine Typen, daher können sie nicht der Typ ihrer Instanzen sein.)

Eric O Lebigot
quelle
11
Und dies ist einer der Gründe, warum wir jetzt Python 3 haben.
Steven Rumbalski
2
Übrigens: (Oldstyle().__class__ is Oldstyle)istTrue
Tino
2
@Tino: in der Tat, aber es OldStyle().__class__geht darum zu zeigen, wie man testet, ob ein Objekt ( OldStyle()) aus einer Klasse alten Stils stammt. Wenn man nur Klassen neuen Stils im Auge hat, könnte man versucht sein, isinstance(OldStyle(), object)stattdessen den Test mit zu machen.
Eric O Lebigot
27
Es ist absurd, von wie viel der Python-Standardbibliothek, die noch in 2.7.x enthalten ist, nicht erbt object, was Sie per Proxy verarscht .
Nick Bastin
2
@ NickBastin - das ist kein Zufall. Es ist alles, um alle in Python 3 zu bringen. Wo "schon alles in Ordnung ist". Aber - Vorbehalt Emptor - es ist nur Köder und Schalter.
Tomasz Gandor
204

super () kann nur in Klassen neuen Stils verwendet werden. Dies bedeutet, dass die Stammklasse von der Klasse 'object' erben muss.

Zum Beispiel muss die Spitzenklasse so sein:

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

nicht

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

Die Lösung besteht also darin, die init- Methode des Elternteils wie folgt direkt aufzurufen :

class TextParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.all_data = []
Colin Su
quelle
8
Für mich musste ich folgendes tun: HTMLParser .__ init __ (self) Ich bin gespannt, ob Ihr letztes Beispiel funktioniert hat?
Chaimp
1
@EOL Was heißt? jeffp hat gerade darauf hingewiesen, dass der in dieser Antwort angegebene Code aufgrund fehlender selfParameter im HTMLParser.__init__()Aufruf falsch ist .
Piotr Dobrogost
1
@PiotrDobrogost: Entschuldigung, meine Bemerkung betraf die Antwort von LittleQ, nicht den (guten) Punkt von Jeffp.
Eric O Lebigot
1
@ Jeffp Entschuldigung, es ist ein Tippfehler, ich schreibe es einfach auf SO, habe es aber nicht getestet, meine Schuld. Danke für die Korrektur
Colin Su
1
Upvote für einen Fix, der mit vorhandenem Code funktioniert, wie z. B. Protokollierung. Format in Python2.6
David Reynolds
28

Sie können auch verwenden class TextParser(HTMLParser, object):. Dies macht TextParsereine Klasse neuen Stils und super()kann verwendet werden.

Valentin Lorentz
quelle
Eine Gegenstimme von mir, da das Hinzufügen der Vererbung vom Objekt eine gute Idee ist. (Diese Antwort befasst sich jedoch nicht mit dem Problem des Verständnisses des TypeError der Frage.)
Eric O Lebigot
23

Das Problem ist, dass man als Vorfahr einen superbraucht object:

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

Bei näherer Betrachtung findet man:

>>> type(myclass)
classobj

Aber:

>>> class newstyle(object): pass

>>> type(newstyle)
type    

Die Lösung für Ihr Problem wäre also, sowohl vom Objekt als auch vom HTMLParser zu erben. Stellen Sie jedoch sicher, dass das Objekt in den Klassen MRO an letzter Stelle steht:

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

>>> myclass().os
True
user2070206
quelle
Gültige Punkte, aber sie sind bereits in vorherigen Antworten enthalten. Anstatt zu überprüfen type(myclass), ist es auch wichtig, ob myclassvom Objekt geerbt wird (dh ob isinstance(myclass, object)es wahr ist - es ist falsch).
Eric O Lebigot
17

Wenn Sie sich den Vererbungsbaum (in Version 2.6) ansehen, HTMLParsererbt er, von SGMLParserdem erbt, von ParserBasedem nicht erbtobject . Dh HTMLParser ist eine alte Klasse.

Über Ihre Überprüfung mit habe isinstanceich einen kurzen Test in ipython durchgeführt:

In [1]: Klasse A:
   ...: bestehen
   ...: 

In [2]: isinstance (A, Objekt)
Out [2]: Richtig

Selbst wenn eine Klasse eine Klasse alten Stils ist, ist sie immer noch eine Instanz von object.

Ein Programmierer
quelle
2
Ich glaube, dass der richtige Test sein sollte isinstance(A(), object), nicht isinstance(A, object)nein? Mit letzterem testen Sie , ob die Klasse A eine ist object, während die Frage, ob Instanzen von Asind object, nicht wahr?
Eric O Lebigot
5
PS: Der beste Test scheint zu sein issubclass(HTMLParser, object), der False zurückgibt.
Eric O Lebigot
5

Die richtige Vorgehensweise ist in den Klassen alten Stils, die nicht von 'object' erben, wie folgt.

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

class B(A):
    def foo(self, name):
        return A.foo(self) + name
Jacob Abraham
quelle
1
In der Frage wird diese Methode bereits erwähnt: Das Problem besteht darin, zu verstehen, warum eine Fehlermeldung erzeugt wurde, und sie nicht verschwinden zu lassen (insbesondere auf diese Weise).
Eric O Lebigot
Außerdem funktioniert diese Lösung nicht, wenn wir eine Instanz der Klasse aufrufen möchten A, in der ein Status gespeichert ist.
Tashuhka
0

FWIW und obwohl ich kein Python-Guru bin, bin ich damit durchgekommen

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

Ich habe gerade die Analyseergebnisse nach Bedarf zurückbekommen.

qwerty_so
quelle