Herança em Python Orientado a Objetos: subclasses, super() e especialização segura

Capítulo 6

Tempo estimado de leitura: 7 minutos

+ Exercício

O que é herança e quando faz sentido usar

Herança é um mecanismo de reutilização e especialização: você cria uma subclasse a partir de uma superclasse para reaproveitar comportamento e, quando necessário, especializar (alterar ou estender) partes desse comportamento.

Use herança quando a relação for do tipo “é um” (is-a). Ex.: um Desenvolvedor é um Funcionario. Se a relação for mais “tem um” (has-a), normalmente composição é mais adequada (ex.: um Funcionario tem um Endereco).

Boas práticas rápidas

  • Prefira hierarquias rasas: 1 ou 2 níveis costumam ser suficientes.
  • Evite “classes Deus” (superclasses gigantes) e subclasses que só existem para mudar detalhes mínimos.
  • Garanta que a subclasse respeite o contrato da superclasse (mesma intenção, entradas/saídas compatíveis, invariantes preservadas).

Criando subclasses e reutilizando comportamento

Em Python, você cria uma subclasse colocando a superclasse entre parênteses:

class Funcionario: ...

class Desenvolvedor(Funcionario): ...

A subclasse herda métodos e atributos públicos da superclasse. Você pode:

  • Usar como está (reutilização direta).
  • Sobrescrever (override) um método para mudar o comportamento.
  • Estender um método, chamando a versão da superclasse e adicionando algo.

super(): chamando o construtor e métodos da superclasse

super() é a forma recomendada de acessar a implementação da superclasse, especialmente para:

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

  • Chamar o construtor da superclasse dentro do __init__ da subclasse.
  • Estender métodos sobrescritos sem duplicar lógica.

Regra prática: se a superclasse faz validações e inicializações importantes, a subclasse deve chamar super().__init__(...) para manter o objeto consistente.

Mini-projeto progressivo: Funcionario → Desenvolvedor / Gerente

Neste mini-projeto, vamos modelar uma base Funcionario e duas especializações: Desenvolvedor e Gerente. O foco será: criação de subclasses, uso de super(), sobrescrita e extensão de comportamento, além de validações para manter o contrato.

Passo 1 — Superclasse Funcionario com contrato claro

Definiremos um contrato simples:

  • Todo funcionário tem nome não vazio.
  • Todo funcionário tem salario_base maior que zero.
  • Todo funcionário sabe calcular seu pagamento_mensal().
class Funcionario:
    def __init__(self, nome: str, salario_base: float):
        nome = (nome or "").strip()
        if not nome:
            raise ValueError("nome deve ser preenchido")
        if salario_base <= 0:
            raise ValueError("salario_base deve ser > 0")

        self.nome = nome
        self.salario_base = float(salario_base)

    def pagamento_mensal(self) -> float:
        """Contrato: retorna um valor numérico > 0."""
        return self.salario_base

    def resumo(self) -> str:
        return f"{self.nome} - pagamento: R$ {self.pagamento_mensal():.2f}"

Note que pagamento_mensal() define um contrato: deve retornar um número positivo. As subclasses podem mudar como o pagamento é calculado, mas devem respeitar esse contrato.

Passo 2 — Subclasse Desenvolvedor: especialização com validação extra

Um desenvolvedor tem um nível (ex.: junior, pleno, senior) que influencia um bônus percentual. Aqui veremos:

  • Uso de super().__init__ para reaproveitar validações e inicialização.
  • Sobrescrita de método para especializar o cálculo.
class Desenvolvedor(Funcionario):
    BONUS_POR_NIVEL = {
        "junior": 0.05,
        "pleno": 0.10,
        "senior": 0.20,
    }

    def __init__(self, nome: str, salario_base: float, nivel: str):
        super().__init__(nome, salario_base)

        nivel = (nivel or "").strip().lower()
        if nivel not in self.BONUS_POR_NIVEL:
            raise ValueError(f"nivel inválido: {nivel}")

        self.nivel = nivel

    def pagamento_mensal(self) -> float:
        bonus = self.BONUS_POR_NIVEL[self.nivel]
        return self.salario_base * (1 + bonus)

Repare que a subclasse não reimplementa validação de nome e salario_base: ela delega isso à superclasse com super(). Isso reduz duplicação e mantém o contrato consistente.

Passo 3 — Subclasse Gerente: extensão de comportamento e invariantes

Um gerente terá um bônus fixo mensal e um limite de equipe. Aqui veremos:

  • Validações específicas da subclasse.
  • Extensão do comportamento: usar a base e somar algo.
