Apariencia
📙 Clase 15 — Herencia
Fase 3 · Programación Orientada a Objetos ⬅️ Volver al índice de clases
🎯 Qué aprendí
- Una clase que hereda de otra: recibe gratis sus atributos y métodos.
super().__init__()para no repetir el constructor del padre, y override de métodos.- El uso que cambia tu vida de desarrollador de escritorio:
class App(ctk.CTk).
📖 PARTE TEÓRICA
👨👦 1. Heredar = recibir todo lo del padre
class Hija(Padre) crea una clase que ya tiene todo lo del padre, y puede agregar o cambiar lo suyo.
python
class Animal: # clase PADRE (o "base")
def __init__(self, nombre):
self.nombre = nombre
def presentarse(self):
return f"Soy {self.nombre}"
def hablar(self):
return "..."
class Perro(Animal): # Perro HEREDA de Animal
def __init__(self, nombre, raza):
super().__init__(nombre) # reusar el __init__ del padre
self.raza = raza # y agregar lo propio
def hablar(self): # OVERRIDE: reemplaza la versión del padre
return "¡Guau!"
p = Perro("Toby", "beagle")
print(p.presentarse()) # Soy Toby ← heredado de Animal, gratis
print(p.hablar()) # ¡Guau! ← la versión de Perro (override)
print(p.raza) # beagle ← lo propio
print(isinstance(p, Animal)) # True ← un Perro ES-UN Animal Animal ┌─ hereda TODO
┌───────────────┐ │
│ nombre │ ▼
│ presentarse() │────▶ Perro(Animal)
│ hablar() "…" │ │ + raza
└───────────────┘ │ hablar() "¡Guau!" ← override🔑 2. super(): hablar con el padre
super() te da acceso a la versión del padre de un método. Su uso número 1 es en __init__: primero deja que el padre se construya, luego agrega lo tuyo.
python
def __init__(self, nombre, raza):
super().__init__(nombre) # ① el padre inicializa lo suyo (self.nombre)
self.raza = raza # ② yo agrego lo mío⚠️ Si defines
__init__en la hija y olvidassuper().__init__(), el del padre no corre: el objeto queda a medio construir (sinself.nombre, o —en CTk— sin ventana real). Es el bug clásico de herencia.
🎭 3. Override y cuándo heredar
- Override: redefinir un método del padre con el mismo nombre (como
hablar). - Regla mental para heredar: la relación debe ser "es un". Un
Perroes unAnimal✅. TuAppes una ventanaCTk✅. UnCarritotiene items (composición, Clase 14), no hereda de lista ❌.
🧪 Tip de entrevista: "¿Herencia vs composición?" → Herencia = es-un (App es una ventana); composición = tiene-un (Carrito tiene una lista de items). Si dudas, empieza por composición: acopla menos.
🖥️ EN TU APP DE ESCRITORIO — class App(ctk.CTk) 🌟
Este es el patrón profesional para el que era toda la Fase 3. Tu app deja de ser código suelto y se convierte en una ventana heredando de ctk.CTk:
python
import customtkinter as ctk
class App(ctk.CTk): # tu App ES-UNA ventana CTk
def __init__(self):
super().__init__() # ① construye la ventana real (¡imprescindible!)
self.title("Mi To-Do") # ② heredaste .title, .geometry, .mainloop…
self.geometry("400x500")
self.tareas = [] # ③ el ESTADO vive en self (adiós global)
self.crear_widgets() # ④ organizado en métodos
def crear_widgets(self):
self.entry = ctk.CTkEntry(self, placeholder_text="Nueva tarea…")
self.entry.pack(padx=10, pady=10)
self.boton = ctk.CTkButton(self, text="Agregar", command=self.agregar)
self.boton.pack()
def agregar(self): # los callbacks son MÉTODOS
texto = self.entry.get()
if texto:
self.tareas.append(texto) # todo se alcanza vía self
self.entry.delete(0, "end")
if __name__ == "__main__":
app = App()
app.mainloop() # heredado de ctk.CTkQué ganaste respecto a tu versión actual:
| Antes (código suelto) | Ahora (class App(ctk.CTk)) |
|---|---|
tareas = [] global | self.tareas — estado encapsulado |
| funciones sueltas por todo el archivo | métodos organizados en la clase |
global para reasignar | nunca más: todo vía self |
| una sola "pantalla" posible | puedes crear frames/ventanas como clases reutilizables |
💡 Verificado en tu
.venv:class App(ctk.CTk)hereda correctamente (issubclass(App, ctk.CTk) → True, CustomTkinter 6.0.0). Pruébalo tú conpython app.py— yo no abro ventanas desde aquí, esa parte del placer es tuya 😄.
También los frames se heredan — así se arma una app grande por piezas:
python
class PanelFormulario(ctk.CTkFrame): # una PIEZA reutilizable de UI
def __init__(self, padre):
super().__init__(padre)
# …sus widgets adentro…🗄️ CON BASE DE DATOS (caso de uso)
Patrón típico en apps con BD: una clase base con lo común y clases hijas por entidad:
python
class Repositorio: # padre: lo común a TODAS las tablas
def __init__(self, con):
self.con = con
class RepoContactos(Repositorio): # hijo: lo específico de contactos
def todos(self):
return self.con.execute("SELECT * FROM contactos").fetchall()🗄️ Lo verás en forma completa en la Fase 6 (SQLite + arquitectura).
🏋️ EJERCICIOS CON SOLUCIÓN
Ejercicio 1 — Vehículos
Crea Vehiculo con marca y método describir() → "Vehículo de Toyota". Haz que Moto herede y haga override: "Moto de Toyota".
Ver solución
python
class Vehiculo:
def __init__(self, marca):
self.marca = marca
def describir(self):
return f"Vehículo de {self.marca}"
class Moto(Vehiculo):
def describir(self):
return f"Moto de {self.marca}" # self.marca heredado, sin redefinir __init__
m = Moto("Toyota")
print(m.describir()) # Moto de ToyotaEjercicio 2 — super().__init__
Haz que Auto(Vehiculo) agregue puertas, reusando el constructor del padre.
Ver solución
python
class Auto(Vehiculo):
def __init__(self, marca, puertas):
super().__init__(marca) # el padre guarda la marca
self.puertas = puertas
a = Auto("Kia", 4)
print(a.describir(), "con", a.puertas, "puertas") # Vehículo de Kia con 4 puertasEjercicio 3 — El bug del super olvidado
¿Por qué explota esto y cómo se arregla?
python
class Auto(Vehiculo):
def __init__(self, marca, puertas):
self.puertas = puertas
a = Auto("Kia", 4)
print(a.describir())Ver solución
python
# AttributeError: 'Auto' object has no attribute 'marca'
# Al definir __init__ propio SIN super().__init__(marca), el del padre nunca corre
# y self.marca jamás se crea. Arreglo: agregar super().__init__(marca) primero.❓ Preguntas y respuestas (autoevaluación)
1. ¿Qué recibe una clase hija al heredar?
Todos los atributos y métodos del padre; puede agregar propios y hacer override de los heredados.
2. ¿Qué hace super().__init__() y qué pasa si lo olvidas en la hija?
Ejecuta el constructor del padre. Si lo olvidas (teniendo
__init__propio), el objeto queda a medio construir — en CTk, la ventana ni se crea.
3. ¿Herencia o composición: Playlist y sus canciones?
Composición: una playlist tiene canciones (lista interna), no es una lista.
4. ¿Por qué class App(ctk.CTk) es el patrón estándar en apps CTk?
Tu app es-una ventana: hereda
.title/.geometry/.mainloop, el estado vive enself, los callbacks son métodos y desaparecen losglobal.
📎 Apuntes relacionados
- El estado en
selfque reemplaza al global → Clase 14 y Clase 10 - Referencia de widgets → CustomTkinter
➡️ Siguiente
Clase 16 · Encapsulación y properties — proteger los datos del objeto.