Trilha de aprendizado · Nível 7 · Tutorial 5

Especializar classes com herança e super

Criar especializações por herança simples, sobrescrever métodos e estender comportamentos com super, escolhendo herança apenas quando a relação entre os tipos a justificar.

  • Nível: Intermediário
  • Duração: 24 min
  • 7 passos
Especializar classes com herança e super

O que você vai percorrer

  1. Criar uma especialização por herança simples Defina uma subclasse de Tarefa e reconheça os comportamentos que ela recebe da classe base sem copiar implementações. 4 min
  2. Sobrescrever um método Defina na subclasse um método de mesmo nome para substituir o comportamento herdado. 3 min
  3. Estender um comportamento com super Use super() para aproveitar a descrição produzida pela classe base e acrescentar uma informação específica da subclasse, sem duplicar a lógica comum. 3 min
  4. Inicializar a subclasse com super().__init__ Adicione a prioridade à subclasse sem perder a inicialização e a validação realizadas por Tarefa. 4 min
  5. Preservar o que a classe base garante Diagnostique inicializadores e sobrescritas que quebram regras, retornos ou operações já oferecidas pela classe base. 3 min
  6. Escolher entre herança e composição Use o significado dos tipos e suas responsabilidades para decidir entre especialização por herança e colaboração por composição. 2 min
  7. Aplicar e revisar a especialização completa Implemente TarefaPrioritaria em um script único, execute verificações manuais e compare sua implementação com uma solução que preserva as regras da classe base. 5 min

O que você vai aprender

  • Definir uma subclasse que aproveita comportamentos da classe base.
  • Sobrescrever um método para alterar ou estender seu comportamento.
  • Usar super para aproveitar a inicialização ou um método herdado.
  • Justificar a escolha entre composição e herança em uma situação de modelagem.

Antes de começar

  • Criar objetos com classes e métodos de instância
  • Preservar regras de validade em objetos
  • Compor objetos com responsabilidades distintas

Passo 1 de 7

Criar uma especialização por herança simples

Defina uma subclasse de Tarefa e reconheça os comportamentos que ela recebe da classe base sem copiar implementações.

Uma tarefa mais específica

Especialização entre tipos

Uma TarefaPrioritaria continua sendo uma Tarefa, mas representa um caso mais específico. Essa relação justifica usar herança: a subclasse pode aproveitar o estado e os comportamentos definidos pela classe base.

A herança não deve ser escolhida apenas porque duas classes têm código parecido. A pergunta principal é: o novo tipo realmente é uma especialização do tipo existente? Neste caso, sim: toda tarefa prioritária ainda pode ser descrita e concluída como uma tarefa.

Relação entre as classes

A seta representa a relação de especialização. Os comportamentos definidos em Tarefa ficam disponíveis em TarefaPrioritaria.

Diagrama em que TarefaPrioritaria aparece como especialização de Tarefa e recebe os comportamentos de inicializar, descrever e concluir.

TarefaPrioritaria é um tipo mais específico de Tarefa e aproveita seus comportamentos.

Definir e usar a subclasse

A classe base entre parênteses

Na definição da subclasse, o nome entre parênteses indica a classe base: class TarefaPrioritaria(Tarefa):.

Como a subclasse ainda não precisa acrescentar nada, usamos pass. Mesmo com o corpo vazio, ela pode usar __init__, descrever e concluir sem que essas implementações sejam copiadas para seu corpo.

Herança simples em Python

Execute este script no seu computador e observe que a instância da subclasse usa os métodos definidos em Tarefa.

python
class Tarefa:
    def __init__(self, titulo):
        if not titulo.strip():
            raise ValueError("O título não pode ficar vazio.")

        self.titulo = titulo
        self.concluida = False

    def concluir(self):
        self.concluida = True

    def descrever(self):
        estado = "concluída" if self.concluida else "pendente"
        return f"{self.titulo} — {estado}"


class TarefaPrioritaria(Tarefa):
    pass


prioritaria = TarefaPrioritaria("Corrigir falha crítica")

print(prioritaria.descrever())
prioritaria.concluir()
print(prioritaria.descrever())

# Saída esperada:
# Corrigir falha crítica — pendente
# Corrigir falha crítica — concluída

Dica

O inicializador também é herdado

Como TarefaPrioritaria não define um __init__, a criação do objeto usa o inicializador disponível em Tarefa. Por isso, titulo e concluida são inicializados e a validação de título não vazio continua valendo.

Como a implementação é encontrada

Busca simplificada neste exemplo

Quando um método é solicitado em uma instância de TarefaPrioritaria, a busca começa na subclasse. Como ela não define os métodos do exemplo, a busca segue para Tarefa.

Assim, a subclasse disponibiliza os comportamentos herdados sem duplicar suas implementações.

Associe cada uso à sua origem

Relacione cada elemento à explicação correta.

Toque em um item e depois no par correspondente.

Passo 2 de 7

Sobrescrever um método

Defina na subclasse um método de mesmo nome para substituir o comportamento herdado.

Mesmo nome, nova implementação

O que é sobrescrita?

Uma subclasse sobrescreve um método quando define um método com o mesmo nome daquele existente na classe base. Ao chamar descrever() em uma instância de TarefaPrioritaria, Python encontra primeiro a implementação da subclasse e usa somente ela.

Uma descrição própria para a subclasse

A subclasse herda a inicialização e os demais métodos, mas fornece outra implementação de descrever.

python
class Tarefa:
    def __init__(self, titulo):
        self.titulo = titulo
        self.concluida = False

    def concluir(self):
        self.concluida = True

    def descrever(self):
        status = "concluída" if self.concluida else "pendente"
        return f"{self.titulo} [{status}]"


class TarefaPrioritaria(Tarefa):
    def descrever(self):
        return f"[PRIORITÁRIA] {self.titulo}"

Qual implementação é escolhida?

Diagrama com uma tarefa comum ligada ao método da classe base e uma tarefa prioritária ligada diretamente ao método definido na subclasse.

Cada instância usa a primeira implementação de descrever encontrada a partir de sua própria classe.

Resultados permanecem separados

A classe base não é alterada

A sobrescrita vale para instâncias da subclasse. Uma instância criada diretamente como Tarefa continua usando o método original. Além disso, chamar o método sobrescrito não executa automaticamente a implementação da classe base.

Compare as duas chamadas

Considerando as classes da tela anterior, observe que cada chamada produz apenas um texto.

python
comum = Tarefa("Revisar relatório")
urgente = TarefaPrioritaria("Corrigir falha crítica")

print(comum.descrever())
print(urgente.descrever())

# Saída:
# Revisar relatório [pendente]
# [PRIORITÁRIA] Corrigir falha crítica

Complete a sobrescrita

Nome do método

Complete o nome que faz a subclasse substituir a descrição herdada:

class TarefaPrioritaria(Tarefa):
def ____ (self):
return f"[PRIORITÁRIA] {self.titulo}"

Preveja as chamadas

Qual será a saída?

Com as classes apresentadas, qual saída será produzida?

print(Tarefa("Enviar proposta").descrever())
print(TarefaPrioritaria("Responder cliente").descrever())

Passo 3 de 7

Estender um comportamento com super

Use super() para aproveitar a descrição produzida pela classe base e acrescentar uma informação específica da subclasse, sem duplicar a lógica comum.

Substituir ou estender?

Aproveite a implementação existente

Uma sobrescrita pode substituir completamente o método herdado ou estendê-lo. Para estender descrever, a subclasse chama super().descrever(), aproveita o texto básico retornado e acrescenta apenas a informação específica.

Neste exemplo de herança simples, super() dá acesso à implementação seguinte da hierarquia: Tarefa.descrever.

Fluxo da extensão

Diagrama em que descrever da tarefa prioritária chama super, passa pela implementação de Tarefa e recebe a descrição básica antes de acrescentar a prioridade.

A chamada entra na sobrescrita, consulta a implementação da classe base e retorna à subclasse para completar o resultado.

Estenda sem duplicar

Descrição básica mais identificação específica

Execute este script no seu computador e observe que a subclasse reutiliza a montagem da descrição básica.

python
class Tarefa:
    def __init__(self, titulo):
        if not titulo.strip():
            raise ValueError("O título não pode ficar vazio.")
        self.titulo = titulo
        self.concluida = False

    def descrever(self):
        estado = "concluída" if self.concluida else "pendente"
        return f"{self.titulo} — {estado}"


class TarefaPrioritaria(Tarefa):
    def descrever(self):
        descricao_base = super().descrever()
        return f"{descricao_base} [PRIORITÁRIA]"


tarefa = TarefaPrioritaria("Revisar orçamento")
print(tarefa.descrever())

# Resultado esperado:
# Revisar orçamento — pendente [PRIORITÁRIA]

Dica

A mesma instância continua em uso

super().descrever() não cria outra Tarefa. O método da classe base é executado sobre a mesma instância de TarefaPrioritaria. Também não se fornece self explicitamente: a vinculação da instância ocorre na chamada com super().

Onde está o reaproveitamento?

A classe base continua responsável por montar título e estado. A subclasse guarda o retorno em descricao_base e acrescenta somente [PRIORITÁRIA]. Se o formato básico mudar em Tarefa.descrever, a especialização continuará aproveitando essa implementação, em vez de manter uma cópia separada.

Siga o caminho da chamada

Ordene o fluxo

Coloque os acontecimentos na ordem em que ocorrem ao executar tarefa.descrever().

  1. A chamada `tarefa.descrever()` é iniciada.
  2. `Tarefa.descrever` usa a mesma instância e retorna a descrição básica.
  3. A sobrescrita chama `super().descrever()`.
  4. A subclasse acrescenta `[PRIORITÁRIA]` e retorna o texto completo.
  5. Python encontra `descrever` definido em `TarefaPrioritaria`.

Explique a decisão

Por que usar super()?

Explique por que super().descrever() é mais adequado do que reconstruir título e estado dentro de TarefaPrioritaria.descrever.

Escreva pelo menos 60 caracteres (0/60).

Passo 4 de 7

Inicializar a subclasse com super().__init__

Adicione a prioridade à subclasse sem perder a inicialização e a validação realizadas por Tarefa.

Uma parte comum e outra específica

Divida as responsabilidades

Ao definir um __init__ em TarefaPrioritaria, a subclasse passa a controlar sua inicialização. Como ela ainda depende da parte comum de Tarefa, deve chamar super().__init__(titulo).

A classe base valida e armazena o título, além de criar o estado de conclusão. Depois, a subclasse valida e armazena a prioridade. Não há cópia de atributos de outro objeto: cada atributo surge quando esses inicializadores são executados sobre a mesma instância.

Inicialização completa

Execute este código em um script Python para observar a divisão de responsabilidades.

python
class Tarefa:
    def __init__(self, titulo):
        if not isinstance(titulo, str) or not titulo.strip():
            raise ValueError("titulo deve ser um texto não vazio")

        self.titulo = titulo
        self.concluida = False

    def concluir(self):
        self.concluida = True

    def descrever(self):
        estado = "concluída" if self.concluida else "pendente"
        return f"{self.titulo} — {estado}"


class TarefaPrioritaria(Tarefa):
    def __init__(self, titulo, prioridade):
        super().__init__(titulo)

        if type(prioridade) is not int:
            raise TypeError("prioridade deve ser um número inteiro")
        if not 1 <= prioridade <= 3:
            raise ValueError("prioridade deve estar entre 1 e 3")

        self.prioridade = prioridade

    def descrever(self):
        descricao_basica = super().descrever()
        return f"{descricao_basica} | Prioridade: {self.prioridade}"


tarefa = TarefaPrioritaria("Revisar relatório", 2)
print(tarefa.descrever())

# Resultado esperado:
# Revisar relatório — pendente | Prioridade: 2

O fluxo de inicialização

A mesma instância passa pelos dois inicializadores

A criação começa em TarefaPrioritaria.__init__. A chamada a super().__init__(titulo) executa o inicializador de Tarefa sobre a mesma instância; por isso, não se fornece self explicitamente. Quando essa chamada termina, a subclasse continua e acrescenta prioridade.

Montagem do estado da instância

O esquema separa a parte inicializada pela classe base da parte acrescentada pela subclasse.

Fluxo em duas etapas: a classe base cria título e estado de conclusão, e a subclasse acrescenta a prioridade à mesma instância.

Primeiro surgem titulo e concluida; depois, prioridade é adicionada à mesma instância.

Dica

Quando não redefinir __init__

Se a inicialização herdada já fornece todo o estado necessário, não defina outro __init__ na subclasse. Nesse caso, o inicializador da classe base pode ser usado diretamente, como ocorreu antes de adicionarmos a prioridade.

Encaminhe a parte comum

Complete o inicializador

Complete a linha que mantém a inicialização de titulo e concluida fornecida por Tarefa:

class TarefaPrioritaria(Tarefa):
def init(self, titulo, prioridade):
__
self.prioridade = prioridade

Identifique cada responsabilidade

Analise a criação do objeto

Considere TarefaPrioritaria("Revisar relatório", 2). Explique:

  1. Quais atributos são criados pelo inicializador da classe base?
  2. Qual atributo e quais validações pertencem à subclasse?
  3. O que deveria acontecer com o __init__ da subclasse se ela não precisasse acrescentar estado?

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 7

Preservar o que a classe base garante

Diagnostique inicializadores e sobrescritas que quebram regras, retornos ou operações já oferecidas pela classe base.

O que uma especialização deve preservar

As garantias continuam valendo

Uma TarefaPrioritaria continua sendo uma especialização de Tarefa. Por isso, deve preservar as expectativas básicas já estabelecidas:

  • o título não pode ser vazio;
  • concluir() deve continuar funcionando;
  • descrever() deve poder ser chamado sem novos argumentos obrigatórios;
  • descrever() deve continuar retornando um texto com as informações básicas da tarefa.

A subclasse pode acrescentar estado e comportamento, mas não deve deixar o objeto incompleto nem mudar o significado básico dessas operações.

Base preservada e especialização incompleta

Comparação entre uma especialização apoiada em uma base íntegra e outra com partes herdadas desconectadas.

À esquerda, a especialização mantém estado, validação e operações da base. À direita, conexões ausentes representam atributos, retornos e chamadas quebrados.

Dica

Compare pela forma de uso

Ao revisar uma sobrescrita, compare a chamada, o retorno, o significado e os efeitos do método com o comportamento da classe base. Acrescentar informações é aceitável; exigir uma chamada incompatível ou eliminar um retorno esperado é um sinal de problema.

Dois defeitos comuns

Especializações quebradas

Leia o código e observe que cada subclasse compromete garantias diferentes de Tarefa.

python
class Tarefa:
    def __init__(self, titulo):
        if not titulo.strip():
            raise ValueError("O título não pode ser vazio")

        self.titulo = titulo
        self.concluida = False

    def concluir(self):
        self.concluida = True

    def descrever(self):
        estado = "concluída" if self.concluida else "pendente"
        return f"{self.titulo} — {estado}"


class TarefaComInicializacaoIncompleta(Tarefa):
    def __init__(self, titulo, prioridade):
        self.prioridade = prioridade


class TarefaComDescricaoIncompativel(Tarefa):
    def descrever(self, prefixo):
        print(f"{prefixo}: {self.titulo}")


incompleta = TarefaComInicializacaoIncompleta("Revisar relatório", 1)
# incompleta.descrever() tenta acessar atributos que não foram criados.

incompativel = TarefaComDescricaoIncompativel("Revisar relatório")
# incompativel.descrever() agora exige um argumento que antes não era necessário.

Atenção

O que foi quebrado?

O primeiro __init__ não chama super().__init__(titulo): a validação do título não acontece e os atributos titulo e concluida não são criados. Métodos herdados podem então falhar com AttributeError.

A segunda sobrescrita exige prefixo, embora a chamada original fosse descrever(). Além disso, ela imprime em vez de retornar texto e perde o estado da tarefa. Nem o método da base nem suas garantias são executados automaticamente.

Relacione defeitos e consequências

Diagnóstico rápido

Associe cada defeito à sua consequência principal.

Toque em um item e depois no par correspondente.

Corrija reutilizando a base

Repare a especialização

Escreva uma única classe TarefaPrioritaria que corrija os dois defeitos. Ela deve chamar o inicializador da base, aceitar prioridade de 1 a 3 e sobrescrever descrever() sem exigir argumentos adicionais. Reutilize super().descrever() e retorne a descrição básica acrescida da prioridade.

Escreva pelo menos 80 caracteres (0/80).

Passo 6 de 7

Escolher entre herança e composição

Use o significado dos tipos e suas responsabilidades para decidir entre especialização por herança e colaboração por composição.

A relação vem antes do código

Antes de usar herança, pergunte: o novo tipo continua representando uma versão especializada do tipo existente?

  • TarefaPrioritaria continua sendo uma tarefa e mantém operações como concluir e descrever. A herança expressa essa especialização.
  • Agenda reúne e utiliza tarefas, mas não representa uma tarefa. Essa relação é de composição.

A decisão depende do significado e das responsabilidades dos tipos, não apenas do código que eles têm em comum.

Especialização ou colaboração

Diagrama que contrasta TarefaPrioritaria como especialização de Tarefa e Agenda como objeto que contém e organiza tarefas.

TarefaPrioritaria amplia o conceito de Tarefa; Agenda apenas mantém e organiza objetos Tarefa.

Reutilização não basta

Exemplo

Duas decisões diferentes

TarefaPrioritaria–Tarefa: use herança porque a primeira acrescenta prioridade sem deixar de representar uma tarefa nem abandonar suas operações esperadas.

Agenda–Tarefa: use composição porque a agenda tem a responsabilidade de reunir e organizar tarefas. Ela não deve herdar de Tarefa, mesmo que queira aproveitar algum atributo ou método parecido.

Dica

Teste de significado

Complete mentalmente: “é uma versão de” favorece herança; “tem, reúne ou usa” favorece composição. Esse teste ajuda, mas a decisão final deve considerar as responsabilidades de cada classe.

Escolha a modelagem coerente

Qual decisão representa adequadamente as duas relações?

Justifique pela responsabilidade

Explique por que TarefaPrioritaria pode herdar de Tarefa, enquanto Agenda deve manter tarefas por composição. Baseie sua resposta no significado dos tipos e em suas responsabilidades.

Escreva pelo menos 60 caracteres (0/60).

Passo 7 de 7

Aplicar e revisar a especialização completa

Implemente TarefaPrioritaria em um script único, execute verificações manuais e compare sua implementação com uma solução que preserva as regras da classe base.

Implemente a especialização

Prática no seu computador

Crie um arquivo chamado tarefas.py no seu editor. Complete apenas a classe TarefaPrioritaria: ela deve inicializar a parte comum com super().__init__, validar uma prioridade inteira de 1 a 3 e estender descrever usando o retorno de super().descrever(). Não redefina concluir, pois o comportamento herdado já atende à especialização.

Script com requisitos e verificações

Substitua o pass pela implementação de TarefaPrioritaria e execute o script.

python
class Tarefa:
    def __init__(self, titulo):
        if not isinstance(titulo, str):
            raise TypeError("título deve ser uma string")

        titulo = titulo.strip()
        if not titulo:
            raise ValueError("título não pode ser vazio")

        self.titulo = titulo
        self.concluida = False

    def concluir(self):
        self.concluida = True

    def descrever(self):
        marcador = "[x]" if self.concluida else "[ ]"
        return f"{marcador} {self.titulo}"


class TarefaPrioritaria(Tarefa):
    # Implemente os métodos __init__ e descrever.
    # Requisitos:
    # 1. Receber titulo e prioridade.
    # 2. Reaproveitar Tarefa.__init__ com super().
    # 3. Aceitar somente prioridade inteira entre 1 e 3.
    # 4. Estender a descrição básica com " | prioridade: N".
    pass


