runtime_checkable en Python: Protocol runtime

Publicado el: 29/08/2026
Tempo de leitura: 5 minutos
A developer typing code on a laptop with a Python book beside in an office.

typing.runtime_checkable permite usar determinados Protocol con isinstance() e issubclass(). Conecta tipado estructural e inspección de runtime, pero tiene una limitación importante: normalmente comprueba la presencia de miembros, no firmas completas, tipos de parámetros, retornos ni comportamiento.

Esta guía explica declaraciones, métodos, atributos, propiedades, falsos positivos, comparación con ABC, plugins, adaptadores, Protocol genéricos, rendimiento, validación más fuerte y cuándo conviene un registro explícito.

Protocol estructural

from typing import Protocol

class Guardable(Protocol):
    def guardar(self) -> None: ...

Una clase no necesita heredar de Guardable. Un analizador estático puede aceptar cualquier clase con un método compatible.

class Documento:
    def guardar(self) -> None:
        print("guardado")


def persistir(item: Guardable) -> None:
    item.guardar()

persistir(Documento())

La compatibilidad existe para el análisis estático. Un Protocol normal no puede usarse directamente en una comprobación de instancia.

Añadir runtime_checkable

from typing import Protocol, runtime_checkable

@runtime_checkable
class Guardable(Protocol):
    def guardar(self) -> None: ...

print(isinstance(Documento(), Guardable))

El decorador autoriza la inspección de runtime. El resultado es verdadero cuando el objeto expone los miembros requeridos de forma reconocible.

No valida firmas

class Incompatible:
    def guardar(self, ruta: str, forzar: bool) -> int:
        return 1

print(isinstance(Incompatible(), Guardable))

El objeto posee un miembro invocable llamado guardar, por lo que el test puede pasar aunque la firma sea incompatible. El analizador estático puede detectar el problema; isinstance() no ofrece la misma precisión.

Presencia no significa comportamiento

Un objeto puede exponer el nombre correcto y aun así fallar, realizar otra acción o lanzar excepciones. runtime_checkable comprueba una forma superficial, no semántica. No trates el resultado como prueba de seguridad o corrección.

Atributos de datos

@runtime_checkable
class Nombrado(Protocol):
    nombre: str

class Producto:
    nombre = "Teclado"

isinstance(Producto(), Nombrado)

El test busca un miembro nombre. No garantiza que el valor sea string. Un entero o descriptor problemático puede satisfacer la presencia.

Propiedades

@runtime_checkable
class ConTamano(Protocol):
    @property
    def tamano(self) -> int: ...

Una propiedad o atributo con el nombre esperado puede pasar. El retorno y los efectos secundarios no se validan. Evita descriptores que realicen I/O o trabajo peligroso durante introspección.

Protocol solo de métodos e issubclass

issubclass() tiene restricciones mayores, especialmente con miembros de datos. Los Protocol compuestos solo por métodos son mejores para pruebas de clase.

@runtime_checkable
class Cerrable(Protocol):
    def cerrar(self) -> None: ...

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

issubclass(Recurso, Cerrable)

Comparación con hasattr

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

hasattr() puede bastar para una comprobación local. runtime_checkable centraliza varias capacidades en un contrato reutilizable, documenta la interfaz y participa en el análisis estático.

Comparación con ABC

Una clase abstracta suele exigir herencia explícita o registro virtual. Es mejor cuando controlas la jerarquía, compartes implementación, impones métodos abstractos o necesitas identidad nominal. Protocol favorece desacoplamiento y compatibilidad con clases externas.

Plugins

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


def cargar(candidato: object) -> Plugin:
    if not isinstance(candidato, Plugin):
        raise TypeError("plugin incompatible")
    return candidato

Es un buen filtro inicial. Para plugins no confiables, valida versión, configuración, firmas, permisos y comportamiento. Puede ser necesario inspect.signature() o registro explícito.

Adaptadores

class AdaptadorArchivo:
    def __init__(self, archivo) -> None:
        self.archivo = archivo

    def guardar(self) -> None:
        self.archivo.flush()

Cuando un objeto externo casi satisface el contrato, un adaptador es más seguro que confiar en una coincidencia accidental de nombres.

TypeGuard para comprobaciones más fuertes

from typing import TypeGuard

def es_guardable(valor: object) -> TypeGuard[Guardable]:
    metodo = getattr(valor, "guardar", None)
    return callable(metodo)

Este ejemplo todavía comprueba solo invocabilidad, pero una función personalizada puede validar marcadores, versiones, atributos o firmas. Consulta TypeGuard en Python.

No llames métodos durante la validación

Invocar una capacidad para comprobarla puede causar efectos secundarios. Prefiere metadatos, introspección, registro y pruebas separadas. La validación de interfaz no debería enviar mensajes, escribir archivos ni alterar estado.

Rendimiento

Las pruebas de Protocol pueden ser más lentas que comprobaciones nominales simples porque inspeccionan miembros. No las repitas en bucles críticos. Valida una vez en la frontera y conserva la referencia tipada.

Caché y monkey-patching

Implementaciones modernas pueden congelar el conjunto de miembros y usar búsqueda estática. Modificar dinámicamente el Protocol o las clases candidatas puede producir resultados inesperados entre versiones.

Capacidades opcionales

Protocol no tiene miembros opcionales directos. Crea Protocol pequeños:

@runtime_checkable
class Guardable(Protocol):
    def guardar(self) -> None: ...

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

El consumidor prueba solo la capacidad necesaria. La segregación mejora reutilización y reduce requisitos falsos.

Composición

class Repositorio(Guardable, Exportable, Protocol):
    pass

Un Protocol compuesto reúne capacidades. Verifica comportamiento de runtime y decoradores en las versiones soportadas.

Protocol genérico

from typing import TypeVar

T = TypeVar("T")

@runtime_checkable
class Lector(Protocol[T]):
    def leer(self) -> T: ...

Los argumentos genéricos se borran en runtime. Puedes comprobar el Protocol no parametrizado, pero no confirmar que leer() devuelva un tipo específico.

Protocol parametrizado en isinstance

Evita isinstance(obj, Lector[str]). Los objetos de typing parametrizados normalmente no son clases de runtime adecuadas y el argumento genérico no puede verificarse.

Errores comunes

  • Suponer que se validaron las firmas: normalmente solo se observan nombres.
  • Usar el resultado como garantía de seguridad: comportamiento y valores no están probados.
  • Comprobar Protocol genérico parametrizado: runtime no valida el argumento.
  • Diseñar Protocol enormes: capacidades pequeñas son más reutilizables.
  • Llamar métodos durante validación: pueden ocurrir efectos secundarios.
  • Comprobar repetidamente en bucles: la introspección tiene coste.

Ejemplo completo: proveedores de notificación

from typing import Protocol, runtime_checkable

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

class Email:
    nombre = "email"
    def enviar(self, destino: str, mensaje: str) -> None:
        print(f"email a {destino}: {mensaje}")

class Sms:
    nombre = "sms"
    def enviar(self, destino: str, mensaje: str) -> None:
        print(f"sms a {destino}: {mensaje}")


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 aprobado")

La prueba acepta implementaciones sin herencia compartida. Un registro de producción también puede validar configuración, versión, límites y manejo de errores.

Cuándo evitar runtime_checkable

Usa herencia nominal cuando controlas todas las implementaciones y necesitas invariantes fuertes. Usa un validador explícito cuando firmas y valores importan en runtime. Usa registro de plugins cuando confianza y versionado son necesarios. Usa runtime_checkable cuando una comprobación superficial es suficiente para despacho o mejores errores.

Conclusión

runtime_checkable permite usar ciertos Protocol con isinstance() e issubclass(), facilitando plugins, adaptadores y detección de capacidades. La comprobación confirma principalmente presencia, no el contrato completo.

La documentación oficial de runtime_checkable en Python define las restricciones. Combina análisis estático con validación explícita cuando firmas, datos o seguridad sean importantes y trata la comprobación estructural como filtro, no como prueba absoluta.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

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

    Unpack en Python: kwargs y tipos variádicos

    Aprende typing.Unpack en Python para tipar **kwargs con TypedDict, expandir tuplas variádicas y conservar firmas precisas.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    Person holding Python logo sticker with blurred background, highlighting programming focus.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Required y NotRequired: campos opcionales en TypedDict

    Aprende Required y NotRequired en Python para controlar claves obligatorias y opcionales de TypedDict sin confundir ausencia con None.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    Ball python slithering on a sunlit gravel pathway outdoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ReadOnly en Python: protege campos TypedDict

    Aprende ReadOnly en Python para proteger campos TypedDict, modelar contratos estables y evitar escrituras accidentales.

    Ler mais

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

    TypeAliasType en Python: alias en runtime

    Aprende TypeAliasType en Python para crear alias explícitos, inspeccionarlos en runtime y modelar APIs genéricas reutilizables.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    overload en Python: firmas precisas

    Aprende typing.overload en Python para firmas precisas con Literal, None, genéricos, métodos y retornos dependientes de argumentos.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ClassVar en Python: separa clase e instancia

    Aprende ClassVar en Python para separar atributos de clase e instancia en dataclasses, registries, caches, herencia y contadores.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026