super () gera "TypeError: deve ser do tipo, não classobj" para a classe de novo estilo

335

O seguinte uso de super()gera um TypeError: 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

Há uma pergunta semelhante no StackOverflow: Python super () gera TypeError , onde o erro é explicado pelo fato de a classe de usuário não ser uma classe de novo estilo. No entanto, a classe acima é uma classe de novo estilo, pois herda de object:

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

o que estou perdendo? Como posso usar super()aqui?

Usar em HTMLParser.__init__(self)vez de super(TextParser, self).__init__()funcionaria, mas eu gostaria de entender o TypeError.

PS: Joachim apontou que ser uma instância de nova classe de estilo não é equivalente a ser uma object. Eu li o contrário várias vezes, daí a minha confusão (exemplo de teste de instância de classe de novo estilo com base no objectteste de instância: https://stackoverflow.com/revisions/2655651/3 ).

Eric O Lebigot
fonte
3
Obrigado pela sua pergunta e resposta. Eu me pergunto por que o 2.7 super.__doc__não menciona nada sobre o antigo e o novo estilo!
Kelvin
Obrigado. :) Os documentos geralmente contêm menos informações que a versão HTML completa da documentação. O fato de super()funcionar apenas para classes (e objetos) de novo estilo é mencionado no documento HTML ( docs.python.org/library/functions.html#super ).
Eric O Lebigot
11
possível duplicata do python super () gera TypeError! Por quê?
usuário
Isso não é duplicado (consulte a pergunta atualizada e a resposta aceita).
Eric O Lebigot

Respostas:

246

Tudo bem, é o habitual " super()não pode ser usado com uma classe de estilo antigo".

No entanto, o ponto importante é que o teste correto para "esta é uma instância de novo estilo (ou seja, objeto)?" é

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

e não (como na pergunta):

>>> isinstance(instance, object)
True

Para as classes , o teste "é uma classe de novo estilo" correto é:

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

O ponto crucial é que, com classes de estilo antigo, a classe de uma instância e seu tipo são distintos. Aqui, OldStyle().__class__é OldStyle, que não herda de object, ao mesmo tempo type(OldStyle())é o instancetipo, que não herdam object. Basicamente, uma classe de estilo antigo apenas cria objetos do tipo instance(enquanto uma classe de estilo novo cria objetos cujo tipo é a própria classe). Provavelmente é por isso que a instância OldStyle()é uma object: type()herda de object(o fato de sua classe não herdar de objectnão conta: classes de estilo antigo apenas constroem novos objetos do tipo instance). Referência parcial:https://stackoverflow.com/a/9699961/42973 .

PS: A diferença entre uma classe de estilo novo e uma classe de estilo antigo também pode ser vista com:

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

(as classes de estilo antigo não são tipos, portanto, não podem ser o tipo de suas instâncias).

Eric O Lebigot
fonte
11
E esta é uma das razões pelas quais agora temos o Python 3. #
Steven Rumbalski 15/03
2
BTW: (Oldstyle().__class__ is Oldstyle)éTrue
Tino
2
@ Tino: na verdade, mas o objetivo OldStyle().__class__é mostrar como testar se um objeto ( OldStyle()) vem de uma classe de estilo antigo. Com apenas aulas de estilo novo em mente, pode-se tentar fazer o teste isinstance(OldStyle(), object).
Eric O Lebigot 03/03
27
É absurdo o quanto da biblioteca padrão python que ainda está no 2.7.x não herda object, fazendo com que você seja procurado por proxy.
Nick # # # # # Bastin #
2
@ NickBastin - isso não é uma coincidência. É tudo para empurrar todos para o Python 3. Onde "já está tudo bem". Mas - ressalva - é apenas isca e interruptor.
Tomasz Gandor
204

super () pode ser usado apenas nas classes de novo estilo, o que significa que a classe raiz precisa herdar da classe 'objeto'.

Por exemplo, a classe superior precisa ser assim:

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

não

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

Portanto, a solução é chamar o método init do pai diretamente, assim:

class TextParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.all_data = []
Colin Su
fonte
8
Para mim, eu tive que fazer o seguinte: HTMLParser .__ init __ (self) Estou curioso para saber se o seu último exemplo funcionou?
chaimp
11
@EOL O que significa? jeffp apenas apontou que o código fornecido nesta resposta está errado devido à falta de selfparâmetro na HTMLParser.__init__()chamada.
Piotr Dobrogost
11
@PiotrDobrogost: Desculpe, minha observação foi sobre a resposta da LittleQ, não sobre o ponto (bom) de jeffp.
Eric O Lebigot
11
@jeffp desculpe, é um erro de digitação, eu apenas digito no SO, mas não testei, minha culpa. obrigado por corrigir
Colin Su
11
Upvote para uma correção que funciona com o código existente, como logging.Formatter no python2.6
David Reynolds
28

Você também pode usar class TextParser(HTMLParser, object):. Isso cria TextParseruma classe de novo estilo e super()pode ser usado.

Valentin Lorentz
fonte
Um voto positivo de mim, pois adicionar a herança do objeto é uma boa ideia. (Dito isto, esta resposta não aborda a questão da compreensão do TypeError de questão.)
Eric O Lebigot
23

O problema é que superprecisa de objectum ancestral:

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

Em um exame mais detalhado, encontra-se:

>>> type(myclass)
classobj

Mas:

>>> class newstyle(object): pass

>>> type(newstyle)
type    

Portanto, a solução para o seu problema seria herdar do objeto e do HTMLParser. Mas verifique se o objeto vem por último nas classes MRO:

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

>>> myclass().os
True
user2070206
fonte
Pontos válidos, mas eles já estão nas respostas anteriores. Além disso, em vez de verificar type(myclass), o que importa é se myclassherdar do objeto (ou seja, se isinstance(myclass, object)é verdadeiro - é falso).
Eric O Lebigot
17

Se você olhar para a árvore de herança (na versão 2.6), HTMLParserherda da SGMLParserqual herda da ParserBasequal não herda object. Ou seja, HTMLParser é uma classe de estilo antigo.

Sobre a sua verificação isinstance, fiz um teste rápido no ipython:

Em [1]: classe A:
   ...: pass
   ...: 

Em [2]: isinstance (A, objeto)
Fora [2]: Verdadeiro

Mesmo que uma classe seja de estilo antigo, ainda é uma instância de object.

Algum cara programador
fonte
2
Eu acredito que o teste correto deve ser isinstance(A(), object), não isinstance(A, object), não? Com o último, você está testando se a classe A é uma object, enquanto a questão é se as instâncias de Asão uma object, certo?
Eric O Lebigot
5
PS: o melhor teste parece ser issubclass(HTMLParser, object), que retorna False.
Eric O Lebigot
5

a maneira correta de fazer será a seguinte nas classes de estilo antigo que não herdam de 'objeto'

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

class B(A):
    def foo(self, name):
        return A.foo(self) + name
Jacob Abraham
fonte
11
Na questão, esse método já foi mencionado: o problema é entender por que uma mensagem de erro foi produzida, e não fazê-la desaparecer (em particular dessa maneira).
Eric O Lebigot
Além disso, essa solução não funcionará no caso de querermos chamar uma instância da classe Aque armazena um estado.
precisa saber é
0

FWIW e, embora eu não seja um guru de Python, convivi com isso

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

Apenas recebi os resultados da análise, conforme necessário.

qwerty_so
fonte