Menu
Coddy logo textTech

Atributos privados

Parte de la sección Object Oriented Programming del Journey de Python de Coddy — lección 15 de 64.

Los atributos privados utilizan guiones bajos para indicar que ciertos datos no deben ser accedidos directamente desde fuera de la clase.

¿Por qué necesitamos atributos privados? Imagina que tienes una cuenta bancaria. No quieres que nadie cambie directamente tu saldo; deben pasar por los canales adecuados (métodos de depósito/retiro) para que el banco pueda validar la transacción, verificar fraudes, mantener registros, etc. Los atributos privados funcionan de la misma manera: protegen tus datos para que no se modifiquen incorrectamente.

Aquí hay un ejemplo usando un guion bajo simple (convención para "uso interno"):

class Person:
    def __init__(self, name, age):
        self._name = name    # "protegido" - uso interno
        self._age = age      # "protegido" - uso interno

Todavía se puede acceder a los atributos con un solo guion bajo, pero es una señal para no hacerlo:

person = Person("Alice", 30)
print(person._name)  # Funciona, pero no se recomienda

Usa guiones bajos dobles para una mayor privacidad (name mangling):

class Person:
    def __init__(self, name, age):
        self.__name = name   # "privado" - se aplica name mangling
        self.__age = age     # "privado" - se aplica name mangling
    
    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 resultar en edades negativas u otros datos inválidos), los obligamos a usar set_age(), que valida la entrada primero. 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 nombre
  • set_age(age) - Valida la edad antes de permitir cambios (¡nada de edades negativas!)
  • Sin estos métodos, alguien podría establecer age = -100 y romper tu programa

Usa los métodos de acceso para interactuar con 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)        # Inválido: ¡La edad debe ser positiva! (la edad se mantiene en 30)

¿Ves el beneficio? El método set_age() evita datos no válidos. Sin él, podrías crear accidentalmente una persona con edad -5, ¡lo cual no tiene sentido!

Los atributos con doble guion bajo sufren una "modificación de nombre" (name mangling), pero aún se puede acceder a ellos:

person = Person("Charlie", 35)
# Esto no funciona:
# print(person.__name)  # AttributeError

# Pero esto funciona (desaconsejado):
print(person._Person__name)  # Charlie

Ejemplo del mundo real - ¿Por qué todo este código?

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # Privado: no se puede cambiar 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 saldo negativo

# Con atributos privados:
account = BankAccount(100)
account.deposit(50)           # Seguro: validado
print(account.get_balance())  # 150

El "código extra" (métodos) protege tus datos de cambios no válidos. Es como tener guardias de seguridad en lugar de dejar tus objetos de valor sin protección.

Salida:

Alice
Bob
Age must be positive!
Charlie
150

Un guion bajo simple _attribute significa "solo para uso interno" por convención. El guion bajo doble __attribute activa el name mangling para una mayor privacidad. Utilice métodos getter/setter (métodos de acceso) para interactuar correctamente con los datos privados y añadir validación. El "código extra" evita errores al asegurar que los datos sean siempre válidos.

challenge icon

Desafío

Fácil

Completa la clase User en user.py con funcionalidad de contraseña privada, luego úsala en driver.py. Sigue los comentarios TODO para implementar los métodos de verificación y cambio de contraseña.

Hoja de referencia

Los atributos privados utilizan guiones bajos para indicar que no se debe acceder a los datos directamente desde fuera de la clase.

Guion bajo simple (_attribute) - convención para "uso interno":

class Person:
    def __init__(self, name, age):
        self._name = name    # "protegido" - uso interno
        self._age = age      # "protegido" - uso interno

Doble guion bajo (__attribute) - activa el "name mangling" (desfiguración de nombres) para una mayor privacidad:

class Person:
    def __init__(self, name, age):
        self.__name = name   # "privado" - se aplica name mangling
        self.__age = age     # "privado" - se aplica name mangling
    
    def get_name(self):
        return self.__name
    
    def set_age(self, age):
        if age >= 0:
            self.__age = age
        else:
            print("Age must be positive!")

Utilice métodos de acceso para interactuar con los atributos privados:

person = Person("Bob", 25)
print(person.get_name())  # Bob
person.set_age(30)
person.set_age(-5)        # Age must be positive!

Los atributos con doble guion bajo sufren "name mangling" pero aún se puede acceder a ellos (desaconsejado):

# Esto no funciona:
# print(person.__name)  # AttributeError

# Pero esto funciona (desaconsejado):
print(person._Person__name)  # Charlie

Pruébalo tú mismo

# TODO: Importar la clase User del módulo user

# TODO: Obtener el caso de prueba de la entrada
test_case = input()

if test_case == "constructor_test":
    # TODO: Crear 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: Crear un objeto User con la contraseña inicial "secure123"
    user = User("secure123")
    # TODO: Probar el método check_password con la contraseña correcta
    #       Imprimir "Password check successful!" si devuelve True
    #       Imprimir "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: Crear un objeto User con la contraseña inicial "secure123"
    user = User("secure123")
    # TODO: Probar el método check_password con la contraseña incorrecta "wrong"
    #       Imprimir "Incorrect password rejected!" si devuelve False
    #       Imprimir "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: Crear un objeto User con la contraseña inicial "secure123"
    user = User("secure123")
    # TODO: Probar el cambio de contraseña de "secure123" a "newpass456"
    #       Imprimir "Password changed successfully!" si devuelve True
    #       Imprimir "Password change failed!" si devuelve False
    result = user.change_password("secure123", "newpass456")
    if result:
        print("Password changed successfully!")
    else:
        print("Password change failed!")
    
    # TODO: Verificar que la nueva contraseña funciona comprobando "newpass456"
    #       Imprimir "New password works!" si devuelve True
    #       Imprimir "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: Crear un objeto User con la contraseña inicial "secure123"
    user = User("secure123")
    # TODO: Intentar cambiar la contraseña con una contraseña antigua incorrecta
    #       Imprimir "Security working: incorrect old password rejected!" si devuelve False
    #       Imprimir "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: Crear un objeto User con la contraseña inicial "secure123"
    user = User("secure123")
    print("User created with initial password")
    
    # TODO: Probar el método check_password con la contraseña correcta
    if user.check_password("secure123"):
        print("Initial password verification successful")
    
    # TODO: Probar el método check_password con una contraseña incorrecta
    if not user.check_password("wrongpass"):
        print("Incorrect password properly rejected")
    
    # TODO: Probar el cambio de contraseña de "secure123" a "newpass456"
    if user.change_password("secure123", "newpass456"):
        print("Password successfully changed")
    
    # TODO: Verificar que la contraseña antigua ya no funciona
    if not user.check_password("secure123"):
        print("Old password no longer works")
    
    # TODO: Verificar que la nueva contraseña funciona
    if user.check_password("newpass456"):
        print("New password works correctly")
    
    # TODO: Intentar cambiar la contraseña con una contraseña antigua incorrecta
    if not user.change_password("wrongold", "hackerpw"):
        print("Security maintained: wrong old password rejected")
    
    # TODO: Verificar que la contraseña sigue siendo segura tras un intento fallido de cambio
    if user.check_password("newpass456"):
        print("Password remains secure after failed change attempt")

else:
    print("Unknown test case")
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming