Menu

Herança em Python: subclasses, super(), sobrescrita e a MRO

Como funciona a herança de classes em Python: criar uma subclasse, chamar super().__init__, sobrescrever e estender métodos, isinstance e issubclass, herança múltipla e a MRO, e quando a composição é a melhor escolha.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Uma classe construída sobre outra

A herança permite definir uma classe nova como extensão de uma que já existe. A classe existente é a classe pai (também chamada de classe base ou superclasse), e a nova é a classe filha (subclasse). A filha recebe todos os métodos e atributos de classe do pai sem repeti-los, e pode adicionar os seus ou substituir os que devem se comportar de outro jeito.

Você declara o pai entre parênteses depois do nome da classe:

Saída:

Rex is an animal
Woof

Dog não tem __init__ nem describe próprios. Quando você chama Dog("Rex"), o Python procura __init__ em Dog, não encontra e usa o de Animal. A mesma busca acontece em rex.describe(). Em rex.speak(), ele encontra speak em Dog primeiro e para ali.

Essa ordem de busca é o mecanismo inteiro: o Python procura na classe do próprio objeto, depois no pai, depois no pai do pai, e usa a primeira correspondência. Se você está começando com classes, a página de classes cobre __init__, self e métodos antes.

Sobrescrevendo um método

Um método na filha com o mesmo nome de um método do pai o substitui para as instâncias da filha. Isso se chama sobrescrita (override). Cada subclasse pode responder à mesma chamada do seu jeito:

Saída:

Rectangle with area 12
Square with area 25
Shape with area 0

summary é definido uma vez só, em Shape, mas chama self.area(). Como self é o objeto real, um Rectangle executa Rectangle.area e um Square encontra area no seu pai Rectangle. O código do pai chama o método da filha sem saber que a filha existe. É isso que torna a herança útil para compartilhar um fluxo comum enquanto cada subclasse preenche os detalhes.

Chamando o pai com super()

Quando uma filha define seu próprio __init__, o __init__ do pai deixa de rodar sozinho. Se a filha ainda precisa da configuração do pai, chame-o com super().__init__():

Saída:

Ada 1050.0

super() retorna um objeto que busca métodos a partir do pai da classe atual. Por isso super().__init__(owner, balance) executa Account.__init__ no mesmo objeto, e self.owner e self.balance passam a existir.

Deixe essa linha de fora e o objeto fica pela metade:

Saída:

0.05
AttributeError: 'SavingsAccount' object has no attribute 'owner'

Você pode escrever Account.__init__(self, owner) no lugar, e código mais antigo faz isso com frequência. super() é preferível porque não repete o nome do pai e coopera corretamente com a herança múltipla, que veremos adiante.

Estendendo um método em vez de substituí-lo

super() funciona em qualquer método, não só no __init__. Chamar a versão do pai dentro de uma sobrescrita permite adicionar comportamento em volta dela em vez de reescrevê-la:

Saída:

started
[12:00] started
INFO [12:00] started

Cada nível adiciona uma parte e delega o resto para cima. Se o format do pai mudar depois, as duas filhas recebem a mudança.

isinstance e issubclass

Uma instância de subclasse também é instância de todas as classes acima dela. isinstance(obj, cls) verifica essa relação, e issubclass(child, parent) verifica entre duas classes:

Prefira isinstance a comparar type(obj) diretamente. Uma verificação como type(obj) is Animal rejeita todas as subclasses, o que quebra assim que alguém adiciona Dog.

Toda classe que você escreve herda de object, mesmo quando você não diz isso. class Animal: é o mesmo que class Animal(object):. É daí que vêm métodos padrão como __str__, __repr__ e __eq__, e é por isso que sobrescrever __str__ muda o que print() mostra:

Saída:

(2, 3)
True

Obrigando as subclasses a implementar um método

Às vezes o pai nunca deve ser usado diretamente, só como modelo. O módulo abc marca uma classe assim como abstrata. Todo método decorado com @abstractmethod precisa ser sobrescrito antes que a classe possa ser instanciada:

As três primeiras linhas da saída são a exportação:

exporting 2 rows
1,2
3,4

A última linha é um TypeError que cita Exporter e o método export que falta (o texto exato muda entre versões do Python). O erro aparece quando o objeto é criado, não mais tarde, quando export finalmente é chamado.

Herança múltipla e a MRO

Uma classe pode listar mais de um pai. Nesse caso ela herda de todos:

Saída:

paddling flapping
swims
['Duck', 'Swimmer', 'Flyer', 'object']

Os dois pais definem move, e Duck fica com o de Swimmer. O Python decide pela ordem de resolução de métodos (MRO, method resolution order): uma lista única de classes, guardada em Duck.__mro__, que toda busca de atributo percorre do começo ao fim. Ela começa pela própria classe, mantém os pais na ordem em que você os listou, coloca cada classe antes dos seus próprios pais e termina em object. Troque a ordem para class Duck(Flyer, Swimmer) e move retorna "flies".

super() segue a mesma lista. Dentro de um método de Swimmer, super() significa "a próxima classe depois de Swimmer na MRO da classe do objeto", que para um Duck é Flyer, não object. É assim que classes cooperativas podem cada uma chamar super() e fazer toda classe da cadeia rodar exatamente uma vez, mesmo num losango em que dois pais têm o mesmo avô.

Na prática, a herança múltipla é usada principalmente para mixins: classes pequenas que adicionam uma capacidade, como um JsonMixin que adiciona um método to_json, combinadas com um pai principal. Hierarquias profundas com vários pais completos são difíceis de acompanhar e raramente necessárias.

Herança ou composição?

A herança modela uma relação "é um". Uma SavingsAccount é uma Account: qualquer código que funciona com uma Account deve funcionar com ela sem mudanças. Quando essa frase soa errada, você provavelmente quer composição, que modela "tem um": o objeto guarda outro objeto como atributo e o chama.

Saída:

Roadster: 300 hp engine running

class Car(Engine) também rodaria, mas afirmaria que um carro é um tipo de motor, daria a Car todos os métodos do motor e tornaria impossível trocar o motor sem criar outra classe. Com composição as partes ficam separadas, e trocar o motor é só passar outro objeto.

Alguns sinais de que a herança é a ferramenta errada:

  • A filha sobrescreve a maioria dos métodos do pai, ou desativa alguns deles.
  • Você herda só para reaproveitar um ou dois métodos auxiliares.
  • A hierarquia passa de dois ou três níveis e você precisa ler vários arquivos para entender o que um método faz.

Erros comuns

  • Esquecer super().__init__(). O __init__ da filha substitui o do pai, então os atributos do pai nunca são definidos e você recebe um AttributeError mais tarde.
  • Passar self nas chamadas de super(). Escreva super().__init__(name), não super().__init__(self, name). super() já liga o método ao objeto atual.
  • Assinaturas incompatíveis ao sobrescrever. Se o speak(self) do pai é chamado por código compartilhado, um speak(self, volume) na filha com um argumento extra obrigatório quebra esse código. Mantenha as sobrescritas compatíveis, ou dê valores padrão aos parâmetros novos.
  • Verificar type(obj) == SomeClass. Isso ignora as subclasses. Use isinstance.
  • Usar herança para compartilhar código entre classes sem relação. Mova o código compartilhado para uma função ou um objeto auxiliar.

Perguntas frequentes

Como herdar de uma classe em Python?

Coloque a classe pai entre parênteses depois do nome da nova classe: class Dog(Animal):. Dog recebe todos os métodos e atributos de classe de Animal, e pode adicionar novos ou sobrescrever os existentes.

O que super().__init__() faz?

Chama o __init__ da classe pai para que ela configure os próprios atributos. Quando uma subclasse define seu próprio __init__, o Python não chama o do pai automaticamente, então você chama super().__init__(...) com os argumentos de que o pai precisa e depois define os atributos extras da subclasse.

Qual a diferença entre isinstance e type em Python?

isinstance(obj, Animal) é True para um Animal e para instâncias de qualquer subclasse de Animal. type(obj) is Animal é True só quando o objeto foi criado a partir do próprio Animal. Prefira isinstance, porque continua funcionando quando alguém adiciona uma subclasse.

Python suporta herança múltipla?

Sim: class C(A, B): herda das duas. Quando os dois pais definem o mesmo método, o Python escolhe um seguindo a ordem de resolução de métodos (MRO), que você pode inspecionar com C.__mro__. Ela lista C primeiro, depois os pais da esquerda para a direita, e object por último.

Quando usar composição em vez de herança?

Use herança para uma relação "é um", em que a subclasse pode ser usada em qualquer lugar onde o pai é esperado (uma SavingsAccount é uma Account). Use composição, guardando um objeto como atributo, para uma relação "tem um" (um Car tem um Engine). Na dúvida, a composição é mais fácil de mudar depois.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR