Encapsulation in Python
What is Encapsulation?
Encapsulation bundles data (attributes) and the methods that operate on that data into a single unit (class), and restricts direct access to some components. It protects the internal state of an object from unintended modification.
Access Modifiers in Python
Python uses naming conventions rather than strict keywords like private or protected.
| Convention | Example | Meaning |
|---|---|---|
| No prefix | self.name | Public — accessible anywhere |
Single underscore _ | self._salary | Protected — "don't touch from outside" (convention only) |
Double underscore __ | self.__password | Private — name-mangled, harder to access from outside |
class BankAccount:
def __init__(self, owner: str, balance: float):
self.owner = owner # public
self._account_type = "savings" # protected (convention)
self.__balance = balance # private (name-mangled)
def deposit(self, amount: float):
if amount > 0:
self.__balance += amount
def get_balance(self) -> float:
return self.__balance # controlled access
account = BankAccount("Alice", 1000)
print(account.owner) # Alice (public — fine)
print(account._account_type) # savings (works but discouraged)
print(account.get_balance()) # 1000 (via method — correct way)
# Direct access to __balance fails
# print(account.__balance) # AttributeError!
# Name mangling — Python renames it to _ClassName__attr
print(account._BankAccount__balance) # 1000 (possible but bad practice)
Getters and Setters with @property
class Temperature:
def __init__(self, celsius: float = 0):
self._celsius = celsius
@property
def celsius(self) -> float:
"""Getter"""
return self._celsius
@celsius.setter
def celsius(self, value: float):
"""Setter with validation"""
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._celsius = value
@property
def fahrenheit(self) -> float:
"""Computed property — no setter needed"""
return self._celsius * 9/5 + 32
@property
def kelvin(self) -> float:
return self._celsius + 273.15
t = Temperature(25)
print(t.celsius) # 25 — looks like attribute access
print(t.fahrenheit) # 77.0
print(t.kelvin) # 298.15
t.celsius = 100 # uses setter
print(t.fahrenheit) # 212.0
# t.celsius = -300 # ValueError!
Real-World Encapsulation Example
class BankAccount:
def __init__(self, owner: str, initial_balance: float = 0):
self.__owner = owner
self.__balance = initial_balance
self.__transactions = []
@property
def owner(self) -> str:
return self.__owner
@property
def balance(self) -> float:
return self.__balance
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.__balance += amount
self.__transactions.append(f"+${amount:.2f}")
def withdraw(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > self.__balance:
raise ValueError("Insufficient funds")
self.__balance -= amount
self.__transactions.append(f"-${amount:.2f}")
def get_statement(self) -> str:
history = "\n".join(self.__transactions) or "No transactions"
return f"Account: {self.__owner}\nBalance: ${self.__balance:.2f}\n{history}"
acc = BankAccount("Alice", 500)
acc.deposit(200)
acc.withdraw(100)
print(acc.get_statement())
# Account: Alice
# Balance: $600.00
# +$200.00
# -$100.00