Python Herança
A Ana está montando o cadastro do petshop da vizinha. Todo animal tem nome e come; o cachorro, além disso, busca a bolinha; o gato mia em vez de latir. Escrever três classes do zero, repetindo o nome e o comer em todas, seria trabalho dobrado, e cada correção teria de ser feita três vezes. A herança resolve: você escreve uma classe geral, Animal, e cria classes filhas, Cachorro e Gato, que recebem tudo o que a mãe tem e só acrescentam ou mudam o que é diferente.
- A criar uma classe filha com
class Cachorro(Animal):, e o que ela herda. - A acrescentar métodos na filha e a sobrescrever (trocar) um método da mãe.
- A usar
super()para aproveitar o__init__e os métodos da mãe. - A perguntar "é um?" com
isinstancee "é filha de?" comissubclass. - Como o Python procura um método: primeiro na filha, depois na mãe (
__mro__). - Quando usar herança ("é um") e quando guardar um objeto dentro do outro ("tem um").
- Os erros reais: o
super().__init__esquecido, oselfa mais, a mãe criada depois da filha e o método com nome errado.
A ideia: "é um"
Na ficha do petshop há uma parte que vale para todo animal (nome, idade, vacinas) e uma parte que muda: o cachorro tem raça e porte; o peixe, o tipo de aquário. A herança segue a mesma lógica da frase "é um": um cachorro é um animal, um gato é um animal. Tudo o que vale para animal vale também para cachorro.
| Nome | Também chamada de | No exemplo |
|---|---|---|
| Classe mãe | classe base, superclasse, classe pai | Animal |
| Classe filha | subclasse, classe derivada | Cachorro, Gato |
| Herdar | estender, derivar | Cachorro herda de Animal |
1. A filha recebe tudo
class Animal:
def __init__(self, nome):
self.nome = nome
def comer(self):
print(f"{self.nome} está comendo.")
def falar(self):
print(f"{self.nome} faz um som.")
class Cachorro(Animal):
pass
rex = Cachorro("Rex")
rex.comer()
rex.falar()
print(type(rex))
- class Animal:
- A classe mãe, escrita como qualquer classe.
- class Cachorro(Animal):
- O nome da mãe entre parênteses depois do nome da classe: "o
Cachorroherda deAnimal". São os parênteses que a aula Classes e Objetos prometeu explicar. - pass
- A filha não acrescenta nada (por enquanto). Mesmo assim, ela já tem tudo o que a mãe tem.
- Cachorro("Rex")
- O
Cachorronão escreveu__init__: usa o da mãe, que pede onome.
$ python3 animal.py Rex está comendo. Rex faz um som. <class '__main__.Cachorro'>
Com duas linhas (class Cachorro(Animal): e pass), o cachorro já tem __init__, comer e falar. E o rex é um Cachorro, não um Animal "genérico": o type mostra.
2. Acrescentando: o que só a filha faz
class Animal:
def __init__(self, nome):
self.nome = nome
def comer(self):
print(f"{self.nome} está comendo.")
class Cachorro(Animal):
def buscar(self):
print(f"{self.nome} foi buscar a bolinha.")
rex = Cachorro("Rex")
rex.comer()
rex.buscar()
bicho = Animal("Bicho")
bicho.buscar()
$ python3 novo_metodo.py
Rex está comendo.
Rex foi buscar a bolinha.
Traceback (most recent call last):
File "/home/ana/novo_metodo.py", line 19, in <module>
bicho.buscar()
^^^^^^^^^^^^
AttributeError: 'Animal' object has no attribute 'buscar'
O rex sabe comer (herdou da mãe) e buscar (escrito na filha). Repare que o buscar usa o self.nome, criado pelo __init__ da mãe: tudo se mistura num objeto só. Mas a herança vai num sentido só: da mãe para a filha. Um Animal qualquer não ganhou o buscar, e daí o AttributeError. Nem todo animal busca bolinha.
3. Sobrescrevendo: a filha faz do seu jeito
Se a filha escreve um método com o mesmo nome de um método da mãe, o da filha vale para ela. Isso se chama sobrescrever (em inglês, override, "passar por cima").
class Animal:
def __init__(self, nome):
self.nome = nome
def falar(self):
print(f"{self.nome} faz um som.")
class Cachorro(Animal):
def falar(self):
print(f"{self.nome} diz: Au au!")
class Gato(Animal):
def falar(self):
print(f"{self.nome} diz: Miau!")
class Peixe(Animal):
pass
for bicho in [Cachorro("Rex"), Gato("Mimi"), Peixe("Nemo")]:
bicho.falar()
$ python3 sobrescrever.py Rex diz: Au au! Mimi diz: Miau! Nemo faz um som. « o Peixe não sobrescreveu: usou o da mãe
Como o Python decide qual falar usar? Ele procura o método primeiro na classe do objeto; se não achar, sobe para a mãe. No Cachorro, achou logo. No Peixe, não achou e subiu para o Animal. É a mesma busca da aula Atributos e Métodos (primeiro o objeto, depois a classe), com mais um andar: depois da classe, a mãe.
Missão: o petshop agora atende passarinhos. Crie a classe Passaro, filha de Animal, que sobrescreve o falar para mostrar Piu diz: Piu piu! (use o self.nome).
class Animal:
def __init__(self, nome):
self.nome = nome
def falar(self):
print(f"{self.nome} faz um som.")
# crie aqui a classe Passaro
piu = Passaro("Piu")
piu.falar()
class Animal:
def __init__(self, nome):
self.nome = nome
def falar(self):
print(f"{self.nome} faz um som.")
class Passaro(Animal):
def falar(self):
print(f"{self.nome} diz: Piu piu!")
piu = Passaro("Piu")
piu.falar()
4. super(): aproveitando o __init__ da mãe
O cachorro precisa guardar a raça, além do nome. Então ele precisa do seu próprio __init__. Mas o __init__ da filha sobrescreve o da mãe, e aí ninguém guardaria o nome. A solução é a filha chamar o __init__ da mãe, para ela fazer a parte dela, e depois fazer a sua:
class Animal:
def __init__(self, nome):
self.nome = nome
def falar(self):
print(f"{self.nome} faz um som.")
class Cachorro(Animal):
def __init__(self, nome, raca):
super().__init__(nome)
self.raca = raca
def falar(self):
print(f"{self.nome}, o {self.raca}, diz: Au au!")
rex = Cachorro("Rex", "vira-lata")
print(rex.nome, rex.raca)
rex.falar()
print(vars(rex))
- def __init__(self, nome, raca):
- O
__init__do cachorro recebe tudo o que um cachorro precisa: o nome (que todo animal tem) e a raça (só dele). - super()
- "A classe de cima", a mãe.
super()dá acesso aos métodos da mãe, mesmo os que a filha sobrescreveu. - super().__init__(nome)
- Chama o
__init__doAnimal, que guarda oself.nome. Semselfnos parênteses: osuper()já sabe qual é o objeto. - self.raca = raca
- Depois, a parte que só o cachorro tem.
$ python3 super_init.py
Rex vira-lata
Rex, o vira-lata, diz: Au au!
{'nome': 'Rex', 'raca': 'vira-lata'}
O vars(rex) mostra os dois atributos num objeto só: o nome, guardado pelo __init__ da mãe, e a raca, pelo da filha. O costume é chamar o super().__init__ na primeira linha do __init__ da filha: primeiro a parte geral, depois a específica.
super() em outros métodos
O super() serve para qualquer método, não só o __init__. Use quando a filha quer fazer o mesmo que a mãe e mais um pouco. Uma conta poupança mostra o saldo como qualquer conta, mais uma linha sobre o rendimento:
class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def mostrar(self):
print(f"{self.titular}: R$ {self.saldo:.2f}")
class ContaPoupanca(ContaBancaria):
def __init__(self, titular, saldo=0, taxa=0.01):
super().__init__(titular, saldo)
self.taxa = taxa
def render(self):
self.saldo += self.saldo * self.taxa
def mostrar(self):
super().mostrar()
print(f" (poupança, rende {self.taxa:.0%} ao mês)")
corrente = ContaBancaria("Ana", 500)
poupanca = ContaPoupanca("Bia", 1000)
poupanca.render()
corrente.mostrar()
poupanca.mostrar()
$ python3 conta.py Ana: R$ 500.00 Bia: R$ 1010.00 (poupança, rende 1% ao mês)
O mostrar da poupança chama o super().mostrar() (a linha com o titular e o saldo, escrita uma vez só, na mãe) e acrescenta a sua linha. Se um dia o banco mudar o jeito de mostrar o saldo, a mudança na mãe vale para as duas contas. (O :.0% mostra o 0.01 como porcentagem sem casas: 1%. Veja a Formatação.)
5. "É um?": isinstance e issubclass
class Animal:
pass
class Cachorro(Animal):
pass
class Gato(Animal):
pass
rex = Cachorro()
print(isinstance(rex, Cachorro))
print(isinstance(rex, Animal))
print(isinstance(rex, Gato))
print(type(rex) == Animal)
print(issubclass(Cachorro, Animal))
print(issubclass(Animal, Cachorro))
print(issubclass(Cachorro, object))
print(Cachorro.__mro__)
$ python3 isinst.py True « o rex é um Cachorro True « …e também é um Animal False « mas não é um Gato False « o type() só olha a classe exata True « Cachorro é filha de Animal False « Animal não é filha de Cachorro True « toda classe é filha de object (<class '__main__.Cachorro'>, <class '__main__.Animal'>, <class 'object'>)
- isinstance(rex, Animal)
True: um cachorro é um animal. Oisinstanceaceita a classe do objeto e todas as mães dela. Era isso que a aula Classes e Objetos prometeu: por isso ele é preferido aotype() ==, que respondeFalseaqui.- issubclass(Cachorro, Animal)
- Pergunta sobre as classes, não sobre objetos: "
Cachorroé filha (ou neta…) deAnimal?". - Cachorro.__mro__
- A ordem de busca dos métodos (MRO, de method resolution order, "ordem de resolução de métodos"): primeiro
Cachorro, depoisAnimal, por fimobject, a mãe de todas as classes.
Em Python, uma classe pode ter mais de uma mãe: class Pato(Voador, Nadador):. O pato recebe os métodos das duas.
class Voador:
def voar(self):
print("Voando!")
class Nadador:
def nadar(self):
print("Nadando!")
class Pato(Voador, Nadador):
pass
donald = Pato()
donald.voar()
donald.nadar()
print(Pato.__mro__)
$ python3 multipla.py Voando! Nadando! (<class '__main__.Pato'>, <class '__main__.Voador'>, <class '__main__.Nadador'>, <class 'object'>)
O __mro__ diz a ordem da busca: se as duas mães tiverem um método com o mesmo nome, vale o da que aparece primeiro. A herança múltipla tem regras próprias e é fácil de embaralhar; para quem está começando, uma mãe só basta para quase tudo.
Formas diferentes: herdar ou guardar dentro?
Herança não é o único jeito de aproveitar uma classe. Outro, muito comum, é um objeto guardar outro como atributo. Isso se chama composição, e segue a frase "tem um": a aluna tem um endereço (ela não é um endereço).
class Endereco:
def __init__(self, rua, cidade):
self.rua = rua
self.cidade = cidade
class Aluno:
def __init__(self, nome, endereco):
self.nome = nome
self.endereco = endereco
casa = Endereco("Rua das Flores, 10", "Teresina")
bia = Aluno("Bia", casa)
print(bia.nome, "mora em", bia.endereco.cidade)
$ python3 composicao.py Bia mora em Teresina
O bia.endereco.cidade se lê da esquerda para a direita: o endereco da bia (um objeto Endereco), e a cidade dele.
| Forma | A frase | Quando usar |
|---|---|---|
Herança: class Cachorro(Animal): | "Um cachorro é um animal" | Quando a filha é um tipo mais específico da mãe e serve em todo lugar onde a mãe serve |
Composição: self.endereco = endereco | "A aluna tem um endereço" | Quando um objeto só usa o outro como parte de si. Na dúvida, prefira esta: é mais simples de mudar depois |
| Copiar e colar o código | — | Nunca: cada correção teria de ser feita em todas as cópias |
Uma armadilha clássica: fazer class Aluno(Endereco): só para "ganhar" a rua e a cidade. Funciona, mas a frase fica errada ("um aluno é um endereço?"), e o aluno herda coisas de endereço que não fazem sentido para ele.
Quando dá errado
super().__init__
class Animal:
def __init__(self, nome):
self.nome = nome
class Cachorro(Animal):
def __init__(self, nome, raca):
self.raca = raca
rex = Cachorro("Rex", "vira-lata")
print(rex.raca)
print(rex.nome)
$ python3 sem_super.py
vira-lata
Traceback (most recent call last):
File "/home/ana/sem_super.py", line 13, in <module>
print(rex.nome)
^^^^^^^^
AttributeError: 'Cachorro' object has no attribute 'nome'
O cachorro nasceu sem nome. O __init__ da filha sobrescreveu o da mãe, e o Python não chama o da mãe sozinho: quem não chamou, ficou sem. O nome até chegou como parâmetro, mas ninguém o guardou. Como consertar: comece o __init__ da filha com super().__init__(nome).
self para o super(): takes 2 positional arguments but 3 were given
class Animal:
def __init__(self, nome):
self.nome = nome
class Cachorro(Animal):
def __init__(self, nome, raca):
super().__init__(self, nome)
self.raca = raca
rex = Cachorro("Rex", "vira-lata")
$ python3 super_self.py
Traceback (most recent call last):
File "/home/ana/super_self.py", line 12, in <module>
rex = Cachorro("Rex", "vira-lata")
File "/home/ana/super_self.py", line 8, in __init__
super().__init__(self, nome)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^
TypeError: Animal.__init__() takes 2 positional arguments but 3 were given
Lendo de baixo para cima: o erro foi na linha 8, dentro do __init__ do cachorro, que tinha sido chamado pela linha 12. "O Animal.__init__() recebe 2 argumentos (self e nome), mas ganhou 3". O super() já passa o objeto como self, sozinho, como o rex.falar() passa o rex. O self escrito à mão foi o terceiro. Como consertar: super().__init__(nome).
missing 1 required positional argument
class Animal:
def __init__(self, nome):
self.nome = nome
class Cachorro(Animal):
def __init__(self, nome, raca):
super().__init__()
self.raca = raca
rex = Cachorro("Rex", "vira-lata")
$ python3 super_falta.py
Traceback (most recent call last):
File "/home/ana/super_falta.py", line 12, in <module>
rex = Cachorro("Rex", "vira-lata")
File "/home/ana/super_falta.py", line 8, in __init__
super().__init__()
~~~~~~~~~~~~~~~~^^
TypeError: Animal.__init__() missing 1 required positional argument: 'nome'
O super().__init__ é uma chamada como outra qualquer: precisa dos dados que o __init__ da mãe pede. Como consertar: repasse o nome: super().__init__(nome).
NameError
class Cachorro(Animal):
pass
class Animal:
pass
$ python3 ordem.py
Traceback (most recent call last):
File "/home/ana/ordem.py", line 1, in <module>
class Cachorro(Animal):
^^^^^^
NameError: name 'Animal' is not defined
Na linha 1, o Python precisa da mãe para montar a filha, e o Animal só é criado na linha 5. Os ^^^^^^ apontam exatamente o nome que faltou. O mesmo erro aparece se o nome da mãe estiver digitado errado (Animall). Como consertar: escreva a classe mãe antes das filhas.
class Animal:
def __init__(self, nome):
self.nome = nome
def falar(self):
print(f"{self.nome} faz um som.")
class Cachorro(Animal):
def fala(self):
print(f"{self.nome} diz: Au au!")
rex = Cachorro("Rex")
rex.falar()
$ python3 nome_errado.py Rex faz um som. « era para latir!
Faltou o r: a filha escreveu fala, um método novo, e não sobrescreveu o falar. Para o Python está tudo certo; o rex.falar() achou o da mãe. Como consertar: o método da filha precisa ter exatamente o nome do método da mãe: def falar(self):.
Referência rápida
| Escrita | O que faz |
|---|---|
class Cachorro(Animal): | Cria a classe filha Cachorro, que herda tudo de Animal |
| Método novo na filha | Só os objetos da filha ganham |
| Método com o mesmo nome do da mãe | Sobrescreve: para a filha, vale o dela |
super().__init__(nome) | Chama o __init__ da mãe (sem self) |
super().mostrar() | Chama o mostrar da mãe, de dentro da filha |
isinstance(rex, Animal) | True se o objeto for da classe ou de uma filha dela |
issubclass(Cachorro, Animal) | True se a classe for filha (ou neta…) da outra |
Cachorro.__mro__ | A ordem em que o Python procura os métodos |
class Pato(Voador, Nadador): | Herança múltipla: duas mães |
| Sintoma | Causa provável |
|---|---|
AttributeError num atributo que a mãe deveria criar | A filha tem __init__ e não chamou o super().__init__(…) |
takes 2 positional arguments but 3 were given no super() | self a mais: super().__init__(self, nome) |
missing 1 required positional argument no super() | Faltou repassar um dado para a mãe |
NameError: name 'Animal' is not defined no class | A mãe foi escrita depois da filha (ou o nome está errado) |
| A filha continua fazendo o que a mãe faz | O método da filha tem um nome diferente do da mãe |
AttributeError num objeto da mãe | O método só existe na filha: a herança vai da mãe para a filha, não ao contrário |
Exercícios
1. A classe filha
Complete para a classe Gato herdar de Animal:
2. Chamando a mãe
Complete a primeira linha do __init__ do Gato, que repassa o nome para o __init__ do Animal:
3. Peixe herda de Animal e não escreve falar. O que acontece em Peixe("Nemo").falar()?
4. Com class Cachorro(Animal) e rex = Cachorro("Rex"), quanto vale isinstance(rex, Animal)?
5. Qual destas relações pede composição (um objeto guardado dentro do outro), e não herança?
6. O __init__ da filha começa com super().__init__(self, nome) e dá TypeError. Por quê?
Missão final: a conta poupança
Missão: complete a ContaPoupanca, filha de ContaBancaria. (1) No __init__, chame o __init__ da mãe com super() (passando o titular e o saldo) e guarde a taxa. (2) Escreva o método render(self), que soma ao saldo o saldo vezes a taxa. A Bia tem 1000 reais a 1% ao mês: depois de render, o programa mostra Bia: R$ 1010.00.
class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def mostrar(self):
print(f"{self.titular}: R$ {self.saldo:.2f}")
class ContaPoupanca(ContaBancaria):
def __init__(self, titular, saldo=0, taxa=0.01):
self.taxa = taxa
# escreva aqui o método render
poupanca = ContaPoupanca("Bia", 1000)
poupanca.render()
poupanca.mostrar()
class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def mostrar(self):
print(f"{self.titular}: R$ {self.saldo:.2f}")
class ContaPoupanca(ContaBancaria):
def __init__(self, titular, saldo=0, taxa=0.01):
super().__init__(titular, saldo)
self.taxa = taxa
def render(self):
self.saldo += self.saldo * self.taxa
poupanca = ContaPoupanca("Bia", 1000)
poupanca.render()
poupanca.mostrar()
Se aparecer 'ContaPoupanca' object has no attribute 'saldo', o super().__init__ ainda não está lá. Repare que você não escreveu mostrar na poupança: ela herdou o da mãe.
Resumo
class Filha(Mae):cria uma classe que herda tudo da mãe:__init__, métodos, atributos de classe. Use quando a frase "a filha é uma mãe" fizer sentido.- A filha pode acrescentar métodos (só ela ganha) e sobrescrever métodos da mãe, com o mesmo nome exato.
- O Python procura um método primeiro na classe do objeto, depois na mãe, até
object: é o__mro__. - Se a filha tem
__init__, ela precisa chamarsuper().__init__(…), semself, para a mãe fazer a parte dela. super().metodo()aproveita o método da mãe para "fazer o mesmo e mais um pouco".isinstance(obj, Mae)éTruetambém para objetos das filhas;issubclass(Filha, Mae)pergunta sobre as classes.- "Tem um" é composição (
self.endereco = …), não herança. Herança múltipla existe, mas uma mãe basta para quase tudo.