class Gerente(Funcionario):
    def __init__(self, nome: str, salario_base: float, bonus_mensal: float, tamanho_equipe: int = 0):
        super().__init__(nome, salario_base)

        if bonus_mensal < 0:
            raise ValueError("bonus_mensal deve ser >= 0")
        if tamanho_equipe < 0:
            raise ValueError("tamanho_equipe deve ser >= 0")

        self.bonus_mensal = float(bonus_mensal)
        self.tamanho_equipe = int(tamanho_equipe)

    def pagamento_mensal(self) -> float:
        # Extensão: mantém a base e adiciona o bônus
        return super().pagamento_mensal() + self.bonus_mensal

Observe a diferença entre sobrescrever “do zero” e estender: aqui, super().pagamento_mensal() deixa explícito que o gerente parte do pagamento base do funcionário e adiciona um bônus.

Passo 4 — Polimorfismo na prática: tratar todos como Funcionario

Uma vantagem direta da herança é poder trabalhar com uma lista de Funcionario sem se preocupar com o tipo concreto. Cada objeto calcula seu pagamento conforme sua especialização.

equipe = [
    Funcionario("Ana", 3000),
    Desenvolvedor("Bruno", 5000, "pleno"),
    Gerente("Carla", 8000, bonus_mensal=2000, tamanho_equipe=6),
]

folha = sum(f.pagamento_mensal() for f in equipe)

for f in equipe:
    print(f.resumo())

print(f"Folha total: R$ {folha:.2f}")

Sobrescrita com segurança: respeitando o contrato da superclasse

Quando você sobrescreve um método, a subclasse deve continuar “fazendo sentido” onde a superclasse era esperada. Na prática, isso significa:

  • Não enfraquecer pré-condições de forma incompatível (ex.: exigir parâmetros mais restritos sem necessidade).
  • Não quebrar pós-condições (ex.: o método prometia retornar um número positivo e a subclasse retorna 0 ou negativo).
  • Manter invariantes (ex.: se salario_base deve ser > 0, a subclasse não deve permitir que vire 0/negativo por algum método).

Exemplo de sobrescrita problemática (o que evitar)

Imagine que alguém implemente um estagiário com pagamento zerado “para teste”. Isso quebra o contrato de pagamento_mensal() (retorno > 0) e pode causar bugs em cálculos de folha.

class Estagiario(Funcionario):
    def pagamento_mensal(self) -> float:
        return 0  # quebra o contrato

Se estagiário não recebe salário, talvez ele nem devesse ser um Funcionario nesse modelo; ou então o contrato da superclasse deveria ser redefinido para comportar esse caso (o que afetaria todo o sistema).

Evite hierarquias profundas: sinais de alerta e alternativas

Hierarquias profundas tendem a ficar difíceis de entender e manter, porque:

  • O comportamento real de um método pode depender de vários níveis de sobrescrita.
  • Pequenas mudanças na superclasse podem quebrar subclasses distantes.
  • Fica mais difícil testar combinações de regras.

Sinais de que a herança está passando do ponto

  • Você tem classes como FuncionarioFuncionarioCLTFuncionarioCLTPlenoFuncionarioCLTPlenoRemoto
  • Subclasses existem apenas para mudar um número/flag.
  • Muitos if isinstance(...) aparecem para “consertar” diferenças entre subclasses.

Como manter a especialização segura sem aprofundar demais

  • Use herança para o núcleo comum e mantenha as variações em poucos níveis.
  • Quando a variação for “plugável” (ex.: regras de bônus), considere extrair para um componente separado (composição) em vez de criar novas subclasses para cada regra.
  • Prefira métodos pequenos e bem nomeados para facilitar extensão via super().

Checklist prático ao criar uma subclasse

PerguntaObjetivo
Isso é realmente um “é um”?Evitar herança onde composição seria melhor
Preciso chamar super().__init__?Garantir validações e inicialização do estado base
Vou sobrescrever ou estender?Escolher entre substituir lógica ou reaproveitar e adicionar
Estou respeitando o contrato?Manter entradas/saídas e invariantes compatíveis
Minha hierarquia está ficando profunda?Evitar complexidade e acoplamento excessivos

Agora responda o exercício sobre o conteúdo:

Ao criar uma subclasse em Python, em que situação é recomendado chamar super().__init__(...) no __init__ da subclasse?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Chamar super().__init__(...) reaproveita validações e inicializações essenciais da superclasse (como nome e salario_base), evitando duplicação e garantindo consistência do objeto e respeito ao contrato.

Próximo capitúlo

Polimorfismo em Python Orientado a Objetos: interfaces de fato e duck typing

Arrow Right Icon
Capa do Ebook gratuito Python Orientado a Objetos para Iniciantes: Classes, Herança e Boas Práticas
43%

Python Orientado a Objetos para Iniciantes: Classes, Herança e Boas Práticas

Novo curso

14 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.