Encapsulation — hiding internal state, name mangling (`__name`)
WHAT is Encapsulation?
HOW Python signals "private"
Python has no hard private keyword like Java's private. Instead it uses conventions plus one mild enforcement mechanism (name mangling).
| Style | Meaning | Enforced? |
|---|---|---|
name |
public | — |
_name (one underscore) |
"internal, please don't touch" | No — convention only |
__name (two underscores) |
name-mangled, avoid clashes | Yes (mild) |
__name__ (dunder) |
special/magic, NOT mangled | — |
Name Mangling — the one real mechanism
WHY does this exist? (Derive the motivation from scratch)
The purpose is NOT security. It is to avoid accidental name clashes in inheritance.
Consider a Base class with an internal __id, and a Child that also invents an attribute named __id for a totally different purpose. Without mangling, Child.__id would silently overwrite Base.__id and break Base's methods.
HOW mangling solves it: the interpreter renames each one to belong to its own class:
Base.__id→_Base__idChild.__id→_Child__id
They no longer collide — each class's methods quietly refer to their own version.

Worked Example 1 — A self-guarding bank account
class Account:
def __init__(self, balance):
self.__balance = 0 # private internal state
self.deposit(balance) # go through the guard
def deposit(self, amount):
if amount < 0: # invariant check
raise ValueError("no negatives")
self.__balance += amount
def get_balance(self):
return self.__balancea = Account(100)
print(a.get_balance()) # 100
a.__balance # AttributeError!
print(a._Account__balance) # 100 <- the mangled name- Why
self.__balanceinside__init__? Why this step: writing__balancetriggers mangling, so it actually stores_Account__balance. Outsiders typinga.__balancelook for a non-existent attribute →AttributeError. The invariant (balance >= 0) can only be changed viadeposit. - Why does
a._Account__balancework? Why this step: mangling only rewrites code inside the class body. From outside you can type the mangled name literally — proving this is not security, just clash-avoidance.
Worked Example 2 — Mangling preventing inheritance collision
class Base:
def __init__(self):
self.__data = "base secret" # -> _Base__data
def reveal(self):
return self.__data # compiled as self._Base__data
class Child(Base):
def __init__(self):
super().__init__()
self.__data = "child stuff" # -> _Child__data (different!)
def reveal_child(self):
return self.__data # self._Child__datac = Child()
print(c.reveal()) # "base secret" <- Base's data intact!
print(c.reveal_child()) # "child stuff"
print(c.__dict__) # {'_Base__data': ..., '_Child__data': ...}- Why doesn't
Child.__dataclobberBase.__data? Why this step: each__datawas mangled at compile time using the class it was written in, giving two distinct keys. This is the whole point of mangling.
Worked Example 3 — Properties: the Pythonic way to hide
Name mangling protects internal names, but the idiomatic way to expose controlled access is @property.
class Temperature:
def __init__(self, celsius):
self._celsius = celsius # single underscore: "internal"
@property
def celsius(self): # getter
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15: # invariant: above absolute zero
raise ValueError("below absolute zero")
self._celsius = value
@property
def fahrenheit(self): # computed, read-only
return self._celsius * 9/5 + 32- Why a property and not a public attribute? Why this step:
t.celsius = -300now runs the setter and is rejected, protecting the invariant — while callers still use cleant.celsiussyntax (no uglyget_/set_). - Why is
fahrenheitread-only? Why this step: no setter defined, so assigning raisesAttributeError; it's derived state and shouldn't be stored independently.
Common Mistakes (Steel-manned)
Recall Feynman: explain to a 12-year-old
A toy robot has a battery sealed inside. You control it with a remote — buttons for forward, turn, beep. You can't yank the battery out mid-game, so the robot never suddenly dies in a weird way. That sealed battery is "private state," and the remote is the "public interface." In Python, putting two underscores like __battery is like writing your own secret nickname on the battery so that if a bigger robot is built around this one and also has a battery, the two batteries don't get mixed up. It's a name tag, not a real lock.
Flashcards
What is encapsulation?
Does Python have a real private keyword?
_name, __name) plus name mangling, not hard enforcement.What does a single leading underscore _name mean?
What does name mangling turn __x into, inside class Foo?
_Foo__x.What is the PURPOSE of name mangling?
Can you still access a mangled attribute from outside?
obj._Foo__x.Is __str__ name-mangled?
What are the conditions for an identifier to be mangled?
What is the Pythonic way to expose controlled access while protecting invariants?
@property getters/setters with validation.How do you make a read-only computed attribute?
@property getter with no setter.Connections
- OOP Fundamentals
- Abstraction — hiding complexity behind interfaces
- Inheritance — base and derived classes
- Properties and Descriptors
- Class Invariants & Validation
- Access Modifiers (Java vs Python)
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Encapsulation ka matlab simple hai: data aur uske methods ko ek hi object ke andar bundle karo, aur bahar wale logon ko internal state seedhe chhune mat do. Jaise vending machine — tum buttons dabate ho, par andar ka coin box nahi nikaal sakte. Isse object apne "rules" (invariants, jaise balance kabhi negative na ho) khud guard kar paata hai. Agar koi bhi account.balance = -500 set kar de, toh object galat state me chala jaayega — encapsulation yahi rokta hai.
Python me Java jaisa private keyword nahi hota. Sirf conventions hain: _name ek single underscore matlab "bhai, ye internal hai, mat chhuo" — par ye sirf politeness hai, koi rok nahi. Aur __name (double underscore) pe name mangling lagta hai: interpreter use chupke se _ClassName__name me badal deta hai. Isiliye bahar se a.__balance likho toh AttributeError aata hai, lekin a._Account__balance likho toh chal jaata hai — yaani ye security nahi hai, sirf naam badalna hai.
Toh phir mangling ka asli fayda kya? Inheritance me name clash rokna. Maan lo Base me __data hai aur Child me bhi galti se __data bana diya. Bina mangling ke dono ek doosre ko overwrite kar dete. Par mangling ke kaaran ek banta hai _Base__data aur doosra _Child__data — dono alag, koi clash nahi, Base ke methods safe.
Practical advice: by default _name (single underscore) use karo "internal" ke liye. __name sirf tab jab genuinely subclass clash ka dar ho. Aur jab tumhe controlled access chahiye with validation, toh @property use karo — clean syntax bhi milta hai aur invariant bhi protect hota hai. Yaad rakhna: Python philosophy hai "we're all consenting adults" — trust + convention, locked vault nahi.