Métodos y Atributos Privados y Protegidos en Python
Resumen
El encapsulamiento es uno de los pilares de la programación orientada a objetos, y dominarlo marca la diferencia entre código amateur y código profesional. En Python, decidir qué información puede verse desde el exterior y qué debe permanecer oculta es fundamental para construir clases robustas y mantenibles.
¿Qué son los atributos y métodos protegidos en Python?
Cuando trabajamos con clases, por defecto podemos acceder a todos los atributos sin restricción alguna. Sin embargo, el encapsulamiento nos permite restringir la información accesible desde el exterior [01:05]. Para indicar que un atributo o método es protegido, utilizamos la convención de un guion bajo al principio del nombre.
Esta convención no impide técnicamente el acceso desde fuera de la clase. Lo que hace es comunicar a otros programadores que esa información no debería manipularse directamente desde el exterior. Es una señal visual en el código.
python
class BaseClass:
def init(self) -> None:
self._variable = "protected"
def _protected_method(self):print("Este es un método protegido")
Al crear una instancia y acceder a _variable o _protected_method(), Python no genera ningún error [03:15]. Esto ocurre porque la protección en Python funciona por convención, no por restricción real. Si otro programador lee el código, sabrá que ese atributo está protegido, pero aún podrá acceder a la información.
¿Cuándo se recomienda usar atributos protegidos?
Su uso principal es dentro de la clase y en subclases que hereden de ella.
No es recomendable acceder a ellos desde instancias externas, aunque sea posible.
Sirven como indicador claro de que esa información tiene un propósito interno.
¿Cómo funcionan los métodos y atributos privados?
Cuando necesitamos restringir el acceso de manera total, debemos usar el doble guion bajo al inicio del nombre [04:25]. Esto convierte al atributo o método en privado, y Python sí aplica una restricción real mediante un mecanismo llamado name mangling.
def __private_method(self):print("Esto es un método privado")def public_method(self): self.__private_method()
El método público puede llamar al método privado sin problema, porque el acceso ocurre desde dentro de la clase [05:52]. Todo lo que esté al mismo nivel de indentación dentro de la clase podrá invocar métodos y atributos privados.
¿Qué sucede al intentar acceder a un atributo privado desde fuera?
Si intentamos acceder a __private_variable desde una instancia, Python lanza un AttributeError indicando que el atributo no existe [07:08]. Incluso sugiere que tal vez quisimos acceder a la variable protegida. Esto confirma que el doble guion bajo restringe la visibilidad de forma efectiva desde el exterior.
python
base = BaseClass()
print(base.__private_variable) # AttributeError
¿Cuál es la diferencia práctica entre protegido y privado?
La distinción es clara y tiene impacto directo en el diseño de clases:
Protegido (un guion bajo): accesible desde fuera pero no recomendado. Pensado para uso interno y en subclases.
Privado (doble guion bajo): inaccesible desde el exterior. Solo la clase internamente puede utilizarlo.
Público (sin guion bajo): accesible libremente desde cualquier parte del código.
Un buen ejercicio para practicar estos conceptos es implementar una clase CuentaBancaria que contenga un método protegido para actualizar el saldo, permitiendo consultarlo de manera externa, y un método privado para registrar transacciones, ya que esa lógica solo le pertenece a la clase de manera interna [08:10].
Comprender cuándo usar cada nivel de acceso es lo que permite escribir código más seguro y profesional. ¿Ya has probado a refactorizar alguna de tus clases aplicando estos niveles de encapsulamiento?
"""Implementar una clase CuentaBancaria con un método protegido para
actualizar el saldo y un método privado para registrar las transacciones
internamente."""classCuentaBancaria:def__init__(self, saldo_inicial=0): self._saldo = saldo_inicial # Atributo protegido self.__transacciones =[]# Atributo privadodef_actualizar_saldo(self, cantidad): self._saldo += cantidad
def__registrar_transaccion(self, transaccion): self.__transacciones.append(transaccion)deftansaccion(self, cantidad): self._actualizar_saldo(cantidad) self.__registrar_transaccion(f"Depósito: +{cantidad}")defprint_transacciones(self):print("Transacciones:", self.__transacciones)# Ejemplo de usocuenta1 = CuentaBancaria(1000)print("Saldo inicial:", cuenta1._saldo)# Accediendo al atributo protegidocuenta1.tansaccion(500)print("Saldo depués del primer deposito:", cuenta1._saldo)# Accediendo al atributo protegidocuenta1.print_transacciones()# Imprimir las transacciones realizadascuenta1.tansaccion(-100)# Realizar un retiroprint("Saldo después de un retiro:", cuenta1._saldo)# Accediendo al atributo protegidocuenta1.print_transacciones()# Imprimir las transacciones realizadas# print(cuenta1.__transacciones) # Esto generará un error porque __transacciones es privado
# Ejemplo de uso
cuenta = CuentaBancaria("Juan", 1000)
cuenta.depositar(500) # Esto debería llamar a __actualizar_saldo con cantidad = 500
cuenta.retirar(200) # Esto debería llamar a __actualizar_saldo con cantidad = -200
cuenta.retirar(1500) # Esto debería imprimir "Fondos insuficientes"
No he considerado invocar a ningún atributo ni método protegido, ya que si bien es cierto, Python lo permite, técnicamente no debería hacerse.
Es por ello que he creado métodos públicos para poder interactuar con los métodos privados y protegidos.
Se ha agregado una lista de diccionarios, para registrar las transacciones por cada evento suscitado.
Al final de código están las distintas transacciones realizadas y se tiene un método público para ver el saldo, otro para ver el histórico de transacciones y otro para abonar_retirar saldo.
Recordar que según lo indiciado en CLASE, se requiere crear un método protegido _actualizar_saldo() y uno privado __registrar_transaccion(). A partir de allí se fue desarrollando el resto de métodos.
from datetime import datetime
classCuentaBancaria: def __init__(self, id_cuenta, titular_cuenta, capital_inicial): self.__id_cuenta= id_cuenta
self.__titular_cuenta= titular_cuenta
self.__saldo= capital_inicial
self.__transacciones=[]
@property
def titular_cuenta(self):return self.__titular_cuenta # Método público
def ver_saldo_actual(self):return f'El saldo actual es S/{self.__saldo} soles' # Método privado
def __registrar_transaccion(self, mensaje): fecha_hora = datetime.now().strftime("%d/%m/%Y %H:%M:%S") self.__transacciones.append({'cuenta': self.__id_cuenta,'Titular': self.__titular_cuenta,'Saldo': self.__saldo,'Mensaje': mensaje,'Fecha_Hora': fecha_hora
}) # Método protegido
def _actualizar_saldo(self,monto, tipo_tx)-> str: mensaje =''if(tipo_tx =='D' or tipo_tx =='R'):if(tipo_tx =='D'): self.__saldo+= monto
mensaje = f'Abono a la cuenta {self.__id_cuenta} por S/{monto} soles. Saldo actual: {self.__saldo}'else:if monto <= self.__saldo: self.__saldo-= monto
mensaje = f'Retiro desde la cuenta {self.__id_cuenta} por S/{monto} soles. Saldo actual: {self.__saldo}'else: mensaje = f'No se pudo realizar el retiro de S/{monto} soles desde la cuenta {self.__id_cuenta} por saldo insuficiente' self.__registrar_transaccion(mensaje)else: mensaje ='Debe ingresar correctamente el tipo de transacción: [D]= deposito / [R] = retiro'return mensaje
# Método público
def ver_transacciones(self):print('\nHistórico de Transacciones realizadas:')for tx in self.__transacciones:print('[Cuenta]: ', tx['cuenta'])print('[Titular]: ', tx['Titular'])print('[Saldo]: ', tx['Saldo'])print('[Fecha Hora]: ', tx['Fecha_Hora'])print('[Mensaje]: ', tx['Mensaje'])print('\n') # Método público -->Para llamar al método protegido
def realizar_transaccion(self, monto, tipo_tx)-> str:return self._actualizar_saldo(monto, tipo_tx)# Crear objetos de la clase BankAccountcuenta1 =CuentaBancaria("0001","Cristofer Peña",20000)cuenta2 =CuentaBancaria("0002","Alicia Salvatierra",10500)# Depósitos y retiros de cuenta 1print('Cliente Titular: ', cuenta1.titular_cuenta)print(cuenta1.realizar_transaccion(500,'D'))print(cuenta1.realizar_transaccion(200,'D'))print(cuenta1.realizar_transaccion(1500,'R'))print(cuenta1.realizar_transaccion(400,'R'))print(cuenta1.realizar_transaccion(8000,'R'))print(cuenta1.realizar_transaccion(1200,'R'))print(cuenta1.ver_saldo_actual())cuenta1.ver_transacciones()print('Cliente Titular: ', cuenta2.titular_cuenta)print(cuenta2.realizar_transaccion(200,'D'))print(cuenta2.realizar_transaccion(5600,'R'))print(cuenta2.realizar_transaccion(8000,'R'))print(cuenta2.realizar_transaccion(300,'D'))print(cuenta2.ver_saldo_actual())cuenta2.ver_transacciones()
classCuentaBancaria:def__init__(self): self._saldo =0def_actualizar_saldo(self,cantidad): self._saldo += cantidad
self.__registrar_transaccion(cantidad)def__registrar_transaccion(self,cantidad):if cantidad <0:print(f'Se han retirado {cantidad} euros')else:print(f'Se han ingresado {cantidad} euros')cuenta = CuentaBancaria()cuenta._actualizar_saldo(50)# Esta va a dar error porque el método es privadocuenta.__registrar_transaccion(50)
classCuentaBancaria: def __init__(self, saldo_inicial): self._saldo= saldo_inicial # Atributo protegido
def _actualizar_saldo(self, cantidad):"""Método protegido: accesible en esta clase y sus subclases.""" self._saldo+= cantidad
self.__registrar_transaccion(f"Saldo actualizado: {cantidad}") def __registrar_transaccion(self, mensaje):"""Método privado: solo accesible dentro de esta clase."""print(f"Registro interno: {mensaje}")# Ejemplo de subclase que puede usar el método protegido
classCuentaAhorro(CuentaBancaria): def aplicar_interes(self, tasa): interes = self._saldo* tasa
self._actualizar_saldo(interes) # Uso permitido en subclase
def deposito(self, monto):print(f"Realizando depósito de: {monto}") # Accediendo al método protegido de la clase padre
self._actualizar_saldo(monto)# ---PRUEBAS---# 1.Prueba de funcionalidad normal a través de una subclase
print("--- Prueba Subclase ---")mi_ahorro =CuentaAhorro(1000)mi_ahorro.deposito(500) # Esto funciona correctamente
# 2.Prueba de acceso protegido
print("\n--- Prueba Acceso Protegido ---")# AunquePython permite esto por convención, se considera mala práctica
mi_ahorro._actualizar_saldo(100)print("Acceso protegido realizado (por convención de un solo guion)")# 3.Prueba de acceso privado(Esto fallará)print("\n--- Prueba Acceso Privado ---")try: mi_ahorro.__registrar_transaccion("Intento externo")except AttributeError:print("Error: No se puede acceder al método privado '__registrar_transaccion' desde fuera.")
"""
se implement una clase CuentaBancaria con un método protegido
para actualizar el saldo y un método privado para registrar
las transacciones internamente.
"""# Creo la clase y asigno los dos atributos protegidos y privadosclassCuentaBancaria:def__init__(self): self._saldo =500000 self.__transacciones =[]def_actualizar_saldo(self, saldo): self._saldo += saldo
self.__registrar_transaccion(saldo)def__registrar_transaccion(self, saldo): self.__transacciones.append(saldo)defmostrar_saldo(self):return self._saldo
nequi = CuentaBancaria()nequi._actualizar_saldo(100000)print(nequi.mostrar_saldo())
classCuentaBancaria:def__init__(self, titular:str, saldo_inicial:float=0.0): self.titular = titular
self.saldo = saldo_inicial
self.__historial =[]# Atributo privado para registrar transacciones# Método protegido (para uso interno y de subclases)def_actualizar_saldo(self, monto:float)->None:"""
Actualiza el saldo de la cuenta.
Este método está diseñado para ser usado solo dentro de la clase o subclases.
""" self.saldo += monto
self.__registrar_transaccion(monto)# Método privado (solo accesible dentro de la clase)def__registrar_transaccion(self, monto:float)->None:"""
Registra internamente la transacción (no accesible fuera de la clase).
""" tipo ="Depósito"if monto >=0else"Retiro" self.__historial.append({"tipo": tipo,"monto": monto,"saldo_final": self.saldo
})defdepositar(self, monto:float)->None:"""Permite realizar un depósito."""if monto <=0:print("El monto a depositar debe ser positivo.")return self._actualizar_saldo(monto)print(f"Depósito de ${monto:.2f} realizado. Saldo actual: ${self.saldo:.2f}")defretirar(self, monto:float)->None:"""Permite realizar un retiro si hay fondos suficientes."""if monto <=0:print("El monto a retirar debe ser positivo.")returnif monto > self.saldo:print("Fondos insuficientes.")return self._actualizar_saldo(-monto)print(f"Retiro de ${monto:.2f} realizado. Saldo actual: ${self.saldo:.2f}")defmostrar_historial(self)->None:"""Muestra el historial de transacciones."""print(f"\n Historial de transacciones de {self.titular}:")for trans in self.__historial:print(f" - {trans['tipo']}: ${abs(trans['monto']):.2f} | Saldo final: ${trans['saldo_final']:.2f}")# Ejemplo de usoif __name__ =="__main__": cuenta = CuentaBancaria("Ana",500.0) cuenta.depositar(200) cuenta.retirar(100) cuenta.retirar(700)# Fondos insuficientes cuenta.mostrar_historial()# Esto no debería hacerse (ejemplo de acceso protegido/privado)# cuenta._actualizar_saldo(1000) # Posible pero NO recomendado# cuenta.__registrar_transaccion(500) # Esto dará error (es privado)
"""
Implementa una clase CuentaBancaria con un metodo protegido para actualizar el saldo y un método privado para registrar las transacciones internamente.1.El método protegido(_actualizar_saldo) solo debe ser utilizado dentro de la clase y sus subclases.2.El método privado(__registrar_transacción) debe ser completamente interno y no accesible desde fuera de la clase."""
classUser: def __init__(self, name, age, id): self.name= name
self.age= age
self.id= id
self.acounts=[] def depositar(self):print("Las cuentas disponibles son las siguientes: ")if self.acounts==[]:print("No tiene cuentas creadas.")returnelse:for n inrange(len(self.acounts)):print(f"{n + 1}. {self.acounts[n].acount_number}") self.acounts[int(input("Introduce el número de la cuenta a realizar transacción: "))-1]._balance_update()classAcount: def __init__(self,user:User, acount_number): self.acount_number= acount_number
self.balance=0 self.user_acount= user
user.acounts.append(self) self.__log=[] def __log_register(self,data): self.__log.append(data)for n in self.__log:print(n) def _balance_update(self):print(f"Su saldo actual es de {self.balance}") check =int(input("Teclea 1 si deseaas deposittar o 2 si deseas retirar: "))if check ==1: amount =int(input("Introduce el monto a depositar: $")) self.balance+= amount
print(f"Acción realisada, ha sumado ${amount} a su cuenta {self.acount_number} \nSu saldo actual es de {self.balance}") data = f"Se realizó un deposio de ${amount} en la cuenta {self.acount_number}" elif check ==2: amount =int(input("Introduce el monto a retirar: $"))if amount > self.balance:print(f"Usted no cuenta con el dinero suficiente. \nSu saldo actual es de {self.balance}") data = f"Se intentó realizar un retiro de ${amount} en la cuenta {self.acount_number} que tiene saldo insuficiente"else: self.balance-= amount
print(f"Acción realisada, ha restado ${amount} a su cuenta {self.acount_number} \nSu saldo actual es de {self.balance}") data = f"Se realizó un retiro de ${amount} en la cuenta {self.acount_number}" self.__log_register(data)classBanco: def __init__(self): self.users=[] self.acounts=[] def add_user(self,user:User): self.users.append(user)print(f"El usuario {user.name} se ha agregado a la base de datos con el id: {user.id}.") def create_acount(self,acount:Acount): self.acounts.append(acount)print(f"Se ha agregado la cuenta con número {acount.acount_number} del usuario {acount.user_acount.name}")if __name__ =='__main__': person1 =User("Franck",22,1) person2 =User("Alan",22,2) acount1 =Acount(person1,"x210010") bbva =Banco() bbva.add_user(person1) bbva.add_user(person2) bbva.create_acount(acount1) person1.depositar() person1.depositar()
'''
crear una clase CuentaBancaria donde puedas registrar y visualizar el saldo.Debe incluir:Un método protegido para actualizar el saldo que pueda ser consultado externamente.Un método privado para registrar las transacciones, que sea interno a la clase.'''
classbankAcc: def __init__(self): self._savings=1000 self.transactions=[] def _update_savings(self,amount): self._savings+= amount
print(f'total savings: {self._savings}') def __register_transaction(self):whileTrue:print('--select 1. for trasaction or 0. for exit--') start =int(input('press only 1 or 0: '))if start ==1: amount =float(input('amount: '))if amount >0: self._update_savings(amount) self.transactions.append(amount)print(f'trasaction success: {self.transactions}')else:print('wrong value!')else:print('goodbye!')break def _show_savings(self):print(f'savings: {self._savings}') def activateTransaction(self): self.__register_transaction()banco =bankAcc()banco._show_savings()banco.activateTransaction()
classBanco: def __init__(self, name,): self.name= name
self.__saldo=0 def __Registro_Actividad(self,mensaje):withopen('registro_banco.txt','a')asfile: file.write(f"\n\n"+ mensaje) def Modificar_Saldo(self,Cantidad:float,modo:int):ifCantidad<0: raise ValueError('No se aceptan numeros menores a 0')if modo ==1: self.__saldo+=Cantidad self.__Registro_Actividad(f'{self.name} añadio a su cuenta {Cantidad}, saldo actual {self.__saldo}') elif modo ==2:ifCantidad> self.__saldo: raise NotImplementedError('No Puede retirar más de la cantidad en la cuenta') self.__saldo-=Cantidad self.__Registro_Actividad(f'{self.name} retiró de su cuenta {Cantidad}, saldo actual {self.__saldo}')else: raise NotImplementedError('Modo Desconocido') def Mostrar_Actividad(self):withopen('registro_banco.txt','r')asfile:for lineas infile:print(lineas.strip())banco =Banco('Juan')banco.Modificar_Saldo(25,1)banco.Mostrar_Actividad()banco.Modificar_Saldo(5,2)banco.Mostrar_Actividad()
Aqui mi mini programa de cuenta bancaria con metodos privados y protegidos, con algunos añadidos.
classCuentaBancaria:def__init__(self, saldo:float): self._saldo = saldo
self.__transacciones =[]definiciar_sistema(self): seguir =Truewhile seguir: transaccion =int(input("\nOpciones\n1.Depositar\n2.Retirar\n3.Consultar\n4.Ver Transacciones\n5.Salir\nElección: "))match transaccion:case1: self.__depositar_dinero()case2: self.__retirar_dinero()case3: self.__consultar_saldo()case4: self.__mostrar_transacciones()case5: seguir =Falseprint(f"Saliendo del sistema...")case_:print("Esta opción no existe, Elija una correcta")returnNonedef__consultar_saldo(self):print(f'Su saldo es de: ${self._saldo}')def__depositar_dinero(self): dinero =int(input("Ingrese la cantidad que desea depositar: $"))if dinero <=0:print("No puede depositar una cantidad igual o menor a 0")returnNoneelse: self._saldo += dinero
print(f"Los ${dinero} se han depositado correctamente") self.__registrar_transaccion("deposito", dinero)def__retirar_dinero(self): dinero =int(input("Ingrese la cantidad que desea retirar: $"))if dinero <=0:print("No puede depositar una cantidad igual o menor a 0")returnNoneelif dinero > self._saldo:print("El monto a retirar excede los fondos")returnNoneelse: self._saldo -= dinero
print(f"Usted ha retirado ${dinero} exitosamente de su cuenta") self.__registrar_transaccion("retiro:", dinero)def__registrar_transaccion(self, transaccion:str, dinero:float): self.__transacciones.append(f"Transacción: {transaccion}, Cantidad: ${dinero}")def__mostrar_transacciones(self):for transaccion in self.__transacciones:print(transaccion)if __name__ =="__main__": cuenta = CuentaBancaria(50) cuenta.iniciar_sistema()