Yeda AI Tips · #127

English

Protocol en vez de herencia: costuras de prueba estructurales en Python

¿Duck typing con verificador de tipos? El Protocol de Python hace ambas cosas. Desde Python 3.8, typing.Protocol ofrece subtipado estructural: cualquier objeto con la forma correcta satisface el tipo, y el checker lo verifica — sin clase base compartida, sin abc.ABC, sin importar tu clase dentro del doble de prueba.

El problema de las costuras por herencia

Para reemplazar una dependencia real por un fake en las pruebas, la jugada clásica es una clase base abstracta compartida. Pero eso acopla tres cosas que no deberían estar acopladas:

  1. La implementación real debe heredar de la base.
  2. El fake de prueba debe importar y heredar la misma base.
  3. Toda implementación nueva — incluidas las de código ajeno — tiene que adherirse explícitamente.

Ese acoplamiento se propaga. Las clases de terceros no pueden heredar tu ABC de forma retroactiva, así que terminas escribiendo adaptadores cuyo único trabajo es decir "sí, soy uno de esos".

La solución estructural

Define un Protocol con solo el método que necesitas. Cualquier objeto que lo implemente satisface el tipo — la clase nunca menciona el protocolo:

from typing import Protocol

class Renderer(Protocol):
    def render(self, template: str) -> str: ...

class HtmlRenderer:            # no base class
    def render(self, template: str) -> str:
        return f"<html>{template}</html>"

class FakeRenderer:            # test double: just implement the method
    def render(self, template: str) -> str:
        return "stub"

def publish(r: Renderer, tpl: str) -> str:
    return r.render(tpl)

publish(HtmlRenderer(), "x")   # type-checks
publish(FakeRenderer(), "x")   # type-checks too

La especificación de tipado es explícita: un tipo concreto es asignable a un protocolo si y solo si implementa todos los miembros del protocolo — heredar explícitamente está permitido, pero "no es necesario … a efectos de verificación de tipos". Tu fake sigue siendo una clase simple en el archivo de pruebas, y mypy/pyright igual detectan un método renombrado o un tipo de retorno incorrecto.

Reglas prácticas

SituaciónUsa
Costura de prueba para uno o dos métodosProtocol — gana la interfaz mínima
Controlas todas las implementaciones y quieres comportamiento compartidoABC con métodos concretos de ayuda
Clases de terceros deben satisfacer la interfazProtocol (no pueden heredar tu ABC)
Necesitas isinstance() en tiempo de ejecuciónProtocol con @runtime_checkable — pero mira la advertencia abajo
Atributo, no métodoLos protocolos admiten miembros de datos: name: str en el cuerpo

Notas para usuarios avanzados

Recursos

Mira la versión de 60 segundos: reel #127 de la serie Yeda AI Tips. ¿Construyes flujos de ingeniería asistidos por AI? Yeda AI diseña, audita y entrega sistemas LLM de producción.

Habla con nosotros · Lee el blog · Read in English