2.1.6OOP Fundamentals

Encapsulation — hiding internal state, name mangling (`__name`)

1,987 words9 min readdifficulty · medium1 backlinks

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__id
  • Child.__id_Child__id

They no longer collide — each class's methods quietly refer to their own version.

Figure — Encapsulation — hiding internal state, name mangling (`__name`)

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.__balance
a = Account(100)
print(a.get_balance())   # 100
a.__balance              # AttributeError!
print(a._Account__balance)  # 100  <- the mangled name
  • Why self.__balance inside __init__? Why this step: writing __balance triggers mangling, so it actually stores _Account__balance. Outsiders typing a.__balance look for a non-existent attribute → AttributeError. The invariant (balance >= 0) can only be changed via deposit.
  • Why does a._Account__balance work? 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__data
c = 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.__data clobber Base.__data? Why this step: each __data was 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 = -300 now runs the setter and is rejected, protecting the invariant — while callers still use clean t.celsius syntax (no ugly get_/set_).
  • Why is fahrenheit read-only? Why this step: no setter defined, so assigning raises AttributeError; 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?
Bundling data + methods into one unit AND restricting direct external access to internal state, exposing only a controlled interface.
Does Python have a real private keyword?
No — it uses conventions (_name, __name) plus name mangling, not hard enforcement.
What does a single leading underscore _name mean?
A convention signaling "internal, don't touch"; NOT enforced by the interpreter.
What does name mangling turn __x into, inside class Foo?
_Foo__x.
What is the PURPOSE of name mangling?
To prevent accidental name clashes between a class and its subclasses, NOT to provide security.
Can you still access a mangled attribute from outside?
Yes, by using its mangled name, e.g. obj._Foo__x.
Is __str__ name-mangled?
No — dunders (≥2 trailing underscores) are excluded from mangling.
What are the conditions for an identifier to be mangled?
At least 2 leading underscores AND at most 1 trailing underscore, written inside a class body.
What is the Pythonic way to expose controlled access while protecting invariants?
Use @property getters/setters with validation.
How do you make a read-only computed attribute?
Define a @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

is

is

enables

enables

signaled by

convention only

mild enforcement

triggers

produces

purpose is to

not for

Encapsulation

Bundling data plus methods

Information hiding

Guard invariants

Free to change internals

Python conventions

_name single underscore

__name double underscore

Name mangling

Rewritten to _ClassName__name

Avoid inheritance name clashes

Not security

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.

Go deeper — visual, from zero

Test yourself — OOP Fundamentals

Connections