super () genera "TypeError: deve essere type, non classobj" per la classe new-style


335

Il seguente uso di super()genera un TypeError: perché?

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

C'è una domanda simile su StackOverflow: Python super () genera TypeError , dove l'errore è spiegato dal fatto che la classe utente non è una nuova classe. Tuttavia, la classe sopra è una nuova classe, poiché eredita da object:

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

Cosa mi sto perdendo? Come posso usare super()qui?

Usando HTMLParser.__init__(self)invece di super(TextParser, self).__init__()avrebbe funzionato, ma vorrei capire TypeError.

PS: Joachim ha sottolineato che essere un'istanza di nuova classe non equivale ad essere un object. Ho letto il contrario molte volte, quindi la mia confusione (esempio di test di istanza di classe di nuovo stile basato sul objecttest di istanza: https://stackoverflow.com/revisions/2655651/3 ).


3
Grazie per la tua domanda e risposta. Mi chiedo perché i 2.7 super.__doc__non menzionino nulla del vecchio e del nuovo stile!
Kelvin,

Grazie. :) In genere, Docstrings contiene meno informazioni rispetto alla versione HTML completa della documentazione. Il fatto che super()funziona solo per le classi (e gli oggetti) di nuovo stile è menzionato nel documento HTML ( docs.python.org/library/functions.html#super ).
Eric O Lebigot,


Questo non è un duplicato (vedi la domanda aggiornata e la risposta accettata).
Eric O Lebigot,

Risposte:


246

Va bene, è il solito " super()non può essere usato con una classe vecchio stile".

Tuttavia, il punto importante è che il test corretto per "è un'istanza di nuovo stile (cioè un oggetto)?" è

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

e non (come nella domanda):

>>> isinstance(instance, object)
True

Per le classi , il test corretto "è questa una nuova classe" è:

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

Il punto cruciale è che con le classi vecchio stile, la classe di un'istanza e il suo tipo sono distinti. Qui, OldStyle().__class__è OldStyle, che non eredita da object, mentre type(OldStyle())è il instancetipo, che non eredita da object. Fondamentalmente, una classe vecchio stile crea oggetti di tipo instance(mentre una nuova classe crea oggetti il ​​cui tipo è la classe stessa). Questo è probabilmente il motivo per cui l'istanza OldStyle()è una object: type()eredita da object(il fatto che la sua classe non erediti objectnon conta: le classi di vecchio stile costruiscono semplicemente nuovi oggetti di tipo instance). Riferimento parziale:https://stackoverflow.com/a/9699961/42973 .

PS: la differenza tra una nuova classe e una vecchia può essere vista anche 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

(le classi vecchio stile non sono tipi, quindi non possono essere il tipo delle loro istanze).


11
E questo è uno dei motivi per cui ora abbiamo Python 3.
Steven Rumbalski il

2
A proposito: (Oldstyle().__class__ is Oldstyle)èTrue
Tino,

2
@Tino: in effetti, ma il punto OldStyle().__class__è mostrare come testare se un oggetto ( OldStyle()) proviene da una classe vecchio stile. Con in mente solo classi di nuovo stile, si potrebbe essere tentati di fare il test isinstance(OldStyle(), object)invece.
Eric O Lebigot

27
È assurdo quanta parte della libreria standard di Python ancora in 2.7.x non eredita object, rovinandoti così per procura.
Nick Bastin,

2
@NickBastin - questa non è una coincidenza. È tutto per spingere tutti in Python 3. Dove "già tutto va bene". Ma - caveat emptor - è solo esca e interruttore.
Tomasz Gandor,

204

super () può essere utilizzato solo nelle classi new-style, il che significa che la classe root deve ereditare dalla classe 'object'.

Ad esempio, la classe superiore deve essere così:

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

non

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

Quindi, la soluzione è quella di chiamare direttamente il metodo init del genitore , in questo modo:

class TextParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.all_data = []

8
Per me, ho dovuto fare questo: HTMLParser .__ init __ (self) Sono curioso di sapere se il tuo ultimo esempio ha funzionato?
Chaimp,

1
@EOL Cosa significa? jeffp ha appena sottolineato che il codice fornito in questa risposta è errato a causa della mancanza del selfparametro in HTMLParser.__init__()call.
Piotr Dobrogost,

1
@PiotrDobrogost: Mi dispiace, la mia osservazione riguardava la risposta di LittleQ, non il punto (buono) di jeffp.
Eric O Lebigot,

1
@jeffp mi dispiace, è un errore di battitura, ho appena scritto su SO ma non l'ho provato, colpa mia. grazie per la correzione
Colin Su

1
Fai l'upgrade per una correzione che funziona con il codice esistente, come la registrazione. Formattazione in python2.6
David Reynolds,

28

Puoi anche usare class TextParser(HTMLParser, object):. Questo rende TextParseruna classe di nuovo stile e super()può essere utilizzato.


Un mio apprezzamento, poiché aggiungere l'eredità dall'oggetto è una buona idea. (Detto questo, questa risposta non affronta il problema della comprensione del TypeError della domanda.)
Eric O Lebigot

23

Il problema è che ha superbisogno objectdi un antenato:

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

A un esame più attento si trova:

>>> type(myclass)
classobj

Ma:

>>> class newstyle(object): pass

>>> type(newstyle)
type    

Quindi la soluzione al tuo problema sarebbe ereditare dall'oggetto e da HTMLParser. Ma assicurati che l'oggetto arrivi per ultimo nelle classi MRO:

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

>>> myclass().os
True

Punti validi, ma sono già nelle risposte precedenti. Inoltre, invece di verificare type(myclass), ciò che conta è se myclassereditare dall'oggetto (cioè se isinstance(myclass, object)è vero, è falso).
Eric O Lebigot,

17

Se guardi l'albero delle eredità (nella versione 2.6), HTMLParser eredita da SGMLParsercui eredita da ParserBasecui non eredita object. Vale a dire HTMLParser è una classe vecchio stile.

A proposito del tuo controllo isinstance, ho fatto un rapido test su ipython:

In [1]: classe A:
   ...: passa
   ...: 

In [2]: isinstance (A, oggetto)
Out [2]: True

Anche se una classe è una classe vecchio stile, è comunque un'istanza di object.


2
Credo che il test corretto dovrebbe essere isinstance(A(), object), no isinstance(A, object), no? Con quest'ultimo, stai verificando se la classe A è an object, mentre la domanda è se le istanze di Asono an object, giusto?
Eric O Lebigot

5
PS: il miglior test sembra essere issubclass(HTMLParser, object), che restituisce False.
Eric O Lebigot,

5

il modo corretto di fare sarà il seguente nelle classi vecchio stile che non ereditano da 'oggetto'

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

class B(A):
    def foo(self, name):
        return A.foo(self) + name

1
Nella domanda, questo metodo è già menzionato: il problema è capire perché è stato prodotto un messaggio di errore, non farlo sparire (in particolare in questo modo).
Eric O Lebigot,

Inoltre, questa soluzione non funzionerà nel caso in cui desideriamo chiamare un'istanza della classe Ache memorizza uno stato.
Tashuhka,

0

FWIW e anche se non sono un guru di Python, ci sono riuscito

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

Mi hanno appena restituito i risultati dell'analisi, se necessario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.