super () podnosi „TypeError: must be type, not classobj” dla klasy nowego stylu

335

Następujące użycie super()podnosi typ błędu: dlaczego?

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

Podobne pytanie dotyczy StackOverflow: Python super () podnosi TypeError , gdzie błąd jest wyjaśniony przez fakt, że klasa użytkownika nie jest klasą nowego stylu. Jednak powyższa klasa jest klasą w nowym stylu, ponieważ dziedziczy po object:

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

czego mi brakuje? Jak mogę użyć super()tutaj?

Używanie HTMLParser.__init__(self)zamiast super(TextParser, self).__init__()byłoby działało, ale chciałbym zrozumieć błąd TypeError.

PS: Joachim wskazał, że bycie instancją klasy nowego stylu nie jest równoznaczne z byciem an object. Wiele razy czytam przeciwieństwo, stąd moje zamieszanie (przykład testu instancji klasy w nowym stylu na podstawie objecttestu instancji: https://stackoverflow.com/revisions/2655651/3 ).

Eric O Lebigot
źródło
3
Dziękuję za twoje pytanie i odpowiedź. Zastanawiam się, dlaczego 2.7 super.__doc__nie wspomina o starym i nowym stylu!
Kelvin
Dzięki. :) Dokumenty zazwyczaj zawierają mniej informacji niż pełna wersja HTML dokumentacji. Fakt, że super()działa tylko w przypadku klas (i obiektów) w nowym stylu, jest wspomniany w dokumencie HTML ( docs.python.org/library/functions.html#super ).
Eric O Lebigot,
1
możliwy duplikat Python super () podnosi błąd typu! Dlaczego?
użytkownik
To nie jest duplikat (patrz zaktualizowane pytanie i zaakceptowana odpowiedź).
Eric O Lebigot

Odpowiedzi:

246

W porządku, to zwykłe „ super()nie można używać z klasą w starym stylu”.

Ważną kwestią jest jednak to, czy poprawny test „czy jest to instancja w nowym stylu (tj. Obiekt)?” jest

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

a nie (jak w pytaniu):

>>> isinstance(instance, object)
True

W przypadku klas poprawny test „czy to klasa nowego stylu” to:

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

Kluczowym punktem jest to, że z zajęć w starym stylu, klasy z instancji i jego rodzaj są różne. Tutaj OldStyle().__class__jest OldStyle, co nie dziedziczą object, podczas gdy type(OldStyle())jest to instancetyp, który ma dziedziczyć object. Zasadniczo klasa w starym stylu po prostu tworzy obiekty typu instance(podczas gdy klasa w nowym stylu tworzy obiekty, których typem jest sama klasa). Prawdopodobnie dlatego instancja OldStyle()jest następująca object: type()dziedziczy po object( nie liczy się to, że jej klasa nie dziedziczy object: klasy w starym stylu jedynie konstruują nowe obiekty typu instance). Częściowe odniesienie:https://stackoverflow.com/a/9699961/42973 .

PS: Różnicę między klasą w nowym stylu a klasą w starym stylu można również zobaczyć w:

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

(klasy w starym stylu nie są typami, więc nie mogą być typem swoich instancji).

Eric O Lebigot
źródło
11
I to jest jeden z powodów, dla których mamy teraz Python 3.
Steven Rumbalski
2
BTW: (Oldstyle().__class__ is Oldstyle)jestTrue
Tino
2
@Tino: rzeczywiście, ale chodzi o OldStyle().__class__to, aby pokazać, jak sprawdzić, czy obiekt ( OldStyle()) pochodzi z klasy w starym stylu. Mając na uwadze wyłącznie zajęcia w nowym stylu, można pokusić się o wykonanie testu isinstance(OldStyle(), object).
Eric O Lebigot,
27
To niedorzeczne, ile z biblioteki standardowej Pythona jeszcze w 2.7.x nie dziedziczą object, więc przykręcenie cię przez pełnomocnika.
Nick Bastin
2
@NickBastin - to nie przypadek. Wszystko po to, aby zachęcić wszystkich do Python 3. Gdzie „już wszystko jest w porządku”. Ale - zastrzeżenie emptor - to tylko przynęta i zmiana.
Tomasz Gandor,
204

super () może być używane tylko w klasach w nowym stylu, co oznacza, że ​​klasa główna musi dziedziczyć po klasie „object”.

Na przykład najwyższa klasa musi wyglądać tak:

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

nie

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

Tak więc rozwiązaniem jest bezpośrednie wywołanie metody init rodzica , w następujący sposób:

class TextParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.all_data = []
Colin Su
źródło
8
Dla mnie musiałem to zrobić: HTMLParser .__ init __ (self) Jestem ciekawy, czy twój ostatni przykład zadziałał?
chaimp
1
@EOL Co oznaczają? jeffp właśnie wskazał, że kod podany w tej odpowiedzi jest niepoprawny z powodu braku selfparametru w HTMLParser.__init__()wywołaniu.
Piotr Dobrogost
1
@PiotrDobrogost: Przepraszam, moja uwaga dotyczyła odpowiedzi LittleQ, a nie punktu (dobrego) Jeffa.
Eric O Lebigot,
1
@ jeffp przepraszam, to literówka, po prostu wpisuję to na SO, ale nie testuję, moja wina. dzięki za sprostowanie
Colin Su,
1
Poproś o poprawkę, która działa z istniejącym kodem, takim jak rejestrowanie. Formatowanie w python2.6
David Reynolds
28

Możesz także użyć class TextParser(HTMLParser, object):. To sprawia, że TextParserdo nowego stylu klasy i super()mogą być użyte.

Valentin Lorentz
źródło
Pozytywne zdanie, ponieważ dodanie dziedziczenia po obiekcie jest dobrym pomysłem. (To powiedziawszy, ta odpowiedź nie rozwiązuje problemu zrozumienia błędu typu pytania).
Eric O Lebigot
23

Problem w tym, że superpotrzebuje objectprzodka:

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

Przy bliższym zbadaniu stwierdza się:

>>> type(myclass)
classobj

Ale:

>>> class newstyle(object): pass

>>> type(newstyle)
type    

Rozwiązaniem Twojego problemu byłoby dziedziczenie po obiektach, a także po HTMLParser. Ale upewnij się, że obiekt jest na końcu klasy MRO:

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

>>> myclass().os
True
user2070206
źródło
Ważne punkty, ale są już w poprzednich odpowiedziach. Ponadto zamiast sprawdzania type(myclass), ważne jest to, czy myclassdziedziczymy po obiekcie (tj. Czy isinstance(myclass, object)jest to prawda - jest to fałsz).
Eric O Lebigot,
17

Jeśli spojrzysz na drzewo dziedziczenia (w wersji 2.6), HTMLParserdziedziczy, z SGMLParserktórego dziedziczy, z ParserBasektórego nie dziedziczyobject . Tj. HTMLParser jest klasą w starym stylu.

Jeśli chodzi o sprawdzanie isinstance, zrobiłem szybki test w ipython:

W [1]: klasa A:
   ...: przechodzić
   ...: 

W [2]: isinstance (A, object)
Out [2]: Prawda

Nawet jeśli klasa jest klasą w starym stylu, nadal jest instancją klasy object.

Jakiś programista, koleś
źródło
2
Uważam, że poprawny test powinien brzmieć isinstance(A(), object)nie isinstance(A, object), nie? Z tym ostatnim, testujesz, czy klasa A jest object, natomiast pytanie, czy przypadki z Aobject, prawda?
Eric O Lebigot
5
PS: wydaje się issubclass(HTMLParser, object), że najlepszym testem jest Fałsz.
Eric O Lebigot
5

poprawny sposób postępowania będzie taki, jak w klasach w starym stylu, który nie dziedziczy po „obiekcie”

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

class B(A):
    def foo(self, name):
        return A.foo(self) + name
Jakub Abraham
źródło
1
W pytaniu wspomniano już o tej metodzie: problem polega na zrozumieniu, dlaczego został wygenerowany komunikat o błędzie, a nie na zniknięciu (w szczególności w ten sposób).
Eric O Lebigot,
Poza tym to rozwiązanie nie będzie działać w przypadku, gdy chcemy wywołać instancję klasy, Aktóra przechowuje stan.
tashuhka
0

FWIW i chociaż nie jestem guru Pythona, poradziłem sobie z tym

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

Właśnie dostałem wyniki analizy w razie potrzeby.

qwerty_so
źródło