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

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, super
e 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.
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 substituirParentName.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.