Skip to content

📙 Clase 14 — Métodos y atributos

Fase 3 · Programación Orientada a Objetos ⬅️ Volver al índice de clases

🎯 Qué aprendí

  • Atributos de instancia (cada objeto los suyos) vs atributos de clase (compartidos).
  • Métodos que leen, modifican y calculan sobre self.
  • El patrón contador/ID automático, directo a tus registros de app.

📖 PARTE TEÓRICA

🧬 1. Atributo de instancia vs atributo de clase

python
class Tarea:
    contador = 0                       # atributo de CLASE: compartido por TODAS

    def __init__(self, texto):
        Tarea.contador += 1            # toco el de la clase (prefijo Tarea.)
        self.id = Tarea.contador       # atributos de INSTANCIA: de ESTE objeto
        self.texto = texto
        self.hecha = False

    def completar(self):
        self.hecha = True

t1 = Tarea("Comprar pan")
t2 = Tarea("Estudiar")
t2.completar()

print(t1.id, t1.hecha)      # 1 False
print(t2.id, t2.hecha)      # 2 True
print(Tarea.contador)       # 2   ← el compartido: cuántas se crearon
     Tarea (clase)                    t1                    t2
   ┌────────────────┐        ┌────────────────┐   ┌────────────────┐
   │ contador = 2   │◀──┬───▶│ id=1           │   │ id=2           │
   │ (compartido)   │   │    │ texto="pan…"   │   │ texto="Estud…" │
   └────────────────┘   │    │ hecha=False    │   │ hecha=True     │
              lo ven ambos   └────────────────┘   └────────────────┘
Atributo de instanciaAtributo de clase
Se definedentro de __init__ con self.directo en el cuerpo de la clase
Pertenece acada objetola clase (compartido)
Uso típicodatos del registro (texto, hecha)contadores, configuración común, constantes

⚠️ Gotcha: si haces self.contador = 99 en una instancia, no cambias el de la clase — creas un atributo de instancia que lo "tapa" para ese objeto. Para el compartido, usa siempre Tarea.contador.

🛠️ 2. Tres tipos de método (por lo que hacen)

python
class Carrito:
    def __init__(self):
        self.items = []                 # composición: un objeto CONTIENE una lista

    def agregar(self, nombre, precio):  # 1) MODIFICA el estado
        self.items.append({"nombre": nombre, "precio": precio})

    def total(self):                    # 2) CALCULA a partir del estado
        return sum(item["precio"] for item in self.items)

    def resumen(self):                  # 3) PRESENTA el estado
        return f"{len(self.items)} items — S/{self.total()}"

c = Carrito()
c.agregar("Teclado", 120)
c.agregar("Mouse", 45)
print(c.total())      # 165
print(c.resumen())    # 2 items — S/165

💡 Fíjate cómo resumen() llama a self.total(): los métodos se combinan entre sí a través de self. Y el estado (self.items) viaja gratis — ya no necesitas globals ni pasar la lista de función en función. Este es el adiós al global de la Clase 10.

🧪 Tip de entrevista: "¿Diferencia entre atributo de clase y de instancia?" → El de clase se define en el cuerpo y es compartido; el de instancia se crea con self. y es propio de cada objeto. Si asignas vía self, "tapas" el de clase para esa instancia.


🖥️ EN TU APP DE ESCRITORIO

Cada fila de tu To-Do/agenda pasa de dict suelto a objeto con ID y comportamiento — y la GUI consume sus métodos de presentación:

python
class Tarea:
    contador = 0
    def __init__(self, texto):
        Tarea.contador += 1
        self.id = Tarea.contador       # ¡cada tarea nace con su ID único!
        self.texto = texto
        self.hecha = False

    def alternar(self):                # para el checkbox de la GUI
        self.hecha = not self.hecha

    def etiqueta(self):
        return f"{'✅' if self.hecha else '⬜'} {self.id}. {self.texto}"

# En la GUI: cada checkbox llama al método del objeto correcto
for tarea in tareas:
    chk = ctk.CTkCheckBox(lista, text=tarea.etiqueta(),
                          command=lambda t=tarea: (t.alternar(), pintar()))

💡 El lambda t=tarea: de la Clase 10 + métodos de objeto = cada widget opera sobre su registro. Este es el patrón que escala.


🗄️ CON BASE DE DATOS (caso de uso)

El id autoincremental que acabas de implementar con Tarea.contador existe nativo en SQLite: INTEGER PRIMARY KEY AUTOINCREMENT. Cuando migres a BD, borras tu contador manual y dejas que la BD asigne los IDs — pero ya entiendes qué hace y por qué hace falta (identificar cada registro para editar/borrar el correcto).

sql
CREATE TABLE tareas (
    id INTEGER PRIMARY KEY AUTOINCREMENT,   -- el "Tarea.contador" profesional
    texto TEXT,
    hecha INTEGER
)

🏋️ EJERCICIOS CON SOLUCIÓN

Ejercicio 1 — Cuenta bancaria

Crea Cuenta con atributo saldo (inicia en 0) y métodos depositar(monto) y consultar() que devuelva "Saldo: S/X".

Ver solución
python
class Cuenta:
    def __init__(self):
        self.saldo = 0

    def depositar(self, monto):
        self.saldo += monto

    def consultar(self):
        return f"Saldo: S/{self.saldo}"

c = Cuenta()
c.depositar(100)
c.depositar(50)
print(c.consultar())   # Saldo: S/150

Ejercicio 2 — ID automático

Agrega a Cuenta un número de cuenta automático (la primera es la 1, la segunda la 2…) usando un atributo de clase.

Ver solución
python
class Cuenta:
    ultimas = 0                      # atributo de CLASE

    def __init__(self):
        Cuenta.ultimas += 1
        self.numero = Cuenta.ultimas
        self.saldo = 0

a, b = Cuenta(), Cuenta()
print(a.numero, b.numero, Cuenta.ultimas)   # 1 2 2

Ejercicio 3 — Método que usa otro método

Agrega puede_retirar(monto) que devuelva True/False, y retirar(monto) que lo use antes de descontar.

Ver solución
python
class Cuenta:
    def __init__(self):
        self.saldo = 0

    def depositar(self, monto):
        self.saldo += monto

    def puede_retirar(self, monto):
        return monto <= self.saldo

    def retirar(self, monto):
        if not self.puede_retirar(monto):    # un método llamando a otro vía self
            return False
        self.saldo -= monto
        return True

c = Cuenta(); c.depositar(100)
print(c.retirar(30), c.saldo)    # True 70
print(c.retirar(500), c.saldo)   # False 70

❓ Preguntas y respuestas (autoevaluación)

1. ¿Dónde se define un atributo de clase y quiénes lo ven?

En el cuerpo de la clase (fuera de __init__); lo comparten todas las instancias.

2. ¿Qué pasa si haces self.contador = 99 cuando contador es de clase?

Creas un atributo de instancia que "tapa" al de clase solo para ese objeto; el compartido no cambia.

3. ¿Cómo llama un método a otro de la misma clase?

A través de self: self.otro_metodo().

4. ¿Qué problema de la Fase 2 resuelve guardar estado en self?

El abuso de global: el estado viaja con el objeto y cualquier método lo alcanza sin variables globales.


📎 Apuntes relacionados

➡️ Siguiente

Clase 15 · Herencia — el mecanismo con el que tu app será una ventana.