Apariencia
📙 Clase 16 — Encapsulación y properties
Fase 3 · Programación Orientada a Objetos ⬅️ Volver al índice de clases
🎯 Qué aprendí
- La convención
_privadoy el name mangling de__doble. @property: atributos calculados y setters que validan.- Proteger el estado de tu app para que nadie lo deje inválido.
📖 PARTE TEÓRICA
🔒 1. "Privado" a la Python: convención, no candado
Python no tiene private como Java/C#. Usa convenciones:
| Prefijo | Significado | ¿Bloquea el acceso? |
|---|---|---|
nombre | público: úsalo libremente | — |
_nombre | "interno: no lo toques desde fuera" | No (solo convención) |
__nombre | activa name mangling (se renombra) | Dificulta, no imposibilita |
python
class Cuenta:
def __init__(self):
self._saldo = 0 # _ = "esto es interno, respétalo"
class Secreta:
def __init__(self):
self.__clave = "x" # __ = Python lo renombra a _Secreta__clave
s = Secreta()
print(hasattr(s, "__clave")) # False ← "desapareció"
print(hasattr(s, "_Secreta__clave")) # True ← en realidad se renombró📌 Filosofía Python: "somos todos adultos".
_saldocomunica la intención; el 99% del tiempo basta con el guion bajo simple.__se reserva para evitar choques de nombres en herencia.
🎛️ 2. @property: métodos disfrazados de atributos
Un @property se lee como atributo pero ejecuta código. Sirve para dos cosas:
a) Atributos calculados (siempre frescos, nunca desincronizados):
python
class Carrito:
def __init__(self):
self.items = []
@property
def total(self): # se calcula al leer
return sum(i["precio"] for i in self.items)
c = Carrito()
c.items.append({"precio": 120})
print(c.total) # 120 ← SIN paréntesis: parece atributo, es métodob) Setters que validan (el estado nunca queda inválido):
python
class Cuenta:
def __init__(self):
self._saldo = 0 # el dato real, "interno"
@property
def saldo(self): # el GETTER: leer cuenta.saldo
return self._saldo
@saldo.setter
def saldo(self, valor): # el SETTER: cuenta.saldo = x pasa por AQUÍ
if valor < 0:
raise ValueError("El saldo no puede ser negativo")
self._saldo = valor
cta = Cuenta()
cta.saldo = 100 # OK (pasó por el setter)
print(cta.saldo) # 100
cta.saldo = -50 # 💥 ValueError: El saldo no puede ser negativocuenta.saldo ──▶ getter ──▶ devuelve _saldo
cuenta.saldo = -50 ──▶ setter ──▶ valida ──▶ 💥 o guarda🧪 Tip de entrevista: "¿Para qué sirve
@property?" → Exponer un método como atributo: permite calcular al leer y validar al asignar sin cambiar la interfaz pública (obj.xsigue siendoobj.x, singet_x()/set_x()a la Java).
🖥️ EN TU APP DE ESCRITORIO
La encapsulación protege el estado de tu app de quedar inválido por un descuido de la GUI:
python
class App(ctk.CTk):
def __init__(self):
super().__init__()
self._tareas = [] # interno: la GUI no lo toca directo
@property
def pendientes(self): # dato calculado para la barra de estado
return sum(1 for t in self._tareas if not t.hecha)
def agregar_tarea(self, texto): # ÚNICA puerta de entrada (valida)
texto = texto.strip()
if not texto:
raise ValueError("La tarea no puede estar vacía")
self._tareas.append(Tarea(texto))
# En el callback:
# self.status.configure(text=f"Pendientes: {self.pendientes}")| Sin encapsular | Encapsulado |
|---|---|
cualquier callback hace tareas.append("") y mete basura | solo agregar_tarea() inserta, y valida |
| el contador de pendientes se recalcula "a mano" por ahí | self.pendientes siempre correcto |
💡 CustomTkinter funciona igual por dentro: por eso configuras con
boton.configure(text=...)y lees con.cget("text")— no tocas sus atributos internos directamente.
🗄️ CON BASE DE DATOS (caso de uso)
Un setter con validación es tu última línea de defensa antes del INSERT: nada inválido llega a la BD.
python
class Contacto:
@property
def email(self):
return self._email
@email.setter
def email(self, valor):
if "@" not in valor:
raise ValueError("Email inválido")
self._email = valor.lower().strip() # además NORMALIZA antes de guardar🗄️ Las BD también validan (
NOT NULL,UNIQUE,CHECK), pero validar en el objeto da errores más claros y evita viajes inútiles a la BD.
🏋️ EJERCICIOS CON SOLUCIÓN
Ejercicio 1 — Temperatura segura
Crea Nevera con property temperatura que rechace valores fuera de -30 a 10 °C.
Ver solución
python
class Nevera:
def __init__(self):
self._temperatura = 4
@property
def temperatura(self):
return self._temperatura
@temperatura.setter
def temperatura(self, valor):
if not -30 <= valor <= 10:
raise ValueError("Temperatura fuera de rango")
self._temperatura = valor
n = Nevera()
n.temperatura = -5
print(n.temperatura) # -5
# n.temperatura = 50 # ValueError: Temperatura fuera de rangoEjercicio 2 — Atributo calculado
Crea Rectangulo(base, altura) con property area (sin paréntesis al leer).
Ver solución
python
class Rectangulo:
def __init__(self, base, altura):
self.base = base
self.altura = altura
@property
def area(self):
return self.base * self.altura
r = Rectangulo(3, 4)
print(r.area) # 12
r.base = 10
print(r.area) # 40 ← siempre fresco, nunca desincronizadoEjercicio 3 — ¿Qué comunica el guion bajo?
En self._conexion, ¿qué le dice el _ a otro programador (o a tu yo del futuro)?
Ver solución
python
# "Este atributo es INTERNO de la clase: no lo leas ni modifiques desde fuera.
# Usa los métodos/properties públicos." No es un candado técnico — es un contrato
# de equipo que Python confía en que respetes.❓ Preguntas y respuestas (autoevaluación)
1. ¿Python tiene atributos privados de verdad?
No.
_nombrees convención ("no tocar") y__nombresolo renombra (name mangling →_Clase__nombre).
2. ¿Qué dos superpoderes da @property?
Atributos calculados al leer y validación al asignar (con
@x.setter) — manteniendo sintaxis de atributo.
3. ¿Cómo se lee un property: c.total o c.total()?
c.total, sin paréntesis: se usa como atributo aunque ejecute código.
4. ¿Dónde valida mejor una app con BD: en el objeto o en la BD?
En ambos: el objeto da errores claros e inmediatos (setter); la BD garantiza integridad (
UNIQUE,NOT NULL) contra cualquier camino que se salte al objeto.
📎 Apuntes relacionados
raise ValueErroren setters → Clase 12 · Excepciones- El estado en
selfque estamos protegiendo → Clase 14
➡️ Siguiente
Clase 17 · Métodos dunder — que tus objetos se impriman, comparen y midan como los nativos.