# Criação válida e descrição inicial
tarefa = TarefaPrioritaria("Corrigir relatório", 2)
print(tarefa.descrever())

# Uso do método concluir herdado
tarefa.concluir()
print(tarefa.descrever())

# Prioridade fora da faixa
try:
    TarefaPrioritaria("Enviar relatório", 4)
except ValueError as erro:
    print(f"Prioridade inválida: {erro}")

# Título inválido: a regra da classe base deve continuar valendo
try:
    TarefaPrioritaria("   ", 1)
except ValueError as erro:
    print(f"Título inválido: {erro}")

Registre o que aconteceu

Sua implementação e suas observações

Cole o trecho que você criou para TarefaPrioritaria. Depois, anote o resultado da descrição antes e depois de concluir() e as exceções observadas para prioridade 4 e título vazio.

Escreva pelo menos 80 caracteres (0/80).

Compare com uma solução

Siga o fluxo de cada operação

Na inicialização, a classe base valida e cria titulo e concluida; depois, a subclasse valida e cria prioridade. Em descrever, a descrição básica é produzida sobre a mesma instância e recebe o complemento específico. Já concluir é encontrado na classe base e usado sem cópia.

Responsabilidades da especialização

O esquema separa a parte preservada da classe base da parte acrescentada pela subclasse.

Diagrama de Tarefa como classe base e TarefaPrioritaria como subclasse, mostrando inicialização e descrição estendidas e o método concluir herdado.

A subclasse acrescenta prioridade sem duplicar a validação do título, o estado de conclusão ou a montagem da descrição básica.

Implementação de referência

Compare esta classe com a sua. Observe que self não é passado nas chamadas feitas com super().

python
class TarefaPrioritaria(Tarefa):
    def __init__(self, titulo, prioridade):
        super().__init__(titulo)

        if type(prioridade) is not int:
            raise TypeError("prioridade deve ser um número inteiro")
        if not 1 <= prioridade <= 3:
            raise ValueError("prioridade deve estar entre 1 e 3")

        self.prioridade = prioridade

    def descrever(self):
        descricao_basica = super().descrever()
        return f"{descricao_basica} | prioridade: {self.prioridade}"

Exemplo

Saída esperada

[ ] Corrigir relatório | prioridade: 2
[x] Corrigir relatório | prioridade: 2
Prioridade inválida: prioridade deve estar entre 1 e 3
Título inválido: título não pode ser vazio

A mudança de [ ] para [x] confirma o uso de concluir herdado. A permanência do título e da prioridade confirma que descrever preservou a informação básica e apenas acrescentou o detalhe da subclasse.

Revisão final

Resumo

Critérios para decidir e implementar

Use a relação entre as responsabilidades para escolher a técnica, não apenas a quantidade de código economizada.

  • Use herança quando a subclasse representar uma especialização coerente da classe base, como TarefaPrioritaria em relação a Tarefa.
  • Não redefina um método quando o comportamento herdado já atender à subclasse.
  • Sobrescreva quando a subclasse precisar alterar um comportamento específico.
  • Use super() quando a sobrescrita precisar preservar e estender a implementação seguinte da hierarquia.
  • Ao redefinir __init__, chame super().__init__ quando a parte herdada precisar ser inicializada e validada.
  • Prefira composição quando um objeto apenas reunir, organizar ou utilizar outros objetos, como uma Agenda que contém tarefas.

Justifique a modelagem

Explique brevemente por que TarefaPrioritaria pode herdar de Tarefa, enquanto uma Agenda que reúne tarefas deve usar composição.

Escreva pelo menos 60 caracteres (0/60).

Especialização concluída

Parabéns! Você concluiu: Especializar classes com herança e super

Você concluiu o tutorial! Agora sabe criar subclasses, sobrescrever e estender métodos com super(), preservar as regras da classe base e escolher entre herança e composição.

100 XP

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso