Python's Underscore Convention
Python uses naming conventions rather than strict access modifiers. Single underscore = "internal use". Double underscore = name-mangled (harder to access from outside).
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # Public
self._balance = balance # Protected (convention only)
self.__pin = "1234" # Private (name-mangled)
acc = BankAccount("Alice", 1000)
print(acc.owner) # Alice — OK
print(acc._balance) # 1000 — works but discouraged
# print(acc.__pin) # AttributeError!
print(acc._BankAccount__pin) # "1234" — mangled name@property – Clean Getters and Setters
@property lets you use attribute-style access while running code behind the scenes.
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
t = Temperature(25)
print(t.celsius) # 25
print(t.fahrenheit) # 77.0
t.celsius = 100
print(t.fahrenheit) # 212.0Using Properties for Validation
Properties enforce data integrity by validating values before setting them.
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade # calls setter
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, value):
if not 0 <= value <= 100:
raise ValueError(f"Grade must be 0-100, got {value}")
self._grade = value
s = Student("Alice", 95)
print(s.grade) # 95
s.grade = 110 # Raises ValueErrorRead-Only Properties
Omit the setter to make a property read-only.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self): return self._radius
@property
def area(self): # Computed, read-only
import math
return math.pi * self._radius ** 2
c = Circle(5)
print(round(c.area, 2)) # 78.54
# c.area = 100 # AttributeError: can't setEncapsulation in Python: Convention, Not Enforcement
Python has no truly private attributes. Instead it uses naming conventions plus a light "name mangling" trick. The philosophy: "we're all adults here" — signal intent, don't build walls.
| Name | Means | Enforced? |
|---|---|---|
value | public | — |
_value | "internal, please don't touch" | convention only |
__value | name-mangled to _Class__value | discourages, not prevents |
class Account:
def __init__(self):
self._balance = 0 # protected by convention
@property
def balance(self): # read-only public interface
return self._balance
@balance.setter
def balance(self, amt):
if amt < 0:
raise ValueError("negative balance")
self._balance = amt
a = Account()
a.balance = 100 # goes through the setter — validated
a.balance = -5 # ValueError
The @property pattern is the real tool: expose attributes directly, but route reads/writes through methods so you can validate or compute without changing the public API. Start with a plain attribute; promote it to a property only when you need logic. Double-underscore mangling is mainly for avoiding name clashes in subclasses, not security.
🏋️ Practical Exercise
Create a Temperature class:
- Store the value in a “private” attribute
_celsius. - Expose a
celsius@propertywith a getter and setter. - In the setter, reject temperatures below absolute zero (-273.15°C) with a
ValueError. - Add a read-only
fahrenheitproperty computed from celsius.
🔥 Challenge Exercise
Build an Account class that protects its balance. Use a _balance attribute, a read-only balance property, and deposit() / withdraw() methods that validate amounts (no negative deposits, no overdrafts). Demonstrate that external code cannot set the balance directly through the property, only through the controlled methods.
📋 Summary
- Encapsulation bundles data with the methods that operate on it and controls access to that data.
- Python uses convention, not enforcement: a single underscore
_xsignals “internal use”. - A double underscore
__xtriggers name mangling to_ClassName__x, discouraging accidental access. - The
@propertydecorator turns a method into a managed attribute with optional getter, setter, and deleter. - Setters are the place to validate values before they are stored.
- Omitting a setter creates a read-only (computed) property.
Interview Questions on Encapsulation
- What is encapsulation and why is it useful?
- Does Python have truly private attributes?
- What is the difference between a single underscore (
_x) and double underscore (__x) prefix? - What is name mangling and when does it happen?
- How does the
@propertydecorator work? - How do you create a read-only attribute in Python?
- Why use a property instead of a plain attribute?
Related Topics
FAQ
No. Python deliberately has no access modifiers like private. The underscore conventions and name mangling discourage external access, but a determined caller can still reach the attribute. Python trusts the developer.
When you prefix an attribute with two underscores (e.g. __balance), Python rewrites it internally to _ClassName__balance. This avoids accidental clashes in subclasses and makes the attribute harder to access from outside.
@property instead of plain attributes? +No. Start with plain attributes. Switch to a property only when you need validation, computed values, or read-only access. Because the syntax is identical to attribute access, you can refactor later without breaking callers.
Define only the getter with @property and do not add a corresponding setter. Any attempt to assign to it raises an AttributeError.

