2.1.6 · D5OOP Fundamentals

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

1,556 words7 min readBack to topic

True or false — justify

Two underscores like __balance make an attribute truly private and inaccessible from outside
False — it is merely renamed to _ClassName__balance, which any outsider can type directly and read. Mangling prevents accidental clashes, not deliberate access.
_name with one underscore is enforced by the interpreter
False — a single underscore is pure convention, a "staff only" sign; Python does nothing to stop obj._name. Only the double-underscore form triggers actual renaming.
Name mangling happens at the moment you call the method that uses __name
False — mangling happens at compile time based on the class the code is written inside, not at call time. This is why Base.__data and Child.__data become distinct keys.
__init__ gets name-mangled because it starts with two underscores
False — it ends with two underscores too (a dunder), and the rule excludes anything with 2+ trailing underscores, so dunders are never mangled.
Encapsulation is mainly about security against malicious users
False — it is about protecting invariants and freedom to change internals; a determined user can always reach in. Its value is design safety, not a locked vault.
A property with only a getter and no setter is effectively read-only
True — assigning to it raises AttributeError because no setter exists, which is exactly how you expose derived state like fahrenheit without letting it be stored independently.
If you rename an internal attribute, callers who used the public interface must all update their code
False — that is the whole point of hiding: callers depend on the public method, so you can rename or restructure internals freely without breaking them.
__x_ (two leading, one trailing underscore) gets mangled
True — the rule is "at least 2 leading, at most 1 trailing," and one trailing underscore is within the limit, so it becomes _Class__x_.
Using __ on every attribute is a safe default for good encapsulation
False — over-mangling breaks legitimate subclass access and complicates debugging; prefer single _name, reserving __name only for genuine clash avoidance.
Encapsulation and abstraction are the same thing
False — abstraction hides complexity (what an operation means), while encapsulation hides state (bundling + access control). They cooperate but answer different questions.

Spot the error

class Box:
    def __init__(self):
        self.__size = 10
b = Box()
print(b.__size)   # expected 10

::: b.__size fails with AttributeError; from outside there is no __size, only _Box__size. Mangling only rewrites __size inside the class body, not in outside code.

class Base:
    def __init__(self): self.__id = 1
class Child(Base):
    def show(self): return self.__id   # wants Base's id

::: self.__id inside Child mangles to _Child__id, which does not exist — you get AttributeError. The Base value lives under _Base__id; the two names deliberately do not meet.

class Temp:
    @property
    def celsius(self): return self._c
t = Temp(); t.celsius = 25

::: Assignment raises AttributeError because no setter was defined — a getter-only property is read-only. Add a @celsius.setter if you intend to allow writes.

class Acct:
    def __init__(self): self.__bal = 0
    def deposit(self, x): self.__bal += x
a = Acct(); a._Acct__bal = -500   # "just checking the balance"

::: No error, but you have bypassed the guard and put the object in an invalid state. This demonstrates that mangling is not protection — the invariant lives in deposit, and reaching around it breaks the design contract.

class C:
    def __init__(self): self.__str__ = "hi"   # trying to hide it via mangling

::: __str__ is a dunder (2 trailing underscores), so it is not mangled — you just shadowed the special method name as an instance attribute, which is confusing and can break str(obj) expectations. Never treat dunders as manglable.


Why questions

Why does mangling use the class name rather than just adding a random suffix
Because the class name makes each version deterministic and self-documenting: Base's code always looks up _Base__x and Child's always looks up _Child__x, so methods reliably find their own attribute across inheritance.
Why is a._Account__balance being accessible a feature, not a bug
It proves mangling is a naming discipline, not a security wall — Python's "consenting adults" philosophy means you can reach in when you truly must (debugging, serialization), while normal code stays cleanly separated.
Why prefer a property over a plain public attribute when you might add validation later
Because a property lets you start with attribute-like syntax obj.x and later insert a setter with checks without changing any caller's code. A plain attribute would force callers to migrate to get_/set_ methods.
Why does encapsulation give you "freedom to change"
Hidden internals are not part of the public promise, so no external code depends on them; you can rewrite storage, add caching, or rename fields, and only the public interface must stay stable.
Why is fahrenheit computed on access instead of stored
Because it is derived from celsius — storing it separately would create two sources of truth that can drift out of sync. Computing on read keeps it always consistent with the underlying _celsius.
Why does Python not just add a private keyword like Java
Python's design bets that convention plus mild mangling is enough, and that hard enforcement fights the flexibility (metaprogramming, testing, debugging) the language values. See Access Modifiers (Java vs Python) for the contrast.

Edge cases

What does __ (exactly two underscores, no name) do inside a class
Nothing manglable — there is no identifier after the underscores, so it is not a valid __name pattern; it is just a two-character name and mangling has no name to attach the class prefix to.
Is __name__ (leading and trailing dunder) ever mangled
No — it has 2+ trailing underscores, which the rule explicitly excludes, so dunders like __name__, __init__, __dict__ always keep their exact spelling.
What happens if you write self.__x inside a method that is defined outside any class (a plain function)
No mangling occurs — the rule fires only inside a class body. Outside, __x is just an ordinary name with two leading underscores.
If a subclass genuinely needs the parent's __data, what should it use
It must reference the fully mangled name self._Parent__data, or better, the parent should expose it via a _data single-underscore attribute or a property. Mangling deliberately blocks the plain __data path — see Inheritance — base and derived classes.
Can two different classes both use __id without ever colliding, even if unrelated
Yes — each mangles to _ClassA__id and _ClassB__id respectively, so they occupy different keys in each instance's __dict__ regardless of any relationship between the classes.
What is stored in __dict__ for an object that set self.__x
The key _ClassName__x, not __x — inspecting obj.__dict__ reveals the mangled name, which is a handy way to confirm mangling actually happened.
Does a property's validation protect against obj._celsius = -300 directly
No — the setter only runs for obj.celsius = -300; assigning the underlying _celsius skips the check entirely. This is why the invariant is only as strong as the discipline to always go through the public property. See Class Invariants & Validation.
Does @property combine with name mangling for stronger hiding
They are orthogonal — you can store state in a mangled __value and expose it via a property, but the property does the controlled access, the mangling only hides the raw name. Neither makes the object truly tamper-proof. See Properties and Descriptors.

Recall One-line summary to carry away

Mangling is a name tag, not a lock; properties are the guard, not the wall. Real safety comes from routing all changes through validated methods — and even then, Python trusts the caller. Full teaching version: OOP Fundamentals.