runtime_checkable no Python: Protocol em runtime

Publicado em: 29/08/2026
Tempo de leitura: 6 minutos
A laptop screen showing a code editor with visible programming code in a dimly lit environment.

typing.runtime_checkable permite que determinados Protocols sejam usados com isinstance() e issubclass(). O recurso conecta tipagem estrutural e inspeção de runtime, mas com uma limitação importante: a verificação normalmente confirma apenas a presença dos membros, não suas assinaturas completas, tipos de parâmetros ou comportamento.

Neste guia, você aprenderá a declarar Protocols verificáveis, entender o que o teste realmente garante, trabalhar com métodos, atributos e propriedades, evitar falsos positivos, comparar Protocol com ABC, projetar plugins e adaptadores e decidir quando uma validação explícita é mais segura.

Protocol estrutural

from typing import Protocol

class Gravavel(Protocol):
    def salvar(self) -> None: ...

Uma classe não precisa herdar de Gravavel. Se possuir um método compatível, o analisador pode tratá-la como implementação estrutural.

class Documento:
    def salvar(self) -> None:
        print("salvo")


def persistir(item: Gravavel) -> None:
    item.salvar()

persistir(Documento())

Essa compatibilidade existe para o verificador estático. Um Protocol comum não pode ser usado diretamente com isinstance().

Adicionando runtime_checkable

from typing import Protocol, runtime_checkable

@runtime_checkable
class Gravavel(Protocol):
    def salvar(self) -> None: ...

print(isinstance(Documento(), Gravavel))

O decorador autoriza a inspeção em runtime. O resultado será verdadeiro quando o objeto expuser os membros exigidos de forma reconhecível.

O teste não valida a assinatura

class Incompativel:
    def salvar(self, caminho: str, forcar: bool) -> int:
        return 1

print(isinstance(Incompativel(), Gravavel))

A classe possui um atributo chamável chamado salvar, então o teste pode retornar verdadeiro mesmo que a assinatura não seja compatível com o Protocol. O analisador estático pode detectar a incompatibilidade; isinstance() não oferece a mesma precisão.

Presença não significa comportamento

Um objeto pode possuir o método e ainda falhar, fazer outra coisa ou lançar exceções. runtime_checkable verifica forma superficial, não semântica. Nunca trate o resultado como prova de qualidade, segurança ou correção funcional.

Atributos de dados

@runtime_checkable
class Nomeado(Protocol):
    nome: str

class Produto:
    nome = "Teclado"

isinstance(Produto(), Nomeado)

O teste procura o membro nome. Ele não garante que o valor seja string. Um inteiro ou propriedade problemática pode passar pela inspeção superficial.

Propriedades

@runtime_checkable
class ComTamanho(Protocol):
    @property
    def tamanho(self) -> int: ...

A presença de uma propriedade ou atributo com o nome esperado pode satisfazer o teste. O tipo de retorno e possíveis efeitos colaterais não são verificados. Evite propriedades que executam I/O ou lógica perigosa apenas para responder à introspecção.

Protocol somente de métodos e issubclass

issubclass() possui restrições maiores, especialmente quando o Protocol contém membros de dados. Protocols compostos apenas por métodos são mais adequados para testes de classe.

@runtime_checkable
class Fechavel(Protocol):
    def fechar(self) -> None: ...

class Recurso:
    def fechar(self) -> None: ...

issubclass(Recurso, Fechavel)

Comparação com hasattr

if hasattr(objeto, "salvar"):
    objeto.salvar()

hasattr() testa apenas um nome e pode ser suficiente em código local. runtime_checkable centraliza um conjunto de membros em um contrato reutilizável, melhora documentação e se integra ao analisador estático.

Comparação com ABC

Uma classe abstrata exige herança explícita ou registro virtual. Protocol aceita implementação estrutural. ABC é melhor quando você controla a hierarquia, precisa compartilhar implementação, impor métodos abstratos durante construção ou manter identidade nominal. Protocol é melhor para desacoplamento e compatibilidade com classes externas.

Plugins

@runtime_checkable
class Plugin(Protocol):
    nome: str
    def iniciar(self) -> None: ...
    def parar(self) -> None: ...


def carregar(objeto: object) -> Plugin:
    if not isinstance(objeto, Plugin):
        raise TypeError("plugin incompatível")
    return objeto

Esse teste funciona como triagem inicial. Para plugins não confiáveis, valide tipos, assinaturas, versão da API e comportamento necessário. Considere usar inspect.signature() ou uma fase explícita de registro.

Adaptadores

Quando uma biblioteca externa quase satisfaz o Protocol, um adaptador pode oferecer a interface correta:

class AdaptadorArquivo:
    def __init__(self, arquivo) -> None:
        self.arquivo = arquivo

    def salvar(self) -> None:
        self.arquivo.flush()

Adaptadores são mais seguros do que confiar em uma coincidência superficial de nomes.

TypeGuard para validação mais forte

from typing import TypeGuard

def eh_gravavel(valor: object) -> TypeGuard[Gravavel]:
    metodo = getattr(valor, "salvar", None)
    return callable(metodo)

Esse TypeGuard ainda verifica apenas chamabilidade, mas permite adicionar regras específicas. Você pode inspecionar outros atributos, versões ou marcadores. O guia sobre TypeGuard no Python explica a relação de confiança.

Não chame membros durante a validação

Executar o método para descobrir se ele funciona pode causar efeitos colaterais. Prefira inspeção, metadados, registro explícito e testes separados. A validação de interface não deve salvar arquivos, enviar mensagens ou alterar estado.

Desempenho

Testes com Protocol podem ser mais lentos que verificações nominais simples porque precisam inspecionar membros. Não use isinstance(obj, Protocolo) repetidamente em loops críticos. Valide uma vez na fronteira e mantenha a referência tipada.

Cache e mudanças de classe

Implementações modernas podem observar membros definidos na criação do Protocol e usar inspeção estática. Alterar dinamicamente atributos do Protocol ou monkey-patching de classes pode produzir resultados inesperados entre versões. Evite depender de mutação dinâmica da interface.

Métodos opcionais

Protocol não possui conceito direto de membro opcional. Se uma capacidade é opcional, crie Protocols menores:

@runtime_checkable
class Salvavel(Protocol):
    def salvar(self) -> None: ...

@runtime_checkable
class Exportavel(Protocol):
    def exportar(self, caminho: str) -> None: ...

O consumidor testa apenas a capacidade necessária. Essa segregação melhora design e reduz falsos requisitos.

Composição de capacidades

class Repositorio(Salvavel, Exportavel, Protocol):
    pass

Um Protocol composto reúne capacidades. Para runtime, confirme se a composição e os decoradores são suportados como esperado pelo interpretador e pelo analisador.

Protocol genérico

from typing import TypeVar

T = TypeVar("T")

@runtime_checkable
class Leitor(Protocol[T]):
    def ler(self) -> T: ...

Em runtime, parâmetros genéricos são apagados. Você pode testar o Protocol não parametrizado, mas não confirmar que ler() retorna um tipo específico apenas com isinstance().

Protocol parametrizado em isinstance

Evite testes como isinstance(obj, Leitor[str]). Tipos parametrizados geralmente não são classes adequadas para esse uso. A inspeção de runtime não conhece o argumento genérico.

Erros comuns

  • Assumir que assinaturas foram verificadas: normalmente só os nomes são observados.
  • Usar o teste como validação de segurança: comportamento e tipos reais não são garantidos.
  • Testar Protocol genérico parametrizado: argumentos de tipo não existem dessa forma em runtime.
  • Criar Protocols enormes: pequenas capacidades são mais reutilizáveis.
  • Executar métodos durante validação: pode causar efeitos colaterais.
  • Usar em loops críticos: a introspecção pode ter custo relevante.

Exemplo completo: sistema de notificações

from typing import Protocol, runtime_checkable

@runtime_checkable
class Notificador(Protocol):
    nome: str
    def enviar(self, destino: str, mensagem: str) -> None: ...

class Email:
    nome = "email"
    def enviar(self, destino: str, mensagem: str) -> None:
        print(f"email para {destino}: {mensagem}")

class Sms:
    nome = "sms"
    def enviar(self, destino: str, mensagem: str) -> None:
        print(f"sms para {destino}: {mensagem}")


def registrar(candidato: object) -> Notificador:
    if not isinstance(candidato, Notificador):
        raise TypeError("notificador inválido")
    return candidato

notificadores = [registrar(Email()), registrar(Sms())]
for notificador in notificadores:
    notificador.enviar("cliente", "Pedido aprovado")

O teste aceita implementações sem herança comum. Em produção, o registro também pode validar configuração, versão, limites e tratamento de erros.

Quando evitar runtime_checkable

Use herança nominal quando você controla todas as classes e precisa de invariantes fortes. Use uma função explícita de validação quando assinaturas e valores importam em runtime. Use registro de plugins quando confiança e versionamento são necessários. Use runtime_checkable quando uma verificação estrutural superficial é suficiente para escolher um caminho ou emitir uma mensagem de erro melhor.

Conclusão

runtime_checkable torna Protocols utilizáveis em isinstance() e issubclass(), facilitando plugins, adaptadores e detecção de capacidades. Porém, o teste confirma principalmente a presença dos membros, não o contrato completo.

A documentação oficial de runtime_checkable no módulo typing detalha as restrições. Combine análise estática com validação explícita quando assinaturas, dados ou segurança forem importantes, e trate o teste estrutural como uma triagem, não como uma prova absoluta.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Close-up of a laptop screen with code and a coffee mug, perfect for tech abstract themes.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Unpack no Python: kwargs e tipos variádicos

    Aprenda typing.Unpack no Python para tipar **kwargs com TypedDict, expandir tuplas variádicas e preservar assinaturas precisas.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Required e NotRequired: campos opcionais no TypedDict

    Aprenda Required e NotRequired no Python para controlar chaves obrigatórias e opcionais em TypedDict com contratos claros.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ReadOnly no Python: proteja campos TypedDict

    Aprenda ReadOnly no Python para marcar campos TypedDict como somente leitura, modelar contratos imutáveis e evitar alterações acidentais.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A detailed image of a reticulated python showcasing its patterned scales and intricate skin texture.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    TypeAliasType no Python: aliases em runtime

    Aprenda TypeAliasType no Python para criar aliases explícitos, inspecionar tipos em runtime e modelar APIs genéricas com segurança.

    Ler mais

    Tempo de leitura: 7 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    overload no Python: assinaturas precisas

    Aprenda typing.overload no Python para criar assinaturas precisas com Literal, None, genéricos, métodos e retornos dependentes dos argumentos.

    Ler mais

    Tempo de leitura: 8 minutos
    29/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ClassVar no Python: separe classe e instância

    Aprenda ClassVar no Python para separar atributos de classe e instância em dataclasses, registries, caches, herança e contadores.

    Ler mais

    Tempo de leitura: 7 minutos
    29/08/2026