
Passo 1 de 7
Reconhecer a adesão explícita a um contrato
Entenda por que um contrato nominal depende de pertencer declaradamente a uma hierarquia de classes.
Trilha de aprendizado · Nível 12 · Tutorial 9
Criar interfaces baseadas em herança explícita com ABC e abstractmethod, distinguindo suas garantias das oferecidas por Protocol.
Reconhecer a adesão explícita a um contrato
Entenda por que um contrato nominal depende de pertencer declaradamente a uma hierarquia de classes. 2 min
Declarar operações abstratas e bloquear instâncias incompletas
Use ABC e @abstractmethod para declarar operações obrigatórias e identificar quais classes ainda não podem ser instanciadas. 3 min
Implementar subclasses concretas substituíveis
Concretize uma operação abstrata em subclasses, preserve sua assinatura e use diferentes implementações por meio do mesmo tipo base. 3 min
Compartilhar lógica concreta na classe abstrata
Reutilize uma operação em lote na classe abstrata sem abrir mão da implementação específica exigida de cada formatador. 3 min
Separar instanciação, assinaturas e comportamento
Diferencie o bloqueio de classes abstratas, a análise estática de assinaturas e os testes de comportamento. 4 min
Escolher entre ABC e Protocol
Compare contratos nominais e estruturais para decidir quando a herança explícita é requisito e quando basta oferecer operações compatíveis. 2 min
Aplicação final: completar e verificar o contrato
Complete uma nova implementação de formatador e reúna evidências distintas para concretização, compatibilidade estática e comportamento. 4 min

Passo 1 de 7
Entenda por que um contrato nominal depende de pertencer declaradamente a uma hierarquia de classes.
Em um contrato nominal, a compatibilidade começa pela relação declarada entre classes: uma implementação precisa pertencer à família do tipo base, por herança direta ou indireta.
Imagine Formatador como a base de uma família. FormatadorMaiusculo e FormatadorTitulo fazem parte dessa família porque a estendem. Por isso, podem ser tratadas como formatadores em pontos do código que exigem Formatador.
A seta de herança é a evidência de adesão ao contrato nominal.

Herança direta ou indireta liga uma classe ao contrato nominal; sem essa ligação, métodos parecidos não bastam.
Exemplo
class Formatador:
def formatar(self, texto: str) -> str:
return texto
class FormatadorMaiusculo(Formatador):
def formatar(self, texto: str) -> str:
return texto.upper()
class Copiador:
def formatar(self, texto: str) -> str:
return texto.upper()
def exibir(formatador: Formatador, texto: str) -> None:
print(formatador.formatar(texto))FormatadorMaiusculo adere nominalmente: sua declaração inclui Formatador. Já Copiador tem um método de mesmo nome e resultado semelhante, mas não declarou participação nessa hierarquia.
Dica
Ao anotar formatador: Formatador, você registra uma decisão de projeto: esse ponto aceita objetos da família Formatador. Em código tipado, um verificador estático pode apontar o uso de Copiador como incompatível por ele não herdar da base.
A anotação, sozinha, não impede automaticamente uma chamada em tempo de execução nem valida os argumentos recebidos.
Considere esta hierarquia:
class Formatador:
def formatar(self, texto: str) -> str:
return texto
class FormatadorTitulo(Formatador):
pass
class FormatadorComEmoji(FormatadorTitulo):
pass
class Copiador:
def formatar(self, texto: str) -> str:
return textoUma função está anotada para receber Formatador. Qual classe abaixo participa desse contrato por herança indireta?

