Pular para o conteúdo principal

Introdução à herança múltipla e super()

Um guia introdutório para Pythonistas sobre herança múltipla, a função super() e como lidar com o problema do diamante.
Actualizado 16 de jan. de 2025  · 8 min de leitura

Visão geral rápida da herança

À medida que você desenvolve seus projetos e pacotes Python, inevitavelmente desejará utilizar classes e aplicar o princípio DRY (don't-repeat-yourself, não repita a si mesmo) ao fazer isso. A herança de classe é uma maneira fantástica de criar uma classe com base em outra classe para que você mantenha a DRY. Esta publicação abordará conceitos mais avançados de herança, e a herança básica não será abordada em profundidade. Faremos uma introdução rápida, mas há introduções muito melhores e detalhadas por aí. Aqui estão alguns recursos para você começar: Curso de programação orientada a objetos em Python & Programação orientada a objetos (OOP) em Python: Tutorial.

Então, o que é herança de classe? Da mesma forma que a genética, uma classe filha pode "herdar" atributos e métodos de uma classe mãe. Vamos direto para o código de um exemplo. No bloco de código abaixo, demonstraremos a herança com uma classe Child herdando de uma classe Parent.

Entrada

class Parent:
    def __init__(self):
        self.parent_attribute = 'I am a parent'

    def parent_method(self):
        print('Back in my day...')


# Create a child class that inherits from Parent
class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
        self.child_attribute = 'I am a child'


# Create instance of child
child = Child()

# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()

Saída

I am a child
I am a parent
Back in my day...

Você verá que a classe Child "herdou" atributos e métodos da classe Parent. Sem nenhum trabalho de nossa parte, o Parent.parent_method faz parte da classe Child. Para obter os benefícios do método Parent.__init__(), precisamos chamar explicitamente o método e passar self. Isso ocorre porque, quando adicionamos um método __init__ a Child, substituímos o método herdado __init__.

Com essa breve visão geral, não muito abrangente, vamos passar para a essência da postagem.

Introdução a super

No caso mais simples, a função super pode ser usada para substituir a chamada explícita para Parent.__init__(self). Nosso exemplo de introdução da primeira seção pode ser reescrito com super, conforme mostrado abaixo. Observe que o bloco de código abaixo foi escrito em Python 3; as versões anteriores usam uma sintaxe ligeiramente diferente. Além disso, a saída foi omitida, pois é idêntica ao primeiro bloco de código.

class Parent:
    def __init__(self):
        self.parent_attribute = 'I am a parent'

    def parent_method(self):
        print('Back in my day...')


# Create a child class that inherits from Parent
class Child(Parent):
    def __init__(self):
        super().__init__()
        self.child_attribute = 'I am a parent'


# Create instance of child
child = Child()

# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()

Para ser honesto, super nesse caso nos dá pouca ou nenhuma vantagem. Dependendo do nome da nossa classe principal, podemos economizar alguns toques no teclado e não precisamos passar self para a chamada para __init__. Abaixo estão alguns prós e contras do uso do site super em casos de herança única.

Contras

Você pode argumentar que o uso do super aqui torna o código menos explícito. Tornar o código menos explícito viola o Zen do Python, que diz: "Explícito é melhor do que implícito".

Prós

Há um argumento de manutenção que pode ser apresentado para super mesmo em herança única. Se, por qualquer motivo, a classe filha mudar o padrão de herança (ou seja, a classe pai mudar ou houver uma mudança para herança múltipla), você não precisará encontrar e substituir todas as referências remanescentes a ParentClass.method_name(); o uso de super permitirá que todas as alterações fluam com a mudança na declaração class.

Domine suas habilidades em dados com o DataCamp

Mais de 10 milhões de pessoas aprendem Python, R, SQL e outras habilidades tecnológicas usando nossos cursos práticos elaborados por especialistas do setor.

Comece a aprender
learner-on-couch@2x.jpg

super e herança múltipla

Antes de falarmos sobre herança múltipla e super... Atenção, isso pode se tornar bastante estranho e complicado.

Em primeiro lugar, o que é herança múltipla? Até agora, o código de exemplo abordou uma única classe filha herdando de uma única classe pai. Na herança múltipla, há mais de uma classe principal. Uma classe filha pode herdar de 2, 3, 10, etc. classes pai.

É aqui que os benefícios do super se tornam mais claros. Além de economizar o pressionamento de teclas ao fazer referência aos diferentes nomes de classes pai, há benefícios diferenciados no uso do site super com vários padrões de herança. Em resumo, se você for usar herança múltipla, use super.

Herança múltipla sem super

Vamos dar uma olhada em um exemplo de herança múltipla que evita modificar qualquer método pai e, por sua vez, evita super.

Entrada

class B:
    def b(self):
        print('b')


class C:
    def c(self):
        print('c')


class D(B, C):
    def d(self):
        print('d')


d = D()
d.b()
d.c()
d.d()

Saída

b
c
d

Ordem de resolução múltipla

Esse resultado não é muito surpreendente, dado o conceito de herança múltipla. D herdou os métodos x e z de suas classes pai, e tudo está bem no mundo... por enquanto.

E se os sites B e C tivessem um método com o mesmo nome? É nesse ponto que entra em ação um conceito chamado "ordem de resolução múltipla", ou MRO, para abreviar. O MRO de uma classe filha é o que decide onde o Python procurará um determinado método e qual método será chamado quando houver um conflito.

Vejamos um exemplo.

Entrada

class B:
    def x(self):
        print('x: B')


class C:
    def x(self):
        print('x: C')


class D(B, C):
    pass


d = D()
d.x()
print(D.mro())

Saída

x: B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]

Quando chamamos o método herdado x, vemos apenas a saída herdada de B. Podemos ver o MRO da nossa classe D chamando o método da classe mro. Com a saída do D.mro(), aprendemos o seguinte: nosso programa tentará chamar os métodos do D por padrão, depois recorrerá ao B, depois ao C e, por fim, ao object. Se ele não for encontrado em nenhum desses lugares, receberemos o erro de que D não tem o método que solicitamos.

Vale a pena observar que, por padrão, todas as classes herdam de object, e isso está no final de cada MRO.

Herança múltipla, supere o problema do diamante

Abaixo você encontra um exemplo de uso do super para lidar com o MRO do init de uma forma benéfica. No exemplo, criamos uma série de classes de processamento de texto e combinamos suas funcionalidades em outra classe com herança múltipla. Criaremos 4 classes, e a estrutura de herança seguirá a estrutura do diagrama abaixo.

Observação: Essa estrutura é para fins ilustrativos e, salvo restrições, haveria maneiras melhores de implementar essa lógica.

problema do diamante

Na verdade, esse é um exemplo do "problema do diamante" da herança múltipla. Seu nome é, obviamente, baseado na forma de seu design e no fato de ser um problema bastante confuso.

Abaixo, o design está escrito com o uso do site super.

Entrada

class Tokenizer:
    """Tokenize text"""
    def __init__(self, text):
        print('Start Tokenizer.__init__()')
        self.tokens = text.split()
        print('End Tokenizer.__init__()')


class WordCounter(Tokenizer):
    """Count words in text"""
    def __init__(self, text):
        print('Start WordCounter.__init__()')
        super().__init__(text)
        self.word_count = len(self.tokens)
        print('End WordCounter.__init__()')


class Vocabulary(Tokenizer):
    """Find unique words in text"""
    def __init__(self, text):
        print('Start init Vocabulary.__init__()')
        super().__init__(text)
        self.vocab = set(self.tokens)
        print('End init Vocabulary.__init__()')


class TextDescriber(WordCounter, Vocabulary):
    """Describe text with multiple metrics"""
    def __init__(self, text):
        print('Start init TextDescriber.__init__()')
        super().__init__(text)
        print('End init TextDescriber.__init__()')


td = TextDescriber('row row row your boat')
print('--------')
print(td.tokens)
print(td.vocab)
print(td.word_count)

Saída

Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
--------
['row', 'row', 'row', 'your', 'boat']
{'boat', 'your', 'row'}
5

Em primeiro lugar, vemos que a classe TextDescriber herdou todos os atributos da árvore genealógica da classe. Graças à herança múltipla, podemos "combinar" a funcionalidade de mais de uma classe.

Vamos agora discutir as impressões provenientes dos métodos init da classe:

Cada um __init__ foi chamado uma e somente uma vez.

A classe TextDescriber herdou de 2 classes que herdam de Tokenizer. Por que o Tokenizer.__init__ não foi chamado duas vezes?

Se substituíssemos todas as nossas chamadas para super pela maneira antiga, acabaríamos com 2 chamadas para Tokenizer.__init__. As chamadas para super "pensam" um pouco mais em nosso padrão e evitam a viagem extra para A.

Cada um __init__ foi iniciado antes que qualquer um dos outros fosse concluído.

Vale a pena observar a ordem do início e do fim de cada __init__ caso você esteja tentando definir um atributo que tenha um conflito de nomes com outra classe principal. O atributo será substituído, e isso pode se tornar muito confuso.

No nosso caso, evitamos conflitos de nomes com atributos herdados, portanto, tudo está funcionando como esperado.

Para reiterar, o problema do diamante pode se complicar rapidamente e levar a resultados inesperados. Na maioria dos casos de programação, é melhor evitar projetos complicados.

O que aprendemos

  • Aprendemos sobre a função super e como ela pode ser usada para substituir ParentName.method em uma única herança. Essa pode ser uma prática mais sustentável.
  • Aprendemos sobre herança múltipla e como podemos passar a funcionalidade de várias classes pai para uma única classe filha.
  • Aprendemos sobre a ordem de resolução múltipla e como ela decide o que acontece na herança múltipla quando há um conflito de nomes entre os métodos pai.
  • Aprendemos sobre o problema do diamante e vimos um exemplo de como o uso do super navega no diamante.
Temas

Saiba mais sobre Python

Certificação disponível

curso

Introdução ao Python

4 hr
6M
Domine os fundamentos da análise de dados com Python em apenas quatro horas. Neste curso on-line, você conhecerá a interface Python e explorará os pacotes populares.
Ver DetalhesRight Arrow
Iniciar curso
Ver maisRight Arrow
Relacionado

tutorial

Operadores em Python

Este tutorial aborda os diferentes tipos de operadores em Python, sobrecarga de operadores, precedência e associatividade.
Théo Vanderheyden's photo

Théo Vanderheyden

9 min

tutorial

Programação orientada a objetos em Python (OOP): Tutorial

Aborde os fundamentos da programação orientada a objetos (OOP) em Python: explore classes, objetos, métodos de instância, atributos e muito mais!
Théo Vanderheyden's photo

Théo Vanderheyden

12 min

tutorial

Tutorial de funções Python

Um tutorial sobre funções em Python que aborda como escrever funções, como chamá-las e muito mais!
Karlijn Willems's photo

Karlijn Willems

14 min

tutorial

Escopo das variáveis em Python

Saiba o que são as miras telescópicas variáveis e familiarize-se com a regra "LEGB". Você também lidará com cenários em que verá as palavras-chave globais e não locais em ação.
Sejal Jaiswal's photo

Sejal Jaiswal

9 min

tutorial

Tutorial de iteradores e geradores Python

Explore a diferença entre Iteradores e Geradores do Python e saiba quais são os melhores para usar em várias situações.
Kurtis Pykes 's photo

Kurtis Pykes

10 min

tutorial

Tutorial de indexação de lista Python()

Neste tutorial, você aprenderá exclusivamente sobre a função index().
Sejal Jaiswal's photo

Sejal Jaiswal

6 min

Ver maisVer mais