Clase 19: POO - Herencia
Herencia: Reutilizar y Extender Código
¿Qué es la herencia?
La herencia es un mecanismo que permite crear una nueva clase a partir de una ya existente. La clase nueva (llamada hija o subclase) obtiene automáticamente todos los atributos y métodos de la clase original (llamada padre o superclase).
Piensa en la herencia como en la biología: un hijo hereda características de sus padres (color de ojos, altura), pero también tiene sus propias características únicas.
¿Para qué sirve?
- Evita duplicar código: Si varias clases comparten comportamientos, los escribes una sola vez en la clase padre.
- Facilita el mantenimiento: Un cambio en la clase padre se refleja en todas las hijas.
- Permite especializar: Cada hija puede agregar o modificar lo que necesite.
Sintaxis en Python
class Padre:
def __init__(self, atributo_comun):
self.atributo_comun = atributo_comun
def metodo_comun(self):
print("Esto lo heredan todos")
class Hija(Padre): # ← Los paréntesis indican herencia
def __init__(self, atributo_comun, atributo_propio):
super().__init__(atributo_comun) # ← Llamamos al __init__ del padre
self.atributo_propio = atributo_propio
def metodo_propio(self):
print("Esto solo lo tiene la hija")
Entendiendo super() — La clave de la herencia
super() significa “llama al método de la clase padre”. Es la forma de decir: “Antes de hacer mi cosa, deja que el padre haga la suya”.
Ejemplo paso a paso:
class Vehiculo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
print(f"🚗 Vehículo creado: {marca} {modelo}")
class Auto(Vehiculo):
def __init__(self, marca, modelo, cantidad_puertas):
# 1. Primero, el padre se inicializa
super().__init__(marca, modelo)
# 2. Después, la hija agrega lo suyo
self.cantidad_puertas = cantidad_puertas
print(f"🚙 Auto creado con {cantidad_puertas} puertas")
# Probemos:
mi_auto = Auto("Toyota", "Corolla", 4)
Salida:
🚗 Vehículo creado: Toyota Corolla
🚙 Auto creado con 4 puertas
¿Por qué usar
super()y noVehiculo.__init__(self, ...)?
super()es más limpio y funciona correctamente con herencia múltiple.- Si cambias el nombre de la clase padre, no tienes que buscar y reemplazar en todas las hijas.
Ejemplo completo: Sistema de usuarios escolares
# ===== CLASE PADRE =====
class Usuario:
def __init__(self, nombre, email, documento):
self.nombre = nombre
self.email = email
self.documento = documento
def mostrar_info(self):
print(f"👤 {self.nombre}")
print(f" Email: {self.email}")
print(f Documento: {self.documento}")
def saludar(self):
print(f"Hola, soy {self.nombre}")
# ===== CLASES HIJAS =====
class Estudiante(Usuario):
def __init__(self, nombre, email, documento, legajo, curso):
super().__init__(nombre, email, documento) # Inicializa lo común
self.legajo = legajo
self.curso = curso
self.materias = [] # Lista vacía para inscribir materias
def inscribir_materia(self, materia):
self.materias.append(materia)
print(f"✅ {self.nombre} inscrito en {materia}")
def mostrar_info(self): # SOBRESCRITURA (override)
super().mostrar_info() # Primero muestra lo del padre
print(f" Legajo: {self.legajo}")
print(f" Curso: {self.curso}")
print(f" Materias: {', '.join(self.materias) if self.materias else 'Ninguna'}")
class Profesor(Usuario):
def __init__(self, nombre, email, documento, empleado_id, especialidad):
super().__init__(nombre, email, documento)
self.empleado_id = empleado_id
self.especialidad = especialidad
self.materias_dictadas = []
def asignar_materia(self, materia):
self.materias_dictadas.append(materia)
print(f"📚 {self.nombre} dictará {materia}")
def mostrar_info(self): # SOBRESCRITURA
super().mostrar_info()
print(f" Empleado ID: {self.empleado_id}")
print(f" Especialidad: {self.especialidad}")
print(f" Materias: {', '.join(self.materias_dictadas) if self.materias_dictadas else 'Ninguna'}")
class Admin(Usuario):
def __init__(self, nombre, email, documento, nivel_acceso):
super().__init__(nombre, email, documento)
self.nivel_acceso = nivel_acceso # 'basico', 'medio', 'total'
def puede_borrar_usuarios(self):
return self.nivel_acceso == 'total'
def mostrar_info(self):
super().mostrar_info()
print(f" Nivel de acceso: {self.nivel_acceso}")
Probando el sistema
# Creamos objetos de cada tipo
estudiante = Estudiante("Ana García", "ana@email.com", "12345678", "L-2024-001", "6° Año")
profesor = Profesor("Carlos López", "carlos@email.com", "87654321", "EMP-001", "Matemáticas")
admin = Admin("María Admin", "maria@email.com", "11223344", "total")
print("=== ESTUDIANTE ===")
estudiante.mostrar_info()
estudiante.inscribir_materia("Matemáticas")
estudiante.inscribir_materia("Historia")
print("\n=== PROFESOR ===")
profesor.mostrar_info()
profesor.asignar_materia("Matemáticas")
profesor.asignar_materia("Física")
print("\n=== ADMIN ===")
admin.mostrar_info()
print(f"¿Puede borrar usuarios? {admin.puede_borrar_usuarios()}")
Salida:
=== ESTUDIANTE ===
👤 Ana García
Email: ana@email.com
Documento: 12345678
Legajo: L-2024-001
Curso: 6° Año
Materias: Ninguna
✅ Ana García inscrito en Matemáticas
✅ Ana García inscrito en Historia
=== PROFESOR ===
👤 Carlos López
Email: carlos@email.com
Documento: 87654321
Empleado ID: EMP-001
Especialidad: Matemáticas
Materias: Ninguna
📚 Carlos López dictará Matemáticas
📚 Carlos López dictará Física
=== ADMIN ===
👤 María Admin
Email: maria@email.com
Documento: 11223344
Nivel de acceso: total
¿Puede borrar usuarios? True
Sobrescritura de métodos (Override)
Cuando una clase hija define un método con el mismo nombre que la clase padre, reemplaza el comportamiento del padre. Esto se llama sobrescritura.
Reglas de oro:
- Siempre usa
super().metodo()si quieres mantener lo que hace el padre Y agregar más. - No uses
super()si quieres reemplazar completamente el comportamiento.
class Animal:
def hablar(self):
print("El animal hace un sonido")
class Perro(Animal):
def hablar(self): # Sobrescritura COMPLETA (sin super)
print("🐕 Guau guau")
class Gato(Animal):
def hablar(self): # Sobrescritura EXTENDIDA (con super)
super().hablar() # Primero lo del padre
print("🐱 Miau")
perro = Perro()
gato = Gato()
perro.hablar() # Solo: "🐕 Guau guau"
gato.hablar() # Primero: "El animal hace un sonido", luego: "🐱 Miau"
Herencia múltiple (breve mención)
Python permite heredar de varias clases a la vez. Se usa poco, pero es útil para mixins (clases que agregan una funcionalidad específica).
class Volador:
def volar(self):
print("🕊️ Volando...")
class Nadador:
def nadar(self):
print("🏊 Nadando...")
class Pato(Volador, Nadador): # Hereda de AMBOS
def graznar(self):
print("🦆 Cuac cuac")
pato = Pato()
pato.volar() # De Volador
pato.nadar() # De Nadador
pato.graznar() # Propio
Consejo: Prefiere composición (tener objetos dentro de otros) antes que herencia múltiple. Es más claro y evita problemas complejos.
Cuándo usar herencia vs composición
| Situación | Qué usar |
|---|---|
| “Un Auto ES UN Vehículo” | ✅ Herencia |
| “Un Auto TIENE UN Motor” | ✅ Composición |
| “Un Estudiante ES UN Usuario” | ✅ Herencia |
| “Un Estudiante TIENE UNA Mochila” | ✅ Composición |
Regla práctica: Si la relación es “ES UN” (es una especialización), usa herencia. Si es “TIENE UN” (es una parte), usa composición.
Resumen de la clase
| Concepto | Qué hace |
|---|---|
class Hija(Padre) |
La hija hereda todo del padre |
super().__init__(...) |
Llama al constructor del padre |
super().metodo() |
Llama a un método del padre |
| Sobrescritura | La hija redefine un método del padre |
| Herencia múltiple | Una clase hereda de varias (usar con cuidado) |
Actividades
-
Extender la clase
Alumnode la clase 18Crea dos subclases:
AlumnoBecadoyAlumnoIntercambio.Requisitos para ambas:
- Usar
super().__init__()para inicializar atributos heredados - Agregar al menos 2 atributos propios cada una
- Sobrescribir
mostrar_info()usandosuper().mostrar_info()y agregando lo propio - Agregar al menos 1 método propio cada una
Ideas de atributos:
AlumnoBecado:tipo_beca(completa/parcial),requisitos_mantenimientoAlumnoIntercambio:pais_origen,duracion_meses,idioma_nativo
- Usar
-
Probar polimorfismo básico
Crea una lista con 3 objetos: un
Alumno, unAlumnoBecadoy unAlumnoIntercambio. Recorre la lista y llama amostrar_info()en cada uno. Observa cómo cada uno muestra su información específica. -
Pregunta reflexiva (escribir en comentarios)
¿Qué pasaría si
AlumnoBecadoheredara deAlumnoIntercambioen lugar deAlumno? ¿Tiene sentido? ¿Por qué sí o por qué no? -
Desafío opcional: Mixin
SerializableCrea una clase
Serializablecon un métodoto_dict()que devuelva un diccionario con los atributos del objeto. Haz queAlumnoherede deSerializable(herencia múltiple) y prueba llamar aalumno.to_dict().
o/