Question bank — Encapsulation — hiding internal state, name mangling (`__name`)
True or false — justify
Two underscores like __balance make an attribute truly private and inaccessible from outside
_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
obj._name. Only the double-underscore form triggers actual renaming.Name mangling happens at the moment you call the method that uses __name
Base.__data and Child.__data become distinct keys.__init__ gets name-mangled because it starts with two underscores
Encapsulation is mainly about security against malicious users
A property with only a getter and no setter is effectively read-only
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
__x_ (two leading, one trailing underscore) gets mangled
_Class__x_.Using __ on every attribute is a safe default for good encapsulation
_name, reserving __name only for genuine clash avoidance.Encapsulation and abstraction are the same thing
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
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
Why prefer a property over a plain public attribute when you might add validation later
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"
Why is fahrenheit computed on access instead of stored
_celsius.Why does Python not just add a private keyword like Java
Edge cases
What does __ (exactly two underscores, no name) do inside a class
__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
__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)
__x is just an ordinary name with two leading underscores.If a subclass genuinely needs the parent's __data, what should it use
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
_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
_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
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
__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.