---
title: "Herança em Python: O que É e Como Funciona | Python Brasil"
url: "https://python.dev.br/glossario/heranca/"
markdown_url: "https://python.dev.br/glossario/heranca.MD"
description: "Entenda herança em Python: MRO, C3 linearization, super(), mixins, classes abstratas e composição vs herança. Exemplos práticos completos."
date: "2025-10-05"
author: ""
---

# Herança em Python: O que É e Como Funciona | Python Brasil

Entenda herança em Python: MRO, C3 linearization, super(), mixins, classes abstratas e composição vs herança. Exemplos práticos completos.


## O que é Herança?

**Herança** é um dos pilares da Programação Orientada a Objetos que permite que uma classe **herde atributos e métodos** de outra classe. A classe que herda é chamada de **classe filha** (ou subclasse), e a classe da qual se herda é a **classe pai** (ou superclasse).

Isso promove a **reutilização de código** e estabelece relações hierárquicas entre classes. Herança deve modelar uma relação clara de "é um": um `Cachorro` **é um** `Animal`. Se a relação for "tem um", prefira composição.

## Herança simples

```python
class Animal:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def falar(self):
        raise NotImplementedError("Subclasses devem implementar este método")

    def info(self):
        return f"{self.nome}, {self.idade} anos"

class Cachorro(Animal):
    def __init__(self, nome, idade, raca):
        super().__init__(nome, idade)  # Chama o construtor da classe pai
        self.raca = raca

    def falar(self):
        return "Au au!"

    def info(self):
        base = super().info()  # Reutiliza o método da classe pai
        return f"{base}, raça: {self.raca}"

class Gato(Animal):
    def falar(self):
        return "Miau!"

rex = Cachorro("Rex", 5, "Labrador")
mimi = Gato("Mimi", 3)

print(rex.info())              # Rex, 5 anos, raça: Labrador
print(rex.falar())             # Au au!
print(mimi.falar())            # Miau!
print(isinstance(rex, Animal)) # True
```

## MRO: Ordem de Resolução de Métodos

Quando Python precisa encontrar um atributo ou método, ele segue a **Method Resolution Order (MRO)**. Em herança simples isso é direto, mas em herança múltipla o Python usa o algoritmo **C3 Linearization** para determinar a ordem de forma consistente e previsível.

```python
class A:
    def metodo(self):
        return "A"

class B(A):
    def metodo(self):
        return f"B -> {super().metodo()}"

class C(A):
    def metodo(self):
        return f"C -> {super().metodo()}"

class D(B, C):
    def metodo(self):
        return f"D -> {super().metodo()}"

print(D.__mro__)
# D -> B -> C -> A -> object

print(D().metodo())
# D -> B -> C -> A
```

O C3 Linearization garante que: a classe filha sempre aparece antes das classes pai; a ordem das classes pai é preservada; nenhuma classe aparece antes de suas dependências. Você pode inspecionar o MRO de qualquer classe com `Classe.__mro__` ou `Classe.mro()`.

## super() em profundidade

`super()` não retorna "a classe pai" — ele retorna um proxy que segue o **MRO** da classe corrente. Isso é fundamental em herança múltipla:

```python
class Logavel:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        print(f"Logavel inicializado")

class Serializavel:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        print(f"Serializavel inicializado")

class Base:
    def __init__(self, nome, **kwargs):
        super().__init__(**kwargs)
        self.nome = nome
        print(f"Base inicializado com nome={nome}")

class Entidade(Logavel, Serializavel, Base):
    def __init__(self, nome, **kwargs):
        super().__init__(nome=nome, **kwargs)
        print("Entidade inicializada")

e = Entidade("Produto")
# Base inicializado com nome=Produto
# Serializavel inicializado
# Logavel inicializado
# Entidade inicializada
```

Note o uso de `**kwargs` para que cada classe no MRO passe os argumentos restantes adiante sem quebrar a cadeia de chamadas.

## Herança múltipla e o padrão Mixin

O padrão **Mixin** é uma das formas mais elegantes de usar herança múltipla. Um mixin é uma classe que fornece um conjunto específico de funcionalidades, mas que não é destinada a ser instanciada sozinha.

```python
class JsonMixin:
    """Mixin que adiciona serialização JSON a qualquer classe."""
    def to_json(self):
        import json
        return json.dumps(self.__dict__, ensure_ascii=False, default=str)

    @classmethod
    def from_json(cls, json_str):
        import json
        dados = json.loads(json_str)
        return cls(**dados)

class LogMixin:
    """Mixin que adiciona logging automático a métodos."""
    def log(self, mensagem):
        import logging
        logging.info(f"[{self.__class__.__name__}] {mensagem}")

class ValidacaoMixin:
    """Mixin que adiciona validação de campos obrigatórios."""
    campos_obrigatorios = []

    def validar(self):
        for campo in self.campos_obrigatorios:
            if not getattr(self, campo, None):
                raise ValueError(f"Campo obrigatório ausente: {campo}")
        return True

class Usuario(JsonMixin, LogMixin, ValidacaoMixin):
    campos_obrigatorios = ['nome', 'email']

    def __init__(self, nome, email, idade=None):
        self.nome = nome
        self.email = email
        self.idade = idade

u = Usuario("Ana", "ana@email.com", 28)
print(u.to_json())   # {"nome": "Ana", "email": "ana@email.com", "idade": 28}
u.validar()          # True
u.log("Usuário criado com sucesso")
```

## Classes abstratas como contratos

O módulo `abc` permite criar classes que definem uma **interface obrigatória** para suas subclasses:

```python
from abc import ABC, abstractmethod

class Repositorio(ABC):
    """Define o contrato que todos os repositórios devem cumprir."""

    @abstractmethod
    def buscar_por_id(self, id: int):
        pass

    @abstractmethod
    def salvar(self, entidade) -> None:
        pass

    @abstractmethod
    def deletar(self, id: int) -> None:
        pass

    def buscar_todos(self):  # Método concreto — opcional sobrescrever
        return []

class RepositorioEmMemoria(Repositorio):
    def __init__(self):
        self._dados = {}

    def buscar_por_id(self, id):
        return self._dados.get(id)

    def salvar(self, entidade):
        self._dados[entidade['id']] = entidade

    def deletar(self, id):
        self._dados.pop(id, None)

# Repositorio()  # TypeError: não pode instanciar classe abstrata
repo = RepositorioEmMemoria()
repo.salvar({'id': 1, 'nome': 'Produto A'})
print(repo.buscar_por_id(1))  # {'id': 1, 'nome': 'Produto A'}
```

## Composição vs herança

A herança muitas vezes é usada em excesso. Antes de criar uma hierarquia de herança, pergunte-se: a relação é realmente "é um"?

```python
# Herança: "Motor É um Carro?" — Não faz sentido!
class Carro(Motor):  # Errado
    pass

# Composição: "Carro TEM um Motor" — Correto
class Motor:
    def __init__(self, potencia):
        self.potencia = potencia

    def ligar(self):
        return f"Motor de {self.potencia}cv ligado"

class Carro:
    def __init__(self, modelo, potencia):
        self.modelo = modelo
        self.motor = Motor(potencia)  # Composição

    def ligar(self):
        return f"{self.modelo}: {self.motor.ligar()}"

c = Carro("Fusca", 65)
print(c.ligar())  # Fusca: Motor de 65cv ligado
```

A composição favorece o **Princípio da Responsabilidade Única** e torna o código mais flexível para mudanças futuras. Você pode trocar o motor sem alterar a classe `Carro`.

## Princípio de Substituição de Liskov (LSP)

O LSP afirma que objetos de uma subclasse devem poder substituir objetos da superclasse sem quebrar o programa. Violações comuns ocorrem quando a subclasse **restringe** o comportamento esperado:

```python
# Violação do LSP — Quadrado "quebrando" o contrato de Retangulo
class Retangulo:
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura = altura

    def area(self):
        return self.largura * self.altura

class Quadrado(Retangulo):  # Problema: quadrado viola o contrato
    def __init__(self, lado):
        super().__init__(lado, lado)

    @Retangulo.largura.setter
    def largura(self, valor):
        self._largura = valor
        self._altura = valor  # Força lado igual — quebra o contrato!

# Solução LSP: ambos herdam de uma abstração comum
class Forma(ABC):
    @abstractmethod
    def area(self): pass

class Retangulo(Forma):
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura = altura
    def area(self):
        return self.largura * self.altura

class Quadrado(Forma):
    def __init__(self, lado):
        self.lado = lado
    def area(self):
        return self.lado ** 2
```

## Exemplo prático: refatorando com herança

```python
# Antes: código duplicado em cada classe de notificação
class NotificacaoEmail:
    def enviar(self, destinatario, mensagem):
        print(f"[LOG] Enviando email para {destinatario}")
        # lógica de email...
        print(f"[LOG] Email enviado")

class NotificacaoSMS:
    def enviar(self, destinatario, mensagem):
        print(f"[LOG] Enviando SMS para {destinatario}")
        # lógica de SMS...
        print(f"[LOG] SMS enviado")

# Depois: herança elimina duplicação
class Notificacao(ABC):
    def enviar(self, destinatario, mensagem):
        print(f"[LOG] Enviando {self.__class__.__name__} para {destinatario}")
        self._executar_envio(destinatario, mensagem)
        print(f"[LOG] {self.__class__.__name__} enviado")

    @abstractmethod
    def _executar_envio(self, destinatario, mensagem):
        pass

class NotificacaoEmail(Notificacao):
    def _executar_envio(self, destinatario, mensagem):
        print(f"Email: {mensagem} -> {destinatario}")

class NotificacaoSMS(Notificacao):
    def _executar_envio(self, destinatario, mensagem):
        print(f"SMS: {mensagem} -> {destinatario}")
```

## Erros comuns

Chamar `super()` sem argumentos funciona apenas dentro de métodos de classe. Em Python 2 era necessário passar a classe e `self` explicitamente — em Python 3 isso é desnecessário. Outro erro comum é não usar `super().__init__()` na subclasse quando a superclasse possui inicialização, o que resulta em atributos ausentes.

## Quando usar herança?

Use herança quando: a subclasse **é um tipo** da superclasse; você quer **reutilizar e estender** o comportamento; as classes compartilham uma **interface comum obrigatória**. Evite herança apenas para reutilização de código — para isso, prefira composição ou mixins bem delimitados.

## Termos Relacionados

- [Classe](/glossario/classe/) - Base da programação orientada a objetos
- [Polimorfismo](/glossario/polimorfismo/) - Múltiplas formas para mesma interface
- [Encapsulamento](/glossario/encapsulamento/) - Proteção de dados internos
