Pregunta 1¿Cuál es la afirmación más precisa sobre las variables privadas de Python?
Variables privadas y encapsulación — Acceso seguro vía getter / setter
Aprende las variables privadas y la encapsulación en Python. Convención _x vs name mangling __x, get_xxx / set_xxx para acceso seguro, y el estilo Pythonic @property / @xxx.setter — práctica incluida.
La última vez cubrimos los dos primeros pilares de la POO — herencia y polimorfismo. Este artículo cierra el tercero: la encapsulación.
Variables privadas — Python no tiene privacidad real
Java y C++ tienen una palabra clave private que bloquea el acceso desde fuera en cuanto la declaras. Python no tiene privacidad reforzada por el lenguaje. En su lugar, el número de guiones bajos al inicio señala «esto es de uso interno» o «no toques esto directamente» — una convención entre programadores, no una regla dura.
Guion bajo simple _x — privacidad solo por convención
Añadir un solo _ delante de un nombre le dice a la comunidad de Python «este atributo es para uso interno de la clase — no accedas a él directamente desde fuera». El parámetro de __init__ sigue usando el nombre normal; solo el campo self. recibe el _ inicial.
class UserAccount:
def __init__(self, owner_name, balance):
self._owner_name = owner_name # interno -> prefijo _
self._balance = balance
def get_info(self): # accesor hacia el exterior
return {"owner": self._owner_name, "balance": self._balance}
user = UserAccount("Ana", 50000)
print(user._balance) # 50000 <- funciona pero no se recomienda
print(user.get_info()) # {'owner': 'Ana', 'balance': 50000} <- recomendado
Guion bajo doble __x — name mangling
Añadir dos _ iniciales hace que Python reescriba el propio nombre del atributo. Si escribes self.__pin = 1234 dentro de una clase Account, el nombre realmente almacenado se vuelve _Account__pin. Esto se llama name mangling — obj.__pin desde fuera no encuentra nada, así que el acceso se vuelve mucho más difícil en la práctica.
obj.__pin directamente falla con AttributeError porque esa clave no existe.class Account:
def __init__(self, owner, pin):
self._owner = owner # privacidad solo por convención
self.__pin = pin # con name mangling (se vuelve _Account__pin)
acc = Account("Ana", 1234)
print(acc._owner) # Ana <- funciona normal
# print(acc.__pin) # AttributeError <- no es visible directo
print(acc._Account__pin) # 1234 <- el nombre mangled lo alcanza
«__» tampoco es un muro absoluto
El doble guion bajo evita el acceso directo con obj.__pin, lo cual es una protección un nivel más fuerte. Pero cualquiera que sepa el nombre mangled obj._Account__pin aún puede alcanzarlo. No es privacidad real. En proyectos reales, el guion bajo simple _x es mucho más común salvo que haya una razón específica para usar el mangling.
Encapsulación — restringir el acceso a métodos dedicados
La encapsulación es la idea de diseño «agrupa los atributos de datos y los métodos que operan sobre ellos en una sola clase, y obliga al código exterior a pasar por un pequeño conjunto de puntos de entrada publicados». Entonces, ¿cómo construimos esos puntos de entrada?
_price sin verificar. Pasar por un método significa que el setter valida tipo y rango en un solo lugar.El estilo más básico es escribir get_xxx / set_xxx a mano. Dentro del setter, haz una comprobación con isinstance para el tipo y una comprobación de rango, y raise ValueError(...) si algo no encaja. Con eso, ningún valor basura llega nunca a _price.
class Product:
def __init__(self, name, price, stock):
self._name = name
self._price = price
self._stock = stock
def get_price(self):
return self._price
def set_price(self, price):
if isinstance(price, int) and price >= 0:
self._price = price
else:
raise ValueError("price must be a non-negative integer")
product = Product("Camiseta", 1500, 30)
print(product.get_price()) # 1500
product.set_price(2000)
print(product.get_price()) # 2000
# product.set_price(-100) # ValueError
@property y @xxx.setter
El estilo get_price() / set_price(...) es claro, pero los sitios de llamada acaban viéndose muy «llamada de método» — no es lo más limpio. La idiomática más pulida de Python usa dos decoradores: @property y @xxx.setter.
Con ellos, el sitio de llamada se queda como product.price / product.price = 2000 — acceso de atributo plano — pero por debajo, se llaman los métodos getter y setter. Es una estructura de dos capas donde la sintaxis se mantiene simple pero la lógica sigue corriendo.
@property redirige las lecturas y @price.setter redirige las escrituras a llamadas de método. La validación vive dentro del setter.class Product:
def __init__(self, name, price):
self._name = name
self._price = price
@property
def price(self): # getter
return self._price
@price.setter
def price(self, value): # setter — el nombre debe coincidir con el getter
if not isinstance(value, int) or value < 0:
raise ValueError("price must be a non-negative integer")
self._price = value
@property
def label(self): # propiedad calculada — valor derivado
return f"{self._name} ({self._price})"
product = Product("Camiseta", 1500)
print(product.price) # 1500 <- se invoca @property
product.price = 2000 # <- se invoca @price.setter
print(product.price) # 2000
print(product.label) # Camiseta (2000) <- propiedad calculada
Mantén el nombre del setter idéntico al del getter
El price en @price.setter debe coincidir con el nombre del método del @property def price previo. Python interpreta el decorador como «adjunta una versión de escritura al mismo objeto price que ya tiene una versión de lectura» — si el nombre cambia, se tratan como cosas distintas.
Los tres pilares de la POO
- Protección de datos — separa el estado interno de la API pública con
_x - Consistencia — concentra la validación en los setters, en un solo lugar
- Libertad de implementación — cambia los internos sin cambiar la API pública
- Estilo Pythonic — convención más
@property, no aplicación del lenguaje
- Reutiliza la maquinaria del padre
- Mismo nombre de método, comportamiento distinto por tipo
- Canaliza el acceso desde fuera por unas pocas puertas
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Cuál es el mayor beneficio de usar @property y @xxx.setter?
Pregunta 3Dentro de class Account: escribiste self.__pin = 1234. Desde fuera, acc.__pin lanza AttributeError. ¿Qué nombre se almacena realmente en la instancia?