Passo 2 de 7
Use ABC e @abstractmethod para declarar operações obrigatórias e identificar quais classes ainda não podem ser instanciadas.
No contrato nominal, a classe base faz parte da família por herança explícita. Para exigir uma operação antes de permitir instâncias, importe ABC e abstractmethod do módulo abc, herde de ABC e decore o método obrigatório.
O corpo ... indica que esta declaração não fornece uma implementação utilizável. A assinatura anotada documenta a operação que as classes da família deverão concretizar.
from abc import ABC, abstractmethod
class FormatadorTexto(ABC):
@abstractmethod
def formatar(self, texto: str) -> str:
...
Uma operação abstrata pendente bloqueia a criação de instâncias, mas não a declaração de subclasses.
Python permite declarar uma subclasse mesmo que ela ainda não complete todas as operações abstratas. O impedimento aparece apenas ao criar uma instância: a base abstrata e qualquer descendente com métodos abstratos pendentes produzem TypeError.
Neste momento, não é preciso implementar a subclasse: observe que ela pode existir no código, mas não pode ser construída.
class FormatadorParcial(FormatadorTexto):
pass
# FormatadorTexto()
# TypeError: Can't instantiate abstract class FormatadorTexto
# with abstract method formatar
# FormatadorParcial()
# TypeError: Can't instantiate abstract class FormatadorParcial
# with abstract method formatarAtenção
O TypeError informa que ainda há operações abstratas pendentes. Ele não é uma validação dos argumentos recebidos em tempo de execução e não prova que uma futura implementação produzirá a formatação correta.
Herdar de ABC, por si só, não bloqueia instâncias. O bloqueio depende de pelo menos um método marcado com @abstractmethod que permaneça pendente.
Também não confunda um método abstrato com um método comum que lança NotImplementedError. Sem o decorador, Python permite instanciar a classe; a falha só ocorrerá se aquela operação for chamada.
from abc import ABC
class BaseSemObrigacao(ABC):
pass
class BaseComFalhaTardia:
def formatar(self, texto: str) -> str:
raise NotImplementedError("Implemente formatar")
BaseSemObrigacao() # permitido
BaseComFalhaTardia() # permitido
# BaseComFalhaTardia().formatar("Olá") # levanta NotImplementedErrorPara exigir que formatar seja concretizado antes de criar instâncias, escreva @____ antes do método.
A expressão BaseSemObrigacao() gera TypeError apenas porque BaseSemObrigacao herda de ABC.

Passo 3 de 7
Concretize uma operação abstrata em subclasses, preserve sua assinatura e use diferentes implementações por meio do mesmo tipo base.
A classe base Formatador exige o método formatar. Uma subclasse se torna instanciável quando fornece esse método com os mesmos parâmetros e com retorno compatível.
Neste exemplo, Maiusculas implementa formatar(texto: str) -> str. Como não resta nenhuma operação abstrata pendente, Maiusculas("!") pode ser criada normalmente.

A implementação de formatar remove a pendência abstrata da subclasse.
Salve como formatadores.py. A função consumidora recebe o tipo base, não uma subclasse específica.
from abc import ABC, abstractmethod
class Formatador(ABC):
def __init__(self, sufixo: str) -> None:
self.sufixo = sufixo
@abstractmethod
def formatar(self, texto: str) -> str:
pass
class Maiusculas(Formatador):
def formatar(self, texto: str) -> str:
return texto.upper() + self.sufixo
def exibir(formatador: Formatador, texto: str) -> None:
print(formatador.formatar(texto))
exibir(Maiusculas("!"), "Python")
# Saída: PYTHON!A implementação não precisa ser repetida em toda classe descendente. Se uma classe intermediária já concretizou formatar, suas descendentes também não têm essa pendência — salvo se introduzirem outra operação abstrata.
ComColchetes é concreta porque implementa o contrato. ComColchetesDuplos herda essa implementação e, por isso, também pode ser instanciada.
Acrescente estas classes ao mesmo arquivo, antes das chamadas finais:
class ComColchetes(Formatador):
def formatar(self, texto: str) -> str:
return f"[{texto}]{self.sufixo}"
class ComColchetesDuplos(ComColchetes):
pass
exibir(ComColchetes("."), "aviso")
exibir(ComColchetesDuplos("."), "aviso")
# Saída nas duas chamadas:
# [aviso].Dica
A anotação formatador: Formatador permite que a função receba instâncias de subclasses dessa hierarquia. A chamada de formatar usa a implementação disponível na instância recebida.
No seu computador, acrescente Minusculas ao arquivo. Ela deve herdar de Formatador, aceitar o mesmo sufixo do inicializador herdado e implementar formatar(self, texto: str) -> str.
O resultado de exibir(Minusculas("."), "PyThOn") deve ser python.. Depois, execute o arquivo com python formatadores.py e confirme que tanto Maiusculas quanto Minusculas funcionam pelo mesmo consumidor.
Insira a classe antes das chamadas e acrescente a chamada final.
class Minusculas(Formatador):
def formatar(self, texto: str) -> str:
# Substitua esta linha pela transformação necessária.
pass
exibir(Minusculas("."), "PyThOn")
# Saída esperada: python.Quais foram as saídas de Maiusculas e Minusculas? Explique por que ambas são instanciáveis e por que exibir pode receber as duas.
Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 7
Reutilize uma operação em lote na classe abstrata sem abrir mão da implementação específica exigida de cada formatador.
Uma classe que herda de ABC pode combinar métodos abstratos e métodos concretos. O método abstrato formatar continua sendo a operação que cada subclasse precisa fornecer. Já formatar_todos concentra uma regra comum: percorrer os textos e formatar cada um deles.
Assim, as subclasses definem como um texto é formatado, enquanto a base define como aplicar essa operação a uma coleção.

O método concreto herdado chama a operação específica da instância para cada elemento.
Crie ou atualize um arquivo Python no seu computador com este exemplo completo.
from abc import ABC, abstractmethod
from collections.abc import Iterable
class FormatadorTexto(ABC):
@abstractmethod
def formatar(self, texto: str) -> str:
"""Formata um único texto."""
def formatar_todos(self, textos: Iterable[str]) -> list[str]:
return [self.formatar(texto) for texto in textos]
class FormatadorMarkdown(FormatadorTexto):
def formatar(self, texto: str) -> str:
return f"**{texto}**"
class FormatadorColchetes(FormatadorTexto):
def formatar(self, texto: str) -> str:
return f"[{texto}]"
textos = ["aviso", "resumo"]
markdown = FormatadorMarkdown()
colchetes = FormatadorColchetes()
print(markdown.formatar_todos(textos))
print(colchetes.formatar_todos(textos))
Exemplo
['aviso', 'resumo']
['[aviso]', '[resumo]']
Dica
Dentro de formatar_todos, a chamada self.formatar(texto) é resolvida conforme a instância. Em um FormatadorMarkdown, ela executa a versão Markdown; em um FormatadorColchetes, executa a versão com colchetes. A lógica do laço não é duplicada.
Ordene o fluxo produzido por markdown.formatar_todos(["aviso", "resumo"]).
Considere a classe abaixo. Qual afirmação está correta?
<code>class FormatadorPendente(FormatadorTexto):
pass</code>
Em uma frase ou duas, explique por que formatar_todos fica na classe base, enquanto formatar fica a cargo das subclasses.
Escreva pelo menos 40 caracteres (0/40).

Passo 5 de 7
Diferencie o bloqueio de classes abstratas, a análise estática de assinaturas e os testes de comportamento.
Uma ABC responde a uma pergunta específica em tempo de execução: a classe ainda tem operações abstratas pendentes? Se tiver, ela não pode ser instanciada.
Isso não verifica automaticamente se a assinatura usada pela subclasse é compatível com a assinatura declarada na base. Essa comparação é trabalho do mypy. E mesmo uma assinatura aprovada não prova que o resultado produzido atende à regra do domínio: para isso, escreva testes.

