Herança em Python
Aprenda herança em Python: subclasses, substituição de métodos, super(), herança múltipla, MRO e verificações com isinstance — com exemplos executáveis.
Compreendendo a Herança em Python
Herança permite construir uma nova classe a partir de uma já existente. A nova classe (a subclasse ou classe filha) recebe automaticamente todos os atributos e métodos da classe existente (a superclasse, classe base ou classe pai). Em seguida, você pode adicionar comportamentos extras ou substituir seletivamente o que foi herdado.
Este capítulo aborda:
- A sintaxe para criar subclasses
- Como
super()funciona e por que você deve usá-lo - Substituição de métodos e chamada da versão do pai
- Herança simples, multinível e múltipla
- A Ordem de Resolução de Métodos (MRO) do Python
isinstance()eissubclass()para verificações de tipo em tempo de execução- Erros comuns
Antes de ler este capítulo, certifique-se de que está familiarizado com classes e objetos em Python. Para outros pilares de POO relacionados, consulte Encapsulamento em Python e Classes Abstratas em Python.
A Sintaxe da Herança
Para criar uma subclasse, escreva o nome da classe pai dentro de parênteses após o nome da nova classe:
class ParentClass:
pass
class ChildClass(ParentClass):
passUm exemplo mínimo, mas concreto, com a classe base Vehicle e a subclasse Car:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print(f"{self.make} {self.model} started.")
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year) # delegate to Vehicle.__init__
self.num_doors = num_doors
camry = Car("Toyota", "Camry", 2023)
camry.start() # Toyota Camry started.
camry.stop() # Toyota Camry stopped.
print(camry.num_doors) # 4Car herda start() e stop() de Vehicle sem duplicá-los. Adicionar num_doors é a única nova responsabilidade que Car precisa tratar.
Usando super()
super() retorna um objeto proxy que delega chamadas de métodos para a classe pai. O uso mais comum é dentro de __init__ para inicializar os atributos do pai antes de adicionar os do filho:
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year) # runs Vehicle.__init__
self.num_doors = num_doorsPor que preferir super() em vez de escrever Vehicle.__init__(self, ...) diretamente?
- Manutenção: se você renomear ou substituir a classe pai, só precisa alterar uma linha.
- Herança múltipla:
super()segue a Ordem de Resolução de Métodos (MRO) do Python, garantindo que cada classe na cadeia seja chamada corretamente. Codificar o nome do pai diretamente ignora essa lógica.
Você pode chamar super() para qualquer método, não apenas __init__:
class Car(Vehicle):
def start(self):
super().start() # call Vehicle.start first
print(f"({self.num_doors}-door model ready)")Substituição de Métodos
Uma subclasse substitui um método do pai definindo um método com o mesmo nome. Python sempre chama primeiro a versão mais derivada:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print(f"{self.make} {self.model} started.")
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def start(self):
print(f"{self.make} {self.model} revved the engine and started.")
camry = Car("Toyota", "Camry", 2023)
camry.start() # Toyota Camry revved the engine and started.
camry.stop() # Toyota Camry stopped. (inherited, not overridden)Quando você deseja estender em vez de substituir o comportamento do pai, chame super() dentro da substituição:
class Car(Vehicle):
def start(self):
super().start() # Vehicle.start runs first
print("Seatbelt reminder: buckle up!") # then add the extra stepHerdando Atributos de Classe
A herança também se aplica a atributos de classe (compartilhados entre instâncias). Uma subclasse pode substituí-los da mesma forma que substitui métodos:
class Vehicle:
wheels = 4
class Motorcycle(Vehicle):
wheels = 2 # override the class attribute
class Car(Vehicle):
pass # inherits wheels = 4
print(Motorcycle.wheels) # 2
print(Car.wheels) # 4
print(Vehicle.wheels) # 4Tipos de Herança
Herança Simples
Um filho, um pai — o padrão mais comum.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
class Dog(Animal):
def speak(self):
return f"{self.name} says woof!"
dog = Dog("Rex")
print(dog.speak()) # Rex says woof!Herança Multinível
Uma subclasse pode, por sua vez, ser subclassificada, criando uma cadeia:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year)
self.num_doors = num_doors
def start(self):
print(f"{self.make} {self.model} started.")
class ElectricCar(Car):
def __init__(self, make, model, year, range_km):
super().__init__(make, model, year) # calls Car.__init__
self.range_km = range_km
def start(self):
print(f"{self.make} {self.model} powered up silently.")
def charge(self):
print(f"Charging... range is {self.range_km} km.")
tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
tesla.start() # Tesla Model 3 powered up silently.
tesla.charge() # Charging... range is 580 km.
tesla.stop() # Tesla Model 3 stopped. (inherited from Vehicle)ElectricCar está dois níveis abaixo de Vehicle. Cada chamada a super().__init__ sobe um nível na cadeia, fazendo com que todos os três métodos __init__ sejam executados.
Herança Múltipla
Python permite que uma classe herde de mais de um pai. Liste-os entre parênteses, separados por vírgulas:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
class Swimmer:
def swim(self):
return f"{self.name} swims."
class Flyer:
def fly(self):
return f"{self.name} flies."
class Duck(Animal, Swimmer, Flyer):
def speak(self):
return f"{self.name} says quack!"
donald = Duck("Donald")
print(donald.speak()) # Donald says quack!
print(donald.swim()) # Donald swims.
print(donald.fly()) # Donald flies.A herança múltipla é poderosa, mas introduz complexidade. Use mixins — classes pequenas com propósito único que adicionam uma única capacidade — para mantê-la gerenciável. Swimmer e Flyer acima são exatamente esse padrão.
A Ordem de Resolução de Métodos (MRO)
Quando Python procura um método ou atributo, segue uma ordem de busca determinística chamada Ordem de Resolução de Métodos (MRO). Para herança simples, a ordem é óbvia (filho → pai → avô → object). Para herança múltipla, Python usa o algoritmo de linearização C3 para produzir uma ordem inequívoca.
Inspecione o MRO de qualquer classe com .__mro__ ou help():
print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Swimmer'>, <class 'Flyer'>, <class 'object'>)O MRO importa mais quando vários pais definem o mesmo método. Python escolhe a primeira classe no MRO que o define. É por isso que super() é importante em hierarquias de herança múltipla: cada classe no MRO chama super(), dando a todas as classes na cadeia a oportunidade de executar.
class Base:
def greet(self):
print("Hello from Base")
class Left(Base):
def greet(self):
print("Hello from Left")
super().greet()
class Right(Base):
def greet(self):
print("Hello from Right")
super().greet()
class Child(Left, Right):
def greet(self):
print("Hello from Child")
super().greet()
Child().greet()
# Hello from Child
# Hello from Left
# Hello from Right
# Hello from BaseCada chamada a super() segue o MRO (Child → Left → Right → Base), portanto cada greet() é executado exatamente uma vez, mesmo que Base apareça como pai tanto de Left quanto de Right. Isso é o problema do diamante — o MRO do Python o resolve de forma elegante.
Verificando a Herança em Tempo de Execução
isinstance(obj, cls)
Retorna True se obj for uma instância de cls ou de qualquer uma de suas subclasses:
tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
print(isinstance(tesla, ElectricCar)) # True
print(isinstance(tesla, Car)) # True — Car is a parent
print(isinstance(tesla, Vehicle)) # True — Vehicle is a grandparent
print(isinstance(tesla, str)) # FalseIsso é mais confiável do que comparar type(obj) == Car, que retorna False para subclasses.
issubclass(sub, cls)
Retorna True se sub for uma subclasse de cls (inclusive ela mesma):
print(issubclass(ElectricCar, Vehicle)) # True
print(issubclass(Car, ElectricCar)) # False
print(issubclass(Car, Car)) # True — a class is a subclass of itselfErros Comuns
Esquecer de chamar super().__init__()
Se o __init__ do filho não chamar super().__init__(), os atributos do pai nunca serão definidos. Qualquer método que os espere lançará AttributeError:
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
# forgot super().__init__(...)
self.num_doors = num_doors
c = Car("Toyota", "Camry", 2023)
c.start() # AttributeError: 'Car' object has no attribute 'make'Substituir sem chamar super() quando a intenção era estender
Se você substituir um método e esquecer super(), a versão do pai nunca será executada. Isso silenciosamente descarta comportamentos dos quais outros trechos de código podem depender.
Hierarquias profundas de herança múltipla
Mais de dois níveis de herança múltipla é muito difícil de raciocinar. Se você se encontrar escrevendo class Foo(A, B, C, D), considere usar composição — armazenando instâncias como atributos — em vez disso.
Benefícios da Herança
- Reutilização de código — a lógica compartilhada vive em um único lugar; as subclasses a herdam gratuitamente.
- Extensibilidade — você pode adicionar ou alterar comportamentos em uma subclasse sem modificar o pai, mantendo os chamadores existentes inalterados.
- Polimorfismo — funções que aceitam um
Vehiclefuncionam igualmente bem com qualquerCar,MotorcycleouElectricCar. Consulte Polimorfismo em Python para o quadro completo.