29 · Polimorfismo y duck typing
Mismo nombre de método, comportamiento distinto — cómo el duck typing de Python permite que cualquier objeto encaje en un pipeline siempre que tenga los métodos correctos.
**Polimorfismo** significa que la misma llamada a función funciona en diferentes tipos porque cada tipo implementa el método esperado. Python va más lejos con el **duck typing**: si un objeto tiene `.predict()`, puede ir a cualquier función `evaluate()` que llame a `.predict()` — no se requiere clase base compartida. "Si camina como un pato y grazna como un pato, es un pato."
Sin esto:
Sin polimorfismo escribirías una función `evaluate_linear`, `evaluate_quadratic`, `evaluate_rf` separada para cada tipo de modelo. Con él, un único `evaluate(model, xs)` funciona para todos — toda la API de pipelines de sklearn está construida sobre esta idea.
En un lenguaje clásico (Java, C++) el polimorfismo requiere una clase base o interfaz común. En Python es más simple: si llamas a model.predict(x) y el objeto tiene un método .predict, Python lo ejecuta — independientemente del tipo del objeto. Esto es el duck typing.
La función evaluate que sigue funciona con cualquier objeto que tenga un método .predict(x): un modelo lineal, un cuadrático, un random forest, un motor de reglas artesanal. No se requiere herencia. Así es exactamente como funciona Pipeline.fit_transform de sklearn — llama a .fit(X) y .transform(X) en cada paso sin importar si cada paso hereda de una clase base específica.
isinstance() es útil ocasionalmente (para protegerse contra tipos genuinamente incompatibles), pero recurrirle demasiado a menudo es un code smell — significa que estás peleando contra el duck typing en lugar de abrazarlo. La alternativa moderna para soporte del verificador de tipos es typing.Protocol: declara la interfaz que necesitas (las firmas de método requeridas) y el verificador de tipos comprueba cualquier objeto que pase el protocolo.
Python (in browser)
`evaluate` nunca comprueba `type(model)` ni `isinstance(model, AlgunaBase)` — simplemente llama a `.predict(x)`. Python lanzará `AttributeError` si falta el método, lo cual es una señal clara de que no se cumplió el contrato. Esto es idiomático; el contrato se documenta por convención, no se impone por el sistema de tipos en tiempo de ejecución.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
`hasattr(obj, 'predict')` comprueba si el método existe sin requerir un tipo específico. Este es el guard amigable con el duck typing. `isinstance()` se reserva mejor para dispatch genuino de tipos (por ejemplo, manejar rutas de entrada tanto de `str` como de `bytes`).
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
`Protocol` define una interfaz **estructural**: cualquier clase que tenga `predict(self, x: float) -> float` satisface `Predictor` en tiempo de verificación de tipos — sin herencia explícita requerida. Esta es la forma moderna de expresar duck typing en código con anotaciones de tipos. Compara con el `interface Predictor` de Java que requiere `implements` en el punto de uso.
¿Qué significa "duck typing" en Python?
- Polimorfismo: mismo nombre de método, comportamiento distinto por clase. Python lo logra con duck typing — si el método existe, funciona, independientemente de la jerarquía de clases.
- `hasattr(obj, 'nombre_metodo')` es el guard amigable con el duck typing. `isinstance()` es útil ocasionalmente pero puede ser un code smell si se usa en exceso — pelea contra el duck typing.
- `typing.Protocol` te permite expresar una interfaz estructural para los verificadores de tipos sin requerir herencia explícita en el punto de uso.
La interfaz `.fit()` / `.predict()` / `.transform()` / `.score()` de sklearn es un contrato con duck typing — cualquier objeto que implemente estos métodos funciona en un `Pipeline` o una llamada a `cross_val_score`. HuggingFace Transformers expone `.generate()` en docenas de arquitecturas de modelos (GPT-2, T5, BLOOM) con la misma firma de llamada — los callers nunca comprueban qué arquitectura tienen. El `DataLoader` de PyTorch llama a `dataset.__len__()` y `dataset.__getitem__(idx)` en lo que sea que pases como `dataset`.
Si lo quitas: Sin duck typing, los pipelines de sklearn requerirían que cada paso subclasifique explícitamente una clase base — el ecosistema comunitario de transformers y estimadores plug-in sería un orden de magnitud más pequeño.