Juan Diego Andrés PRADA··RAMÍREZ Entrar
Lección 3 de 7

Encapsulamiento y propiedades

¿Qué es?

El encapsulamiento consiste en proteger los datos internos de un objeto para que solo se modifiquen de formas válidas. En lugar de dejar que cualquiera escriba directamente en un atributo, el objeto controla el acceso. Una propiedad (property) es la herramienta de Python para lograrlo sin cambiar la forma en que se usa el objeto desde fuera.

¿Cómo funciona?

En Python no existen atributos verdaderamente privados, pero hay una convención: un nombre que empieza con un guion bajo (_valor) significa "interno, no lo toques desde fuera". Para validar lecturas y escrituras se usa el decorador @property (para leer) y @nombre.setter (para escribir). Desde fuera el atributo se sigue usando como objeto.valor, pero por debajo se ejecuta tu código de validación.

¿Para qué sirve?

Sirve para garantizar invariantes: reglas que siempre deben cumplirse. En el sistema de notas, una nota debe estar entre 0.0 y 5.0. Si el objeto valida eso en un solo lugar, es imposible que un dato inválido entre, sin importar quién lo intente.

Qué pieza del sistema construimos

Construimos la clase Calificacion, que envuelve un valor numérico y garantiza que siempre sea válido (entre 0.0 y 5.0). También endurecemos Estudiante para que rechace notas inválidas. Así el resto del sistema puede confiar en que las notas son correctas sin volver a revisarlas.

El problema sin encapsulamiento

class Calificacion:
 def __init__(self, valor):
 self.valor = valor

c = Calificacion(4.0)
c.valor = 99 # nadie lo impide; dato inválido dentro del sistema

Cualquiera puede asignar un valor absurdo. Necesitamos un punto único donde validar.

Marcando el atributo como interno

Primero renombramos a _valor (convención de "no público") y movemos la validación a un solo método.

class Calificacion:
 def __init__(self, valor):
 self._valor = self._validar(valor)

 def _validar(self, valor):
 if not isinstance(valor, (int, float)):
 raise TypeError("La nota debe ser numérica")
 if not 0.0 <= valor <= 5.0:
 raise ValueError("La nota debe estar entre 0.0 y 5.0")
 return float(valor)

El método _validar es interno (empieza con _) y centraliza la regla. Si llega algo inválido, lanza una excepción en vez de guardar basura.

Exponiendo con property

Ahora queremos que desde fuera se lea c.valor (sin guion bajo) y que al asignar también se valide.

class Calificacion:
 def __init__(self, valor):
 self.valor = valor # esto ya pasa por el setter

 @property
 def valor(self):
 return self._valor

 @valor.setter
 def valor(self, nuevo):
 if not isinstance(nuevo, (int, float)):
 raise TypeError("La nota debe ser numérica")
 if not 0.0 <= nuevo <= 5.0:
 raise ValueError("La nota debe estar entre 0.0 y 5.0")
 self._valor = float(nuevo)

Detalle importante: en __init__ escribimos self.valor = valor, no self._valor. Así la asignación inicial también pasa por el setter y se valida. El setter guarda el dato real en self._valor.

c = Calificacion(4.0)
print(c.valor) # 4.0
c.valor = 5.0 # válido, pasa por el setter
# c.valor = 99 # ValueError: la nota debe estar entre 0.0 y 5.0

Una property puede ser de solo lectura: define el @property y omite el setter. Es ideal para valores calculados, como un promedio que no tiene sentido asignar a mano.

Una propiedad calculada en Estudiante

Aprovechamos property para que promedio se vea como un atributo, no como un método con paréntesis, y para que Estudiante rechace notas inválidas usando Calificacion.

class Estudiante:
 def __init__(self, nombre):
 self.nombre = nombre
 self._notas = []

 def agregar_nota(self, valor):
 self._notas.append(Calificacion(valor)) # valida al entrar

 @property
 def promedio(self):
 if not self._notas:
 return 0.0
 return sum(c.valor for c in self._notas) / len(self._notas)

Ahora estudiante.promedio se lee sin paréntesis y es imposible meter una nota fuera de rango: Calificacion lo impide en la puerta de entrada.

Error común: dentro del setter de valor, escribir self.valor = nuevo en vez de self._valor = nuevo. Eso vuelve a llamar al setter una y otra vez: recursión infinita. Guarda siempre en el atributo interno _valor.

Tech English: encapsulation = encapsulamiento, property = propiedad, getter / setter = lector / asignador, invariant = invariante (regla que siempre se cumple).

Ejercicios

  1. Estudiante con edad validada. Convierte edad en una propiedad que solo acepte enteros entre 0 y 120. Diseña el getter y el setter y demuestra con un ejemplo que asignar -5 lanza ValueError.

  2. Property de solo lectura. Diseña en Estudiante una propiedad calculada aprobado (de solo lectura) que sea True si el promedio es mayor o igual a 3.0. Explica por qué no tiene sentido darle un setter.

Tu progreso se guarda en este navegador. Inicia sesión para guardarlo en tu cuenta y verlo desde cualquier dispositivo.