Encapsulation — @property, Underscore Conventions & Name Mangling
IntermediatePython replaces getters/setters with @property — attribute syntax with method control — and replaces private keywords with conventions: _protected and __mangled.
Overview
Python has no private keyword; it has agreements. A single leading underscore (_balance) means "internal — do not touch from outside" (convention only). A double underscore (__balance) triggers name mangling — the attribute is renamed to _ClassName__balance, preventing accidental override in subclasses (still not true privacy). The crown jewel is @property: expose an attribute publicly, then later add validation or computation behind it WITHOUT changing any caller — the reason Python code skips Java-style getX/setX entirely.
@property — Computed & Validated Attributes
Start with a plain attribute. Need validation later? Convert to a property — callers keep writing obj.attr. The setter runs your checks; a property without a setter is read-only.
class Account:
def __init__(self, balance):
self._balance = balance # internal storage
@property
def balance(self): # read: acc.balance
return self._balance
@balance.setter
def balance(self, value): # write: acc.balance = x
if value < 0:
raise ValueError("balance cannot be negative")
self._balance = value
@property
def balance_lakh(self): # computed, read-only
return round(self._balance / 1_00_000, 2)
acc = Account(2_50_000)
print(acc.balance) # 250000 — attribute syntax, method behind it
acc.balance = 3_00_000 # setter validates
print(acc.balance_lakh) # 3.0
# acc.balance = -5 # ValueError
# acc.balance_lakh = 9 # AttributeError — no setterUnderscores: _convention vs __mangling
One underscore is a social contract (linters warn, language allows). Two underscores rename the attribute per-class — useful to keep a base-class internal safe from subclass name collisions.
class Engine:
def __init__(self):
self._temp = 90 # "internal" — please don't touch
self.__serial = "X99" # mangled -> _Engine__serial
e = Engine()
print(e._temp) # 90 — allowed, but it's on you
# print(e.__serial) # AttributeError!
print(e._Engine__serial) # X99 — mangling is renaming, not security
class TurboEngine(Engine):
def __init__(self):
super().__init__()
self.__serial = "T55" # becomes _TurboEngine__serial — no clash!
t = TurboEngine()
print(t._Engine__serial, t._TurboEngine__serial) # X99 T55Key Points to Remember
- 1No private keyword — _name is convention, __name is name-mangled to _Class__name
- 2@property lets you start with plain attributes and add logic later without breaking callers
- 3Property without a setter = read-only attribute
- 4Never write Java-style get_x()/set_x() in Python — reviewers will flag it
Interview Questions
Sign in to ask AriaHow does Python implement encapsulation without private/protected keywords?
What is name mangling, what problem does it solve, and why is it not security?
Why are @property-based APIs better than getter/setter methods?
Ask Aria about Encapsulation — @property, Underscore Conventions & Name Mangling
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.