- ¿Qué es el polimorfismo?
- Pero, ¿cómo entra la herencia en todo esto?
- Ejemplo: Un despachador de métodos
- ¿Qué diferencia hay entre enlace tardío y despacho dinámico?
En lecciones anteriores ya hablamos sobre el polimorfismo y cómo funciona en Python. Ahora que hemos pasado la mitad del curso, es buen momento para profundizar más: vamos a ver cómo la herencia se conecta con el polimorfismo y cómo esto le da más poder y flexibilidad a nuestro código.
¿Qué es el polimorfismo?
Hagamos un pequeño repaso. El polimorfismo nos permite usar objetos de diferentes clases como si fueran del mismo tipo, siempre que compartan ciertos métodos. Es decir, podemos tratar a distintos objetos de manera uniforme si tienen las mismas interfaces (métodos con el mismo nombre).
Esto es útil para escribir código más general, que funcione con muchos tipos de objetos.
Pero, ¿cómo entra la herencia en todo esto?
Es importante saber que el polimorfismo en Python NO depende de la herencia. Sin embargo, la herencia sí tiene un papel especial en cómo se resuelven los métodos cuando usamos polimorfismo.
Pensemos un momento: si tenemos una jerarquía de clases (una clase base y otras que heredan de ella), cuando Python necesita ejecutar un método, comienza a buscarlo desde la clase del objeto. Si no lo encuentra allí, sigue buscando en sus clases padre, subiendo por la cadena de herencia.
Ejemplo: Un despachador de métodos
Este código simula lo que Python hace por debajo al llamar a un método:
class Dispatcher:
def call_method(self, obj, method_name, *args):
# Obtenemos todos los métodos que tiene el objeto
methods = [method for method in dir(obj) if callable(getattr(obj, method))]
if method_name in methods:
# Si existe el método, lo llamamos
getattr(obj, method_name)(*args)
elif method_name not in methods and '__call__' in methods:
# Si no existe el método, pero el objeto es "llamable", lo invocamos
obj.__call__(*args)
else:
raise Exception('No method error')
Este código busca el método por nombre (como get_name) en el objeto. Si no lo encuentra pero el objeto tiene un método especial call(), entonces lo usa como último recurso.
Ahora viene lo importante: esta búsqueda no se queda solo en la clase del objeto. Si hay herencia, Python sube por la jerarquía (es decir, busca en la clase madre, luego en la abuela, y así).
❓ ¿Cuál es el problema con call?
__call__() es costoso de usar. Si está definido muy arriba en la jerarquía de clases (por ejemplo, en una clase base de muchas otras), puede volver lento tu código, ya que Python tendrá que hacer más pasos para encontrarlo.
¿Qué diferencia hay entre enlace tardío y despacho dinámico?
Hay dos términos que suenan parecidos, pero que no significan lo mismo, y vale la pena aclararlos:
| Concepto | ¿Qué hace? |
|---|---|
| Enlace (binding) | Asocia una variable con un valor o tipo |
| Enlace tardío (late binding) | Se hace durante la ejecución del programa |
| Despacho (dispatch) | Selecciona qué función o método ejecutar dependiendo del tipo del objeto |
| Despacho dinámico | Esta selección también se hace en tiempo de ejecución |
Ejemplo de enlace tardío
En Python, incluso podemos usar una función antes de definirla (en algunos contextos), gracias a que Python no revisa todo el código antes de ejecutarlo.
Un ejemplo más común de enlace tardío es el uso de self:
class Animal:
def hacer_sonido(self):
print("Sonido genérico")
class Perro(Animal):
def hacer_sonido(self):
print("Guau!")
def reproducir_sonido(animal):
animal.hacer_sonido()
mi_perro = Perro()
reproducir_sonido(mi_perro) # Aunque no sabemos el tipo exacto de "animal", Python lo resuelve en ejecución
El tipo exacto de mi_perro se conoce solamente en tiempo de ejecución, y ahí es cuando se ejecuta el hacer_sonido() correcto. Eso es enlace tardío y también despacho dinámico.
Resumen
- El polimorfismo en Python no requiere herencia, pero trabajan bien juntos.
- Herencia permite que Python siga buscando métodos en la jerarquía de clases si no los encuentra en la clase original.
__call__()es un método especial que se usa como último recurso si no se encuentra el método que buscamos.- El uso de
__call__()puede afectar el rendimiento si está muy arriba en la herencia. - Enlace tardío significa que Python decide qué objeto o método usar en el momento en que se ejecuta el código.
- Despacho dinámico es cuando Python decide qué versión del método llamar, dependiendo del tipo real del objeto.
- Ambas características permiten que Python sea muy flexible, pero es clave comprender cómo funcionan para evitar errores y escribir mejor código.
Materiales adicionales
Para acceder completo a curso necesitas un plan básico
El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.