Cada mecanismo cobre uma falha diferente; nenhum substitui os demais.
Atenção
Uma subclasse pode ser considerada concreta apenas por possuir um método com o nome abstrato. O mecanismo de ABC não confere sozinho quantidade, tipos ou retorno dos parâmetros da sobrescrita.
Salve como formatadores.py e execute os dois comandos mostrados depois do código.
from abc import ABC, abstractmethod
class Formatador(ABC):
@abstractmethod
def formatar(self, texto: str) -> str:
...
class FormataComLargura(Formatador):
# O nome coincide com o método abstrato, então a classe é concreta.
# Mas a assinatura exige um argumento que a base não exige.
def formatar(self, texto: str, largura: int) -> str:
return texto.center(largura)
def exibir(formatador: Formatador, texto: str) -> str:
return formatador.formatar(texto)
quebrado = FormataComLargura() # A instanciação é permitida.
print(exibir(quebrado, "abc")) # Em execução: TypeError
# No terminal:
# python formatadores.py
# python -m mypy --strict formatadores.py
A criação de FormataComLargura() é permitida porque não há método abstrato pendente. Porém, exibir chama formatar com apenas texto; por isso a execução termina em TypeError ao faltar largura.
Já o mypy deve apontar a definição de formatar como uma assinatura incompatível com a superclasse. Para substituir a base com segurança, a subclasse precisa aceitar as chamadas previstas por ela e retornar um valor compatível — neste caso, str.
Acrescente este trecho ao mesmo arquivo e rode novamente o mypy.
class ApenasParecido:
def formatar(self, texto: str) -> str:
return f"<{texto}>"
parecido = ApenasParecido()
print(exibir(parecido, "abc"))
# Em execução, a chamada funciona porque Python não aplica a anotação.
# O mypy reporta: argumento incompatível para "exibir".
Atenção
O parâmetro formatador: Formatador orienta ferramentas estáticas; ele não executa uma validação nominal automática quando a função é chamada. Sem o mypy, o segundo exemplo pode rodar se o objeto tiver o método esperado.
Agora considere uma implementação com a assinatura correta, mas com uma regra de negócio incorreta. O mypy aceita a substituição porque parâmetros e retorno são compatíveis. Um teste deve verificar o resultado que o contrato documenta.
Salve como test_formatadores.py, no mesmo diretório de formatadores.py, e execute python -m pytest.
from formatadores import Formatador
class FormataMaiusculasComDefeito(Formatador):
def formatar(self, texto: str) -> str:
return texto.lower() # Assinatura válida, comportamento errado.
def test_formata_em_maiusculas() -> None:
formatador = FormataMaiusculasComDefeito()
assert formatador.formatar("Olá") == "OLÁ"
Por que FormataComLargura pode ser instanciada, mas ainda deve ser corrigida? E por que o teste de maiúsculas continua necessário após o mypy aceitar uma implementação?
Escreva pelo menos 80 caracteres (0/80).
Relacione cada situação ao mecanismo que revela diretamente o problema.
Toque em um item e depois no par correspondente.

Passo 6 de 7
Compare contratos nominais e estruturais para decidir quando a herança explícita é requisito e quando basta oferecer operações compatíveis.
Use ABC quando fazer parte de uma hierarquia explícita é um requisito do domínio ou da arquitetura. A implementação declara essa participação ao herdar da base.
Use Protocol quando o consumidor precisa apenas de determinadas operações e deve aceitar classes independentes que já tenham assinaturas compatíveis. Nesse caso, não é necessário alterar a classe para fazê-la herdar do contrato.
A diferença central é a relação exigida entre a implementação e o contrato.

ABC exige vínculo de herança; Protocol aceita compatibilidade pela estrutura.
Exemplo
Uma biblioteca define os formatadores oficiais do sistema e quer que toda implementação pertença claramente à família Formatador. ABC expressa esse requisito.
Já uma função que apenas precisa chamar formatar(texto: str) -> str pode receber um adaptador criado por outro pacote, sem modificar sua classe. Protocol evita impor uma herança que não é necessária.
Uma ABC pode reunir métodos abstratos e lógica concreta compartilhada, como formatar_lote. Isso é um benefício útil, mas não é motivo suficiente por si só para escolher ABC: uma composição ou uma função auxiliar também pode compartilhar lógica.
A escolha principal é: a implementação deve declarar que pertence a esta família? Se sim, ABC. Se não, e bastam as operações compatíveis, Protocol.
Ambos podem participar da checagem estática de assinaturas; a obrigação de concretizar métodos antes de instanciar é específica da ABC.

Assinaturas, instanciação e comportamento são garantias distintas.
Dica
Evite justificar ABC dizendo que ela valida automaticamente a assinatura da sobrescrita ou o resultado do método. abstractmethod exige uma concretização para permitir instanciar; um verificador como mypy examina compatibilidade de assinaturas; testes verificam o comportamento esperado.
Um relatório recebe objetos de plugins de terceiros. Cada plugin já possui formatar(texto: str) -> str, mas seus autores não podem nem precisam herdar de uma classe do seu projeto. Qual contrato é mais adequado para anotar o consumidor?
Considere dois requisitos:
Formatador e podem aproveitar uma operação concreta comum da base.formatar(texto: str) -> str, sem que suas classes precisem ser alteradas.Escolha ABC ou Protocol para cada requisito e justifique sem atribuir a eles garantias que pertencem ao mypy ou aos testes.
Escreva pelo menos 120 caracteres (0/120).

Passo 7 de 7
Complete uma nova implementação de formatador e reúna evidências distintas para concretização, compatibilidade estática e comportamento.
Você vai adicionar TitleFormatter à família nominal de TextFormatter. A transformação exigida é: remover espaços nas extremidades e aplicar capitalização de título, como em " python AVANÇADO " → "Python Avançado".
Não altere TextFormatter, UpperFormatter nem a função renderizar. Ao final, a nova classe deve participar da mesma hierarquia e funcionar tanto para um texto quanto para uma lista de textos.
A função consumidora conhece apenas a base TextFormatter; cada instância concreta fornece sua própria implementação de format.

A extensão ocorre na família de subclasses, sem mudar quem consome o contrato.
Dica
Use três verificações, cada uma com um alcance diferente: a tentativa de instanciar uma classe incompleta confirma o bloqueio de abstração; mypy --strict examina as anotações e sobrescritas; pytest confirma os resultados esperados. Nenhuma delas, isoladamente, substitui as outras.
Copie este arquivo para o seu computador. Em TitleFormatter.format, substitua o corpo com raise NotImplementedError pela implementação solicitada, mantendo os tipos e a assinatura.
from abc import ABC, abstractmethod
class TextFormatter(ABC):
@abstractmethod
def format(self, text: str) -> str:
"""Formata um texto individual."""
def format_all(self, texts: list[str]) -> list[str]:
return [self.format(text) for text in texts]
class UpperFormatter(TextFormatter):
def format(self, text: str) -> str:
return text.upper()
class TitleFormatter(TextFormatter):
def format(self, text: str) -> str:
raise NotImplementedError # Substitua por sua implementação.
def renderizar(formatter: TextFormatter, texts: list[str]) -> list[str]:
return formatter.format_all(texts)
Exemplo
O corpo final de TitleFormatter.format deve devolver text.strip().title(). Essa implementação concreta satisfaz a operação obrigatória e torna TitleFormatter instanciável.
Este teste cria uma subclasse deliberadamente incompleta para demonstrar o TypeError, sem desfazer sua implementação de TitleFormatter.
import pytest
from formatters import TextFormatter, TitleFormatter, UpperFormatter, renderizar
class IncompleteFormatter(TextFormatter):
pass
def test_subclasse_incompleta_nao_pode_ser_instanciada() -> None:
with pytest.raises(TypeError):
IncompleteFormatter()
def test_title_formatter_formata_um_texto() -> None:
formatter = TitleFormatter()
assert formatter.format(" python AVANÇADO ") == "Python Avançado"
def test_renderizar_aceita_subclasses_da_mesma_familia() -> None:
textos = ["olá mundo", "python"]
assert renderizar(UpperFormatter(), textos) == ["OLÁ MUNDO", "PYTHON"]
assert renderizar(TitleFormatter(), textos) == ["Olá Mundo", "Python"]
No terminal, na pasta que contém os dois arquivos, execute estes comandos depois de completar o método:
python -m mypy --strict formatters.py test_formatters.py
python -m pytest -qAtenção
Se TitleFormatter ainda apenas lançar NotImplementedError, ela pode continuar instanciável: para o mecanismo de ABC, o método deixou de ser abstrato. Os testes revelam que o comportamento ainda está errado. Do mesmo modo, uma aprovação do mypy não prova que a transformação produz o texto correto.
No seu computador, complete o método e execute os comandos. Em seguida, registre: (1) o que ocorreu ao instanciar IncompleteFormatter; (2) o resultado do mypy e dos testes; e (3) por que ABC é apropriada neste caso em vez de um Protocol.
Escreva pelo menos 180 caracteres (0/180).
Resumo
ABC com @abstractmethod impede a instanciação enquanto houver operações abstratas pendentes.format_all da base e ser consumida por uma função anotada com TextFormatter.Parabéns! Você concluiu: Definir contratos nominais com classes abstratas
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks