Herencia y polimorfismo
¿Qué es?
La herencia permite crear una clase nueva a partir de otra existente, heredando sus atributos y métodos y añadiendo o cambiando lo que haga falta. La clase de la que se hereda es la clase base (o superclase); la nueva es la subclase.
El polimorfismo es la capacidad de tratar objetos de distintas clases de la misma manera, siempre que compartan una interfaz común. Si varios objetos responden al método descripcion(), puedo recorrerlos a todos y llamar descripcion() sin saber de qué clase es cada uno.
¿Cómo funciona?
Para heredar, escribes class Subclase(Base):. La subclase recibe todo lo de la base. Con super().__init__(...) llamas al constructor de la base para no repetir su inicialización. Para cambiar un comportamiento heredado, redefines (sobrescribes) el método en la subclase. El polimorfismo aparece solo: si distintas clases tienen un método con el mismo nombre, Python ejecuta el que corresponde al objeto real en tiempo de ejecución.
¿Para qué sirve?
Sirve para reutilizar comportamiento común sin copiar y pegar, y para escribir código que funciona con familias enteras de objetos. En el sistema de notas tendremos distintos tipos de estudiante (regular, becado) que comparten casi todo pero difieren en un detalle.
Qué pieza del sistema construimos
Construimos una jerarquía: una clase base Persona con nombre, y dos subclases de estudiante, EstudianteRegular y EstudianteBecado, que comparten el manejo de notas pero calculan distinto su estado académico. Luego generamos un reporte polimórfico que recorre cualquier mezcla de estudiantes.
Una clase base común
class Persona:
def __init__(self, nombre):
self.nombre = nombre
def presentarse(self):
return f"Soy {self.nombre}"
Heredando y usando super()
Creamos Estudiante como subclase de Persona. Reutilizamos su __init__ con super().
class Estudiante(Persona):
def __init__(self, nombre):
super().__init__(nombre) # inicializa lo de Persona (self.nombre)
self.notas = []
def agregar_nota(self, valor):
self.notas.append(valor)
@property
def promedio(self):
if not self.notas:
return 0.0
return sum(self.notas) / len(self.notas)
def estado(self):
return "aprobado" if self.promedio >= 3.0 else "reprobado"
super().__init__(nombre) ejecuta el constructor de Persona, que asigna self.nombre. Así no repetimos esa línea y, si Persona cambia, la subclase se beneficia automáticamente.
Sobrescribiendo comportamiento
Un estudiante becado necesita un promedio más alto para conservar el estado de aprobado. Heredamos casi todo y solo redefinimos estado.
class EstudianteBecado(Estudiante):
def __init__(self, nombre, minimo=3.5):
super().__init__(nombre)
self.minimo = minimo
def estado(self): # sobrescribe el de Estudiante
return "aprobado" if self.promedio >= self.minimo else "en riesgo"
EstudianteBecado hereda agregar_nota y promedio tal cual; solo cambia estado. Llamamos super().__init__(nombre) para reutilizar la inicialización del estudiante y luego añadimos su atributo propio minimo.
Polimorfismo en acción
Ahora lo bonito: una sola función genera el reporte sin importar el tipo concreto.
def reporte(estudiantes):
for est in estudiantes:
print(f"{est.presentarse()} | promedio {est.promedio:.2f} | {est.estado()}")
ana = Estudiante("Ana")
ana.agregar_nota(4.0)
luis = EstudianteBecado("Luis")
luis.agregar_nota(3.2)
reporte([ana, luis])
# Soy Ana | promedio 4.00 | aprobado
# Soy Luis | promedio 3.20 | en riesgo
La función reporte no pregunta "¿eres regular o becado?". Simplemente llama est.estado() y Python ejecuta la versión correcta para cada objeto. Eso es polimorfismo.
Esto se conoce como duck typing: "si camina como pato y suena como pato, es un pato". A Python no le importa la clase exacta; le importa que el objeto tenga los métodos que vas a usar (presentarse, promedio, estado).
Error común: olvidar super().__init__(...) en la subclase. Si lo omites, los atributos de la base (como self.nombre) nunca se crean y obtienes un AttributeError al usarlos. Siempre llama al constructor de la base cuando defines tu propio __init__.
Tech English: inheritance = herencia, override = sobrescribir, base class / subclass = clase base / subclase, duck typing = tipado pato.
Ejercicios
-
Tercer tipo de estudiante. Diseña
EstudianteOyente, que hereda deEstudiantepero cuyoestado()siempre devuelve"sin evaluación"(no aprueba ni reprueba). Demuestra que la funciónreportelo procesa sin cambiar ni una línea. -
Método extendido, no reemplazado. Diseña una subclase de
PersonallamadaProfesorcuyopresentarse()reutilice el de la base consuper().presentarse()y le añada" y soy profesor". Explica la diferencia entre extender y reemplazar un método.