Menu
Coddy logo textTech

Private Attributes

Part of the Object Oriented Programming section of Coddy's Python journey — lesson 15 of 64.

Private attributes use underscores to indicate that certain data should not be accessed directly from outside the class.

Why do we need private attributes? Imagine you have a bank account. You don't want anyone to directly change your balance - they should go through proper channels (deposit/withdraw methods) so the bank can validate the transaction, check for fraud, keep records, etc. Private attributes work the same way - they protect your data from being changed incorrectly.

Here is an example using single underscore (convention for "internal use"):

class Person:
    def __init__(self, name, age):
        self._name = name    # "protected" - internal use
        self._age = age      # "protected" - internal use

Single underscore attributes can still be accessed, but it's a signal not to:

person = Person("Alice", 30)
print(person._name)  # Works, but not recommended

Use double underscores for stronger privacy (name mangling):

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

Why use getter and setter methods? They're like gatekeepers. Instead of letting anyone change __age directly (which could result in negative ages or other invalid data), we force them to use set_age() which validates the input first. This prevents bugs and ensures data integrity.

The "extra code" has a purpose:

  • get_name() - Controlled access to read the name
  • set_age(age) - Validates age before allowing changes (no negative ages!)
  • Without these methods, someone could set age = -100 and break your program

Use the accessor methods to interact with private attributes:

person = Person("Bob", 25)
print(person.get_name())  # Bob

person.set_age(30)        # Valid: age becomes 30
person.set_age(-5)        # Invalid: Age must be positive! (age stays 30)

See the benefit? The set_age() method prevents invalid data. Without it, you could accidentally create a person with age -5, which makes no sense!

Double underscore attributes get "name mangled" but can still be accessed:

person = Person("Charlie", 35)
# This doesn't work:
# print(person.__name)  # AttributeError

# But this works (discouraged):
print(person._Person__name)  # Charlie

Real-world example - Why all this code?

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # Private: can't be changed directly
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return True
        return False
    
    def get_balance(self):
        return self.__balance

# Without private attributes:
# account.__balance = -1000000  # Disaster! Negative balance allowed

# With private attributes:
account = BankAccount(100)
account.deposit(50)           # Safe: validated
print(account.get_balance())  # 150

The "extra code" (methods) protects your data from invalid changes. It's like having security guards instead of leaving your valuables unprotected.

Output:

Alice
Bob
Age must be positive!
Charlie
150

Single underscore _attribute means "internal use only" by convention. Double underscore __attribute triggers name mangling for stronger privacy. Use getter/setter methods (accessor methods) to properly interact with private data and add validation. The "extra code" prevents bugs by ensuring data is always valid.

challenge icon

Challenge

Easy

Complete the User class in user.py with private password functionality, then use it in driver.py. Follow the TODO comments to implement password checking and changing methods.

Cheat sheet

Private attributes use underscores to indicate that data should not be accessed directly from outside the class.

Single underscore (_attribute) - convention for "internal use":

class Person:
    def __init__(self, name, age):
        self._name = name    # "protected" - internal use
        self._age = age      # "protected" - internal use

Double underscore (__attribute) - triggers name mangling for stronger privacy:

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

Use accessor methods to interact with private attributes:

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

Double underscore attributes get name mangled but can still be accessed (discouraged):

# This doesn't work:
# print(person.__name)  # AttributeError

# But this works (discouraged):
print(person._Person__name)  # Charlie

Try it yourself

# TODO: Import the User class from the user module

# TODO: Get test case from input
test_case = input()

if test_case == "constructor_test":
    # TODO: Create a User object with initial password "secure123"
    user = User("secure123")
    print("User created successfully")
    print("Initial password set")

elif test_case == "correct_password_test":
    # TODO: Create a User object with initial password "secure123"
    user = User("secure123")
    # TODO: Test the check_password method with correct password
    #       Print "Password check successful!" if it returns True
    #       Print "Password check failed!" if it returns False
    result = user.check_password("secure123")
    if result:
        print("Password check successful!")
    else:
        print("Password check failed!")

elif test_case == "incorrect_password_test":
    # TODO: Create a User object with initial password "secure123"
    user = User("secure123")
    # TODO: Test the check_password method with incorrect password "wrong"
    #       Print "Incorrect password rejected!" if it returns False
    #       Print "Security issue: incorrect password accepted!" if it returns 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: Create a User object with initial password "secure123"
    user = User("secure123")
    # TODO: Test changing password from "secure123" to "newpass456"
    #       Print "Password changed successfully!" if it returns True
    #       Print "Password change failed!" if it returns False
    result = user.change_password("secure123", "newpass456")
    if result:
        print("Password changed successfully!")
    else:
        print("Password change failed!")
    
    # TODO: Verify the new password works by checking "newpass456"
    #       Print "New password works!" if it returns True
    #       Print "New password doesn't work!" if it returns 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: Create a User object with initial password "secure123"
    user = User("secure123")
    # TODO: Try changing password with incorrect old password
    #       Print "Security working: incorrect old password rejected!" if it returns False
    #       Print "Security issue: password changed with wrong old password!" if it returns 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: Create a User object with initial password "secure123"
    user = User("secure123")
    print("User created with initial password")
    
    # TODO: Test the check_password method with correct password
    if user.check_password("secure123"):
        print("Initial password verification successful")
    
    # TODO: Test the check_password method with incorrect password
    if not user.check_password("wrongpass"):
        print("Incorrect password properly rejected")
    
    # TODO: Test changing password from "secure123" to "newpass456"
    if user.change_password("secure123", "newpass456"):
        print("Password successfully changed")
    
    # TODO: Verify old password no longer works
    if not user.check_password("secure123"):
        print("Old password no longer works")
    
    # TODO: Verify the new password works
    if user.check_password("newpass456"):
        print("New password works correctly")
    
    # TODO: Try changing password with incorrect old password
    if not user.change_password("wrongold", "hackerpw"):
        print("Security maintained: wrong old password rejected")
    
    # TODO: Verify password is still secure after failed change attempt
    if user.check_password("newpass456"):
        print("Password remains secure after failed change attempt")

else:
    print("Unknown test case")
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming