Tipado de Pato y el Modelo de Datos
Los patrones de Python no son una bolsa aleatoria de trucos.
Busca en todas las páginas de la documentación
Los patrones de Python no son una bolsa aleatoria de trucos.
Casi todas las páginas de esta sección: EAFP, gestores de contexto, estrategia y despacho, inyección de dependencias, se basan en la misma idea subyacente, tipado de pato, aplicada a un problema recurrente diferente.
El tipado de pato significa que a Python le importa lo que un objeto puede hacer, no a qué clase dice pertenecer, y el modelo de datos (el sistema de métodos __dunder__) es cómo los objetos ordinarios optan por ese comportamiento para la sintaxis incorporada como with, for, + y len().
Conceptos básicos de patrones de Python repasa ejemplos concretos; EAFP vs LBYL, Gestores de contexto como patrón, Los patrones de estrategia y despacho y Inyección de dependencias en Python cubren en profundidad una forma de esa idea.
Esta página es la capa debajo de todas ellas: por qué los patrones de Python se ven y se sienten diferentes a los patrones en un lenguaje sin tipado de pato.
Protocol), el patrón de estrategia.La frase tipado de pato proviene del dicho "si camina como un pato y grazna como un pato, es un pato"; en términos de código, un objeto se trata como utilizable para un rol en el momento en que tiene los métodos y atributos correctos, independientemente de su clase o ascendencia real.
Esto es diferente de los lenguajes que verifican una interfaz o clase base declarada antes de permitir que un objeto se use en algún lugar; Python simplemente llama al método y lo averigua.
Una breve ilustración lo hace concreto: cualquier objeto con un método .read() puede usarse donde el código espera "algo similar a un archivo", ya sea un archivo real, un búfer en memoria o un envoltorio de socket de red, sin necesidad de una clase base compartida.
def read_all(source) -> str:
return source.read() # funciona para File, io.StringIO, un envoltorio de socket - cualquier cosa con .read()El tipado de pato por sí solo explica la interoperabilidad ad hoc, pero Python va un paso más allá con el modelo de datos: un conjunto documentado de métodos __dunder__ que permiten que una clase personalizada se enganche directamente a la sintaxis incorporada en lugar de solo a llamadas a métodos simples.
Define __enter__/__exit__ y tu objeto funciona con with.
Define __iter__/__next__ y funciona con for.
Define __add__ y el operador + funciona sobre él.
El modelo de datos es lo que convierte el tipado de pato de "cualquier cosa con el método correcto funciona" a "cualquier cosa con el método correcto funciona, y ese método puede ser activado por sintaxis ordinaria en lugar de una llamada explícita".
Una vez que ves el tipado de pato más el modelo de datos como la base, la mayoría de los patrones nombrados de esta sección se leen como aplicaciones del mismo truco a una forma de problema recurrente específica, en lugar de técnicas independientes para memorizar por separado.
EAFP es tipado de pato aplicado al manejo de errores: en lugar de verificar de antemano si un objeto admite una operación (hasattr, isinstance), simplemente intentas la operación y capturas el fallo específico, porque Python no requiere que demuestres compatibilidad antes de intentarlo.
Los gestores de contexto son tipado de pato aplicado al ciclo de vida de los recursos: cualquier objeto que implemente __enter__/__exit__ puede usarse con with, por lo que el intérprete nunca necesita saber si es un archivo, un bloqueo, una transacción de base de datos o un monkey-patch temporal; solo necesita que esos dos métodos existan.
Estrategia y despacho son tipado de pato aplicado a comportamientos intercambiables: una función simple, o un diccionario que mapea claves a llamables, satisface "la interfaz de estrategia" simplemente siendo llamable con la firma correcta, sin necesidad de una clase base Strategy en ninguna parte.
La inyección de dependencias en Python suele ser más ligera que en lenguajes con tipos nominales por la misma razón: un parámetro de constructor tipado como Protocol (o sin tipar en código más simple) acepta cualquier cosa con la forma correcta, por lo que un falso, un stub y la implementación real son intercambiables sin una cadena de herencia compartida.
Patrón de lenguaje nominal Equivalente en Python con tipado de pato
------------------------------ -------------------------------------------
interface Strategy { run(): X } cualquier llamable con la firma correcta
class ConcreteStrategy una función simple o un diccionario de llamables
implements Strategy { ... }
context.setStrategy(new C()) context.strategy = mi_funcion
Ese diagrama es la forma recurrente en toda esta sección: donde otro lenguaje necesita una declaración de interfaz explícita para hacer segura la sustitución, Python solo necesita que el método o la firma de llamada correctos existan en el momento en que se usan.
Esto también explica por qué varios patrones de diseño clásicos de Gang-of-Four se sienten sobre-diseñados cuando se portan literalmente a Python.
El propósito principal del patrón Strategy en un lenguaje con tipos nominales es definir una interfaz para que las estrategias concretas sean intercambiables; pero las funciones de Python y los diccionarios de llamables ya te brindan comportamiento intercambiable de forma gratuita, por lo que una jerarquía completa de clases Strategy generalmente resuelve un problema que el tipado de pato ya resolvió.
Lo mismo se aplica a partes de Factory y Observer: las funciones de primera clase de Python, los cierres y el modelo de datos absorben gran parte de lo que esos patrones existen para solucionar en lenguajes sin ellos.
Esto no significa que los patrones sean inútiles; Patrones Factory y Builder y Patrones Observer y Pub/Sub cubren usos reales y legítimos, pero explica por qué una implementación idiomática de Python de un "patrón" a menudo parece más pequeña y menos ceremoniosa que la versión de libro de texto.
El costo honesto del tipado de pato se manifiesta en el límite entre "flexible" y "no verificado": debido a que la compatibilidad se verifica por uso en lugar de por declaración, un objeto incompatible puede viajar profundamente en una pila de llamadas antes de fallar, a menudo con un error menos obvio que una violación de interfaz en tiempo de compilación.
EAFP vs LBYL cubre la gestión de ese costo para operaciones individuales, y el tipado estructural a través de Protocol (cubierto en la sección de type hints) recupera parte de la verificación anticipada que ofrece un sistema de tipos nominal, sin abandonar la flexibilidad del tipado de pato en tiempo de ejecución.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| Tipado de pato (implícito) | Cero ceremonia; funciona con cualquier objeto compatible independientemente de su origen | Las incompatibilidades surgen tarde, a veces en lo profundo de una pila de llamadas | Scripts pequeños, código exploratorio, unión de objetos no relacionados |
| Modelo de datos (métodos dunder) | Se integra con la sintaxis incorporada (with, for, +) que los usuarios ya conocen | Fácil de implementar sutilmente mal (por ejemplo, un __exit__ con fugas) | Tipos personalizados que deberían sentirse como incorporados |
Protocol (tipado estructural) | El verificador estático detecta desajustes de forma antes del tiempo de ejecución | Requiere type hints y un verificador en el flujo de trabajo | APIs públicas y equipos más grandes que desean la flexibilidad del tipado de pato con retroalimentación más temprana |
| ABC / herencia explícita | Descubrible, forzado en el momento de la instanciación | Fuerza una relación de herencia que puede no encajar conceptualmente | Tu propia jerarquía de clases con una relación genuina es-un |
__enter__/__exit__ o __iter__ es la forma sancionada de hacer que un tipo personalizado se comporte como uno incorporado.Un objeto es utilizable para un rol basándose en los métodos y atributos que tiene en el momento en que se usa, no basándose en su clase declarada o en una interfaz explícita que implementa.
El conjunto documentado de métodos __dunder__ de Python (__enter__, __iter__, __add__, y muchos más) que permiten que una clase personalizada se enganche a la sintaxis incorporada ( with, for, operadores), en lugar de requerir una llamada explícita a un método.
Están relacionados pero son distintos. El tipado de pato es el principio general de que el comportamiento, no el tipo declarado, determina la usabilidad. El modelo de datos es el mecanismo específico que Python proporciona para que los objetos participen en la sintaxis incorporada bajo ese principio.
Porque el tipado de pato y las funciones de primera clase ya resuelven parte de lo que esos patrones existen para solucionar: una interfaz explícita o una jerarquía de clases para la intercambiabilidad a menudo es innecesaria cuando cualquier objeto o función con la forma correcta ya califica.
No; Protocol le da al tipado de pato una forma estructural y verificable: un verificador estático verifica que la forma correcta exista antes del tiempo de ejecución, sin forzar la herencia. Es tipado de pato con retroalimentación más temprana, no un reemplazo.
Debido a que el tipado de pato pospone "esto funciona" hasta el momento de uso, EAFP es la contraparte natural del manejo de errores: intenta la operación y, si el objeto realmente no la admite, captura la excepción específica en lugar de verificar de antemano.
with funciona en cualquier cosa que implemente __enter__ y __exit__, independientemente de lo que sea el objeto; un archivo, un bloqueo, una transacción de base de datos; eso es tipado de pato aplicado específicamente al ciclo de vida de los recursos.
Sí, cuando hay una relación genuina es-un que deseas modelar y aplicar; la herencia y el tipado de pato no son opuestos, y los programas de Python los usan regularmente donde cada uno encaja.
Un objeto incompatible no se rechaza de inmediato; puede pasarse y usarse parcialmente antes de que una llamada incompatible finalmente genere AttributeError o TypeError, a veces lejos de donde se originó la incompatibilidad.
Porque una función simple o un diccionario que mapea nombres a llamables ya te brinda comportamiento intercambiable; la jerarquía de clases que usa el patrón Strategy en otros lenguajes existe específicamente para sortear la ausencia de funciones de primera clase y tipado de pato allí.
CPython tiene rutas rápidas y especializadas para métodos dunder comunes utilizados por la sintaxis incorporada (como __len__ para len()), por lo que implementarlos correctamente generalmente no es una preocupación de rendimiento en comparación con una llamada a método explícita equivalente.
Dado que cualquier objeto con la forma de método correcta satisface una dependencia, la inyección de un falso o stub para pruebas no necesita una clase base compartida con la implementación real; solo una interfaz coincidente, que el tipado de pato (opcionalmente verificada a través de Protocol) ya proporciona.
Versiones de Stack: Esta página es conceptual y no está vinculada a una versión específica de stack.
Revisado por Chris St. John·Última actualización: 19 jul 2026