Skip to Content
Course content

128: Encapsulation Conventions: _protected and __private

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was lead on a project where we had a junior dev working on a complex data-parsing library. He noticed a method in one of my classes called _calculate_internal_checksum(). Because it worked exactly how he needed it to, he called it directly from his own module. For three months, everything was fine. Then, I refactored that method, renamed it, and changed its return type because it was an internal implementation detail that didn't affect the public API. The moment we deployed, the entire ingestion pipeline crashed. He had tied his code to a "private" piece of my class that I thought was safe to change.

In languages like Java or C#, you have keywords like private and protected that the compiler enforces. If you try to touch a private member from outside the class, the code simply won't compile. Python is different. Python operates on the "consenting adults" philosophy: it assumes you know what you're doing and won't stop you from breaking things, but it gives you conventions to signal your intent.

The "Gentleman's Agreement" of the Single Underscore

When you prefix a variable or method with a single underscore, like _status or _update_cache(), you aren't actually locking the door. You're just putting up a sign that says "Staff Only."

Technically, Python does nothing to stop someone from accessing obj._status. However, the community recognizes this as a "protected" member. I use this whenever I have a method that is essential for the class to function but would be confusing or dangerous for an end-user to call directly. It tells other developers: "I might change this method's name or logic tomorrow, so if you rely on it, your code might break."

class SmartLight:
    def __init__(self, brightness):
        self.brightness = brightness
        self._connection_timeout = 30  # Internal setting, don't touch!

    def _check_connection(self):
        print("Checking hardware connection...")
        return True

    def set_brightness(self, level):
        if self._check_connection():
            self.brightness = level

Name Mangling with Double Underscores

Sometimes, a single underscore isn't enough, especially when you're building a class hierarchy. If you use a double underscore, like __secret, Python triggers a mechanism called name mangling. This is less about security and more about preventing "name clashes" in subclasses.

When Python sees __private_var inside a class called Account, it internally renames it to _Account__private_var. This makes it much harder (though still not impossible) to access from outside the class or to accidentally override it in a child class.

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # Mangled to _BankAccount__balance

    def get_balance(self):
        return self.__balance

class SavingsAccount(BankAccount):
    def __init__(self, balance, interest_rate):
        super().__init__(balance)
        self.__balance = 0  # This creates a NEW variable, it doesn't overwrite the parent's!

account = BankAccount(1000)
# print(account.__balance)  # This would raise an AttributeError
print(account._BankAccount__balance)  # This works, but it's a huge "no-no" in professional code.

I generally advise against using double underscores unless you are writing a library that will be inherited by dozens of other developers. For 95% of your work, the single underscore is the right tool. It communicates intent without adding the weird overhead of name mangling.




📋 Practical Task

Exercise: Implementing a Secure CreditCard Validator

You need to build a CreditCard class that protects sensitive data while allowing controlled access. Follow these requirements:

  • Create a class CreditCard that takes card_number and cvv in its constructor.
  • Store the card_number using name mangling (double underscore) so it isn't easily accessible from outside the class.
  • Store the cvv using the protected convention (single underscore).
  • Implement a method get_masked_number() that returns the card number but replaces all but the last four digits with asterisks (e.g., ************1234).
  • Implement a method validate_cvv(input_cvv) that returns True if the input matches the protected CVV, and False otherwise.

Test your class by attempting to access __card_number directly from an instance to confirm it raises an AttributeError, then use your masking method to verify it works.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.