Objetivos de Aprendizaje
Al finalizar esta clase, serás capaz de:
- Comprender el concepto de encapsulamiento
- Usar convenciones
_(protegido) y__(privado) - Crear getters y setters con
@property - Validar datos en los setters
- Aplicar encapsulamiento al modelo Usuario
¿Qué es el Encapsulamiento? (10 min)
El encapsulamiento es ocultar los detalles internos de una clase y exponer solo lo necesario. Protege los datos del acceso directo.
Analogía: Un coche tiene un motor, pero no puedes tocarlo directamente. Solo interactúas con el volante, pedales y botones. El motor está "encapsulado".
Problema sin Encapsulamiento
class CuentaBancaria:
def __init__(self, saldo):
self.saldo = saldo # Acceso público
cuenta = CuentaBancaria(1000)
cuenta.saldo = -5000 # ¡Cualquiera puede modificar!
print(cuenta.saldo) # -5000 (¡inválido!)
Solución con Encapsulamiento
class CuentaBancaria:
def __init__(self, saldo_inicial):
self._saldo = saldo_inicial # Atributo "protegido"
def depositar(self, cantidad):
if cantidad > 0:
self._saldo += cantidad
def retirar(self, cantidad):
if 0 < cantidad <= self._saldo:
self._saldo -= cantidad
def obtener_saldo(self):
return self._saldo
cuenta = CuentaBancaria(1000)
cuenta.depositar(500)
cuenta.retirar(200)
print(cuenta.obtener_saldo()) # 1300
# El saldo solo se modifica por métodos controlados
# cuenta._saldo = -5000 # No deberías hacer esto
Convenciones en Python (15 min)
Python no tiene verdaderos atributos privados como Java o C++. Usa convenciones:
| Convención | Significado | Ejemplo |
|---|---|---|
nombre |
Público | self.nombre (puede usarse libremente) |
_nombre |
Protegido | self._nombre (no tocar desde fuera) |
__nombre |
Privado (name mangling) | self.__nombre (difícil de acceder) |
class Ejemplo:
def __init__(self):
self.publico = "Todos pueden verme"
self._protegido = "No deberías tocarme desde fuera"
self.__privado = "Difícil de acceder desde fuera"
obj = Ejemplo()
print(obj.publico) # ✅ Funciona
print(obj._protegido) # ⚠️ Funciona pero no deberías
# print(obj.__privado) # ❌ Error! No existe
# Para acceder al privado (no recomendado):
print(obj._Ejemplo__privado) # "Difícil de acceder..."
Regla de Oro: En Python, "somos todos adultos". No hay atributos verdaderamente privados. La convención
_ indica "no tocar" y debe respetarse.
@property - Getters y Setters (20 min)
El decorador @property permite crear atributos "inteligentes" que ejecutan código al leer o escribir.
Getter con @property
class Circulo:
def __init__(self, radio):
self._radio = radio
@property
def radio(self):
"""Getter: se llama al leer circulo.radio"""
print("Obteniendo radio...")
return self._radio
@property
def area(self):
"""Getter calculado: no se almacena, se calcula"""
return 3.14159 * self._radio ** 2
# Uso
c = Circulo(5)
print(c.radio) # Llama al getter
print(c.area) # Calcula automáticamente (78.54)
Setter con @atributo.setter
class Circulo:
def __init__(self, radio):
self._radio = radio
@property
def radio(self):
"""Getter"""
return self._radio
@radio.setter
def radio(self, valor):
"""Setter: se llama al asignar circulo.radio = valor"""
if valor <= 0:
raise ValueError("El radio debe ser positivo")
self._radio = valor
@property
def area(self):
return 3.14159 * self._radio ** 2
# Uso
c = Circulo(5)
print(c.radio) # 5
c.radio = 10 # Llama al setter
print(c.area) # 314.159
# c.radio = -5 # ❌ ValueError: El radio debe ser positivo
Ejemplo: Usuario con Email Validado
class Usuario:
def __init__(self, username, email):
self._username = username
self._email = None # Inicializa vacío
self.email = email # Usa el setter para validar
@property
def username(self):
return self._username
@property
def email(self):
return self._email
@email.setter
def email(self, valor):
if "@" not in valor:
raise ValueError("Email inválido: debe contener @")
self._email = valor
# Uso
u = Usuario("juan", "juan@email.com")
print(u.email) # juan@email.com
u.email = "nuevo@email.com" # ✅ Válido
print(u.email)
# u.email = "invalido" # ❌ ValueError: Email inválido...
Ejercicio: Usuario Encapsulado (25 min)
Mejora la clase Usuario con encapsulamiento completo.
Requisitos
username: solo lectura (no setter), validar ≥3 caracteresemail: getter/setter con validación de @edad: getter/setter, validar 0-120 años_activo: protegido, métodos activar()/desactivar()
# usuario_encapsulado.py
class Usuario:
"""Usuario con encapsulamiento completo."""
def __init__(self, username, email, edad=None):
# Validar username en el constructor
if len(username) < 3:
raise ValueError("Username debe tener al menos 3 caracteres")
self._username = username
self._email = None
self._edad = None
self._activo = True
# Usar setters para validar
self.email = email
if edad is not None:
self.edad = edad
@property
def username(self):
"""Username es de solo lectura (no hay setter)."""
return self._username
@property
def email(self):
return self._email
@email.setter
def email(self, valor):
if "@" not in valor or "." not in valor.split("@")[1]:
raise ValueError("Email inválido")
self._email = valor
@property
def edad(self):
return self._edad
@edad.setter
def edad(self, valor):
if not isinstance(valor, int):
raise ValueError("Edad debe ser un número entero")
if not (0 <= valor <= 120):
raise ValueError("Edad debe estar entre 0 y 120")
self._edad = valor
@property
def activo(self):
return self._activo
def activar(self):
self._activo = True
def desactivar(self):
self._activo = False
def __str__(self):
return f"@{self._username} ({self._email})"
# === PRUEBAS ===
if __name__ == "__main__":
# Crear usuario válido
try:
u = Usuario("juan123", "juan@email.com", 25)
print(f"✅ Creado: {u}")
print(f" Edad: {u.edad}")
print(f" Activo: {u.activo}")
except ValueError as e:
print(f"❌ Error: {e}")
# Intentar modificar username (solo lectura)
print("\n--- Username es solo lectura ---")
try:
u.username = "otro" # Esto debería fallar
except AttributeError as e:
print(f"✅ Correcto: No se puede modificar username")
# Cambiar email válido
print("\n--- Cambiar email ---")
u.email = "nuevo@email.com"
print(f"✅ Nuevo email: {u.email}")
# Intentar email inválido
print("\n--- Email inválido ---")
try:
u.email = "sin-arroba"
except ValueError as e:
print(f"✅ Correcto: {e}")
# Edad inválida
print("\n--- Edad inválida ---")
try:
u.edad = 150
except ValueError as e:
print(f"✅ Correcto: {e}")
# Activar/Desactivar
print("\n--- Estado ---")
u.desactivar()
print(f"Desactivado: Activo = {u.activo}")
u.activar()
print(f"Activado: Activo = {u.activo}")
Conexión con TaskFlow: El encapsulamiento protege los datos del usuario. El email no puede ser cualquier texto, la edad debe ser razonable, y el username no cambia (es la identidad del usuario).
Resumen
- Encapsulamiento: Ocultar detalles internos, exponer solo lo necesario
- _ (underscore): Convención para atributos protegidos
- __ (dunder): Atributos privados con name mangling
- @property: Define getters elegantes
- @atributo.setter: Define setters con validación
Para el examen E1: Practica crear clases con @property y setters que validen datos. Es fundamental para el modelo de dominio.