Atributos privados
Parte de la sección Programación orientada a objetos del Journey de Python de Coddy. Lección 15 de 64.
Los atributos privados usan guiones bajos para indicar que no se debe acceder directamente a ciertos datos desde fuera de la clase.
¿Por qué necesitamos atributos privados? Imagina que tienes una cuenta bancaria. No quieres que nadie cambie directamente tu saldo: debería hacerlo a través de los canales adecuados (métodos de depósito/retiro) para que el banco pueda validar la transacción, comprobar si hay fraude, llevar registros, etc. Los atributos privados funcionan de la misma manera: protegen tus datos para evitar que se modifiquen incorrectamente.
Aquí tienes un ejemplo que utiliza un guion bajo (convención para «uso interno»):
class Person:
def __init__(self, name, age):
self._name = name # "protected" - uso interno
self._age = age # "protected" - uso internoSe puede seguir accediendo a los atributos con un solo guion bajo, pero es una señal de que no deberías hacerlo:
person = Person("Alice", 30)
print(person._name) # Funciona, pero no se recomiendaUsa guiones bajos dobles para una mayor privacidad (alteración de nombres):
class Person:
def __init__(self, name, age):
self.__name = name # "private" - se le aplica la modificación de nombres
self.__age = age # "private" - se le aplica la modificación de nombres
def get_name(self):
return self.__name
def set_age(self, age):
if age >= 0:
self.__age = age
else:
print("Age must be positive!")¿Por qué usar métodos getter y setter? Son como guardianes. En lugar de permitir que cualquiera cambie __age directamente (lo que podría dar lugar a edades negativas u otros datos no válidos), obligamos a usar set_age(), que primero valida la entrada. Esto evita errores y garantiza la integridad de los datos.
El «código extra» tiene un propósito:
get_name()- Acceso controlado para leer el nombreset_age(age)- Valida la edad antes de permitir cambios (¡no se permiten edades negativas!)- Sin estos métodos, alguien podría establecer
age = -100y estropear tu programa
Usa los métodos de acceso para interactuar con los atributos privados:
person = Person("Bob", 25)
print(person.get_name()) # Bob
person.set_age(30) # Válido: la edad pasa a ser 30
person.set_age(-5) # No válido: ¡la edad debe ser positiva! (la edad permanece en 30)¿Ves la ventaja? El método set_age() evita los datos no válidos. Sin él, podrías crear accidentalmente una persona con edad -5, ¡lo cual no tiene sentido!
A los atributos con doble guion bajo se les modifica el nombre, pero aún se puede acceder a ellos:
person = Person("Charlie", 35)
# Esto no funciona:
# print(person.__name) # AttributeError
# Pero esto funciona (no recomendado):
print(person._Person__name) # CharlieEjemplo del mundo real: ¿por qué todo este código?
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Privado: no se puede modificar directamente
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def get_balance(self):
return self.__balance
# Sin atributos privados:
# account.__balance = -1000000 # ¡Desastre! Se permite un saldo negativo
# Con atributos privados:
account = BankAccount(100)
account.deposit(50) # Seguro: validado
print(account.get_balance()) # 150El «código adicional» (los métodos) protege tus datos de cambios no válidos. Es como tener guardias de seguridad en lugar de dejar tus objetos de valor desprotegidos.
Salida:
Alice
Bob
Age must be positive!
Charlie
150Un guion bajo _attribute significa «solo para uso interno» por convención. El doble guion bajo __attribute activa la alteración de nombres para ofrecer mayor privacidad. Usa métodos getter/setter (métodos de acceso) para interactuar correctamente con los datos privados y añadir validación. El «código adicional» evita errores al garantizar que los datos siempre sean válidos.
Desafío
FácilCompleta la clase User en user.py con la funcionalidad de contraseña privada y después úsala en driver.py. Sigue los comentarios TODO para implementar los métodos de comprobación y cambio de contraseña.
Pruébalo tú mismo
# TODO: Importa la clase User del módulo user
# TODO: Obtén el caso de prueba de la entrada
test_case = input()
if test_case == "constructor_test":
# TODO: Crea un objeto User con la contraseña inicial "secure123"
user = User("secure123")
print("User created successfully")
print("Initial password set")
elif test_case == "correct_password_test":
# TODO: Crea un objeto User con la contraseña inicial "secure123"
user = User("secure123")
# TODO: Prueba el método check_password con la contraseña correcta
# Imprime "Password check successful!" si devuelve True
# Imprime "Password check failed!" si devuelve False
result = user.check_password("secure123")
if result:
print("Password check successful!")
else:
print("Password check failed!")
elif test_case == "incorrect_password_test":
# TODO: Crea un objeto User con la contraseña inicial "secure123"
user = User("secure123")
# TODO: Prueba el método check_password con la contraseña incorrecta "wrong"
# Imprime "Incorrect password rejected!" si devuelve False
# Imprime "Security issue: incorrect password accepted!" si devuelve True
result = user.check_password("wrong")
if not result:
print("Incorrect password rejected!")
else:
print("Security issue: incorrect password accepted!")
elif test_case == "change_password_test":
# TODO: Crea un objeto User con la contraseña inicial "secure123"
user = User("secure123")
# TODO: Prueba a cambiar la contraseña de "secure123" a "newpass456"
# Imprime "Password changed successfully!" si devuelve True
# Imprime "Password change failed!" si devuelve False
result = user.change_password("secure123", "newpass456")
if result:
print("Password changed successfully!")
else:
print("Password change failed!")
# TODO: Verifica que la nueva contraseña funcione comprobando "newpass456"
# Imprime "New password works!" si devuelve True
# Imprime "New password doesn't work!" si devuelve False
if user.check_password("newpass456"):
print("New password works!")
else:
print("New password doesn't work!")
elif test_case == "change_password_wrong_old_test":
# TODO: Crea un objeto User con la contraseña inicial "secure123"
user = User("secure123")
# TODO: Intenta cambiar la contraseña con una contraseña anterior incorrecta
# Imprime "Security working: incorrect old password rejected!" si devuelve False
# Imprime "Security issue: password changed with wrong old password!" si devuelve True
result = user.change_password("wrong", "hackerpw")
if not result:
print("Security working: incorrect old password rejected!")
else:
print("Security issue: password changed with wrong old password!")
elif test_case == "comprehensive_test":
# TODO: Crea un objeto User con la contraseña inicial "secure123"
user = User("secure123")
print("User created with initial password")
# TODO: Prueba el método check_password con la contraseña correcta
if user.check_password("secure123"):
print("Initial password verification successful")
# TODO: Prueba el método check_password con una contraseña incorrecta
if not user.check_password("wrongpass"):
print("Incorrect password properly rejected")
# TODO: Prueba a cambiar la contraseña de "secure123" a "newpass456"
if user.change_password("secure123", "newpass456"):
print("Password successfully changed")
# TODO: Verifica que la contraseña anterior ya no funcione
if not user.check_password("secure123"):
print("Old password no longer works")
# TODO: Verifica que la nueva contraseña funcione
if user.check_password("newpass456"):
print("New password works correctly")
# TODO: Intenta cambiar la contraseña con una contraseña anterior incorrecta
if not user.change_password("wrongold", "hackerpw"):
print("Security maintained: wrong old password rejected")
# TODO: Verifica que la contraseña siga siendo segura después de un intento fallido de cambio
if user.check_password("newpass456"):
print("Password remains secure after failed change attempt")
else:
print("Unknown test case")Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación orientada a objetos
1Fundamentos de la POO
Introducción a la POOArchivos externosClases vs. objetosEl parámetro selfMétodosAtributosMétodo constructor (__init__)Repaso: calculadora sencilla4Herencia
Herencia básicaLa función super()Sobrescritura de métodosHerencia múltipleOrden de resolución de métodosRepaso: jerarquía de empleados7Métodos especiales
Introducción a los métodos mágicosSobrecarga de operadoresMétodos mágicos de contenedoresRepaso: lista personalizada10Patrones de diseño, parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy2Decoradores
Introducción a los decoradoresDecorador de propiedadDecorador de método estáticoDecorador de método de clase5Polimorfismo
Repaso de la sobrescritura de métodosTipado patoClases y métodos abstractosDiseño de interfacesRepaso: calculadora de formas8Conceptos avanzados de POO
Composición frente a herenciaMixinsMétodos estáticos y de claseDecoradores de claseGestores de contexto11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Propiedades de clase
Variables de instancia vs. variables de claseDecoradores de propiedadesAtributos privadosRepaso: gestor de cuentas bancariasPractica por tu cuenta: Compilador de Python online