100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

Encapsulation

How Python conventions for protected and private attributes, plus properties, let classes control access to their internal state.

Object-Oriented Programming FundamentalsBeginner10 min readJul 8, 2026
Analogies

Introduction

Encapsulation is the practice of bundling data with the methods that operate on it, and restricting direct access to that data from outside the class. The goal is to protect an object's internal state from being set into an invalid or inconsistent configuration, and to let a class change its internal implementation later without breaking code that depends on it. Unlike some languages, Python does not enforce strict access control with keywords like private; instead it relies on naming conventions and the property mechanism.

🏏

Cricket analogy: A team's dressing room keeps team strategy and player fitness data internal, letting only the coach and captain change tactics, so opponents and media can't corrupt the game plan mid-match.

Explanation

Python has three levels of attribute visibility, all convention-based rather than enforced by the interpreter (with one partial exception). A name with no leading underscore, like self.balance, is public — freely accessible from anywhere. A name with a single leading underscore, like self._balance, is protected by convention: it signals 'this is internal, please don't touch it directly from outside the class,' but Python does not stop external code from accessing it. A name with a double leading underscore (and no more than one trailing underscore), like self.__balance, triggers name mangling: Python internally rewrites it to _ClassName__balance, which makes accidental access or accidental overriding by subclasses much less likely, though it is still technically reachable if you know the mangled name. To expose controlled access to a protected or private attribute, Python provides the @property decorator for a getter and the matching @x.setter decorator for a setter, letting a class validate values on write while still offering attribute-like syntax (obj.balance = 100) to callers.

🏏

Cricket analogy: A domestic player is openly listed on the scorecard (public), a reserve player is 'protected' by convention as bench-only, and a banned player's name is mangled in records so accidental selection is unlikely but still traceable.

Example

python
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner            # public
        self._balance = balance       # protected by convention
        self.__pin = "0000"           # name-mangled to _BankAccount__pin

    @property
    def balance(self):
        """Getter: read-only view of the protected attribute."""
        return self._balance

    @balance.setter
    def balance(self, amount):
        """Setter: validates before mutating internal state."""
        if amount < 0:
            raise ValueError("Balance cannot be negative")
        self._balance = amount

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self.balance += amount  # goes through the setter's validation


account = BankAccount("Priya", 100)
account.deposit(50)
print(account.balance)        # 150 (via the getter)

try:
    account.balance = -10     # blocked by the setter's validation
except ValueError as e:
    print(f"Rejected: {e}")

print(account._BankAccount__pin)  # name-mangled, still technically reachable

Analysis

owner is public because there is no reason to restrict access to it. _balance is protected by convention: it stores the real data, but external code is expected to go through the balance property rather than touching _balance directly. The @property getter lets account.balance be read with plain attribute syntax while actually calling a method behind the scenes. The @balance.setter intercepts every assignment to account.balance, including the one inside deposit(), and raises ValueError if the new value would be invalid — this is the essence of encapsulation: the class, not the caller, enforces its own invariants. __pin demonstrates name mangling: Python rewrites it to _BankAccount__pin internally, which is why account.__pin from outside the class would raise AttributeError, yet the mangled name account._BankAccount__pin still works, showing that double-underscore attributes are obscured, not truly private.

🏏

Cricket analogy: A player's public jersey number is freely known, but the team's real batting order strategy is only revealed through the official team sheet, which the captain validates before submission, rejecting any lineup that breaks selection rules.

Key Takeaways

  • Single underscore (_x) signals 'protected by convention' — accessible but not meant to be touched externally.
  • Double underscore (__x) triggers name mangling to _ClassName__x, discouraging accidental access or override.
  • @property and @x.setter let a class expose controlled, validated access while keeping attribute-like syntax.
  • Python does not enforce true access control the way languages like Java do — it relies on convention and discipline.

Practice what you learned

Was this page helpful?

Topics covered

#Python#SoftwareEngineeringStudyNotes#SoftwareEngineering#Encapsulation#Explanation#Example#Analysis#Key#OOP#StudyNotes#SkillVeris