Pregunta 1¿Cuál es la descripción más precisa del polimorfismo?
Polimorfismo — Mismo nombre de método, comportamiento distinto por tipo
Aprende el polimorfismo en Python. Sobrescribe métodos del padre en las subclases para que quien llama no piense en tipos, y reemplaza ramas if type(...) con POO limpia — todo con diagramas.
La última vez cubrimos la herencia múltiple y el MRO. Para cerrar la serie de POO, este artículo cubre el tercer pilar — el polimorfismo.
¿Qué es el polimorfismo?
El polimorfismo es la idea de que la misma interfaz (nombre de método) puede hacer cosas distintas según el tipo. Supón que quieres una sola operación «calcular salario», pero quieres que empleados, gerentes e ingenieros usen fórmulas distintas.
Define calculate_salary en la clase padre Employee, y haz que las subclases Manager y Engineer la sobrescriban con su propia fórmula. Ahora el código que llama solo escribe employee.calculate_salary() sin preocuparse de qué clase es cuál, y se ejecuta el cálculo correcto.
Employee define calculate_salary. Las subclases Manager / Engineer lo sobrescriben con sus propias fórmulas. Mismo nombre de método, distintos resultados por clase.Construir sobre el padre, cambiar la fórmula en cada hijo
Construyamos el ejemplo del salario de verdad. Employee es el padre, con Manager (extra por tamaño de equipo) y Engineer (extra por nivel de habilidad) como hijos. La clave es que los tres definen un método con el mismo nombre calculate_salary.
class Employee:
def __init__(self, name, base_salary):
self.name = name
self.base_salary = base_salary
def calculate_salary(self): # por defecto = solo salario base
return self.base_salary
class Manager(Employee):
def __init__(self, name, base_salary, team_size):
super().__init__(name, base_salary)
self.team_size = team_size
def calculate_salary(self): # bonus por tamaño de equipo
return self.base_salary + self.team_size * 50000
class Engineer(Employee):
def __init__(self, name, base_salary, skill_level):
super().__init__(name, base_salary)
self.skill_level = skill_level
def calculate_salary(self): # bonus por nivel de habilidad
return self.base_salary + self.skill_level * 20000
emp — esa es la fuerza del polimorfismo.Agrupar en una lista — Un bucle al que no le importan los tipos
El polimorfismo realmente muestra su fuerza cuando metes objetos de tipos distintos en una sola lista y los procesas todos juntos. Envuelve el cálculo de salario en una clase PayrollSystem y el cuerpo del bucle se reduce a una sola línea.
class PayrollSystem:
def __init__(self):
self.employees = []
def add(self, employee):
self.employees.append(employee)
def total(self):
result = 0
for emp in self.employees: # los tipos pueden variar
result += emp.calculate_salary() # el mismo nombre funciona
return result
payroll = PayrollSystem()
payroll.add(Employee("Ana", 300000))
payroll.add(Manager("Carlos", 800000, 8))
payroll.add(Engineer("María", 300000, 4))
print(payroll.total()) # 1880000
employees = [...](tipos mezclados)for emp in self.employees:los recorre- Solo llama emp.calculate_salary()
- retorna base_salary
- base + team * 50k
- base + skill * 20k
PayrollSystem solo llama al mismo nombre de método y se ejecuta el cálculo correcto por tipo.Cómo se ve sin polimorfismo
Intenta hacer lo mismo sin polimorfismo y acabas escribiendo ramas if type(emp) == ...:. Funciona, pero cada nuevo rol significa otra rama if — y en el momento que olvidas una, tienes un bug.
# (MAL) sin polimorfismo (rama por tipo)
def calc(emp):
if type(emp) is Manager:
return emp.base_salary + emp.team_size * 50000
elif type(emp) is Engineer:
return emp.base_salary + emp.skill_level * 20000
else:
return emp.base_salary
# (BIEN) con polimorfismo (lógica empujada a las clases)
def calc(emp):
return emp.calculate_salary() # una línea
if type en quien llama y crece con cada nuevo tipo. El polimorfismo mantiene a quien llama en una sola línea — añadir un nuevo tipo significa solo añadir una clase.«A quien llama no le importan los tipos» — ese es el mantra
Una prueba fiable de si tu diseño es polimórfico: ¿el código de quien llama tiene pilas de if type(...) o if isinstance(...)? Si sí, la refactorización estándar es mover ese branching a overrides de métodos en las clases. «Añadir una clase» y «añadir un if» tienden a ser un trade-off.
Duck typing — Solo necesitas el mismo nombre de método
El polimorfismo de Python tiene un sabor más permisivo: el duck typing. El dicho «si camina como un pato y grazna como un pato, es un pato» se convierte en «si una clase tiene el método correcto, no importa qué clase sea».
En el ejemplo de abajo, Cat y Dog no comparten un padre Animal para nada — pero mientras ambos tengan un método speak(), la misma función maneja ambos. Python prioriza «¿tiene el método?» sobre el grafo de herencia.
class Cat:
def speak(self):
return "Miau"
class Dog:
def speak(self):
return "Guau"
def shout(animal): # el tipo no se impone
print(animal.speak())
shout(Cat()) # Miau
shout(Dog()) # Guau
Lenguajes como Java o C# requieren una clase padre compartida para que el polimorfismo funcione. Python está contento mientras el método esté en el momento de la llamada. Eso te da flexibilidad, pero «asegurarse de que los nombres de método signifiquen lo mismo entre clases» se vuelve responsabilidad de quien llama — tenlo en cuenta.
Los dos diseños en una sola diapositiva
- Código de quien llama — una línea
emp.calculate_salary() - Añadir un nuevo tipo — solo escribe una nueva clase con
calculate_salary - Radio de impacto — se queda dentro de las clases
- Legibilidad — basta con leer «mismo nombre, varía por tipo»
- Código de quien llama — pila de
if type(emp) is ... - Añadir un nuevo tipo — cada rama necesita revisión
- Radio de impacto — se filtra a quien llama
- Legibilidad — relees las ramas cada vez
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2El polimorfismo tiende a eliminar qué estructura del código de quien llama?
Pregunta 3¿Cuál es la mejor descripción del duck typing de Python?