Skip to content

📙 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 olvidas super().__init__(), el del padre no corre: el objeto queda a medio construir (sin self.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 Perro es un Animal ✅. Tu App es una ventana CTk ✅. Un Carrito tiene 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.CTk

Qué ganaste respecto a tu versión actual:

Antes (código suelto)Ahora (class App(ctk.CTk))
tareas = [] globalself.tareas — estado encapsulado
funciones sueltas por todo el archivométodos organizados en la clase
global para reasignarnunca más: todo vía self
una sola "pantalla" posiblepuedes 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ú con python 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 Toyota

Ejercicio 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 puertas

Ejercicio 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 en self, los callbacks son métodos y desaparecen los global.


📎 Apuntes relacionados

➡️ Siguiente

Clase 16 · Encapsulación y properties — proteger los datos del objeto.