Concept
Bundle data (attributes) and methods (behaviours) into a single unit (class). Encapsulation restricts direct access to some of the object’s components, which prevents accidental modification of data and hides internal state and implementation details.
| Type | Prefix | Description |
|---|
| Public | (none) | Accessible from anywhere, no restrictions. |
| Protected | _ (single underscore) | Accessible within the class and its subclasses, but not from outside by convention. |
| Private | __ (double underscore) | Accessible only within the class, not from outside or subclasses. |
Data Protection
- Prevent direct access to sensitive data
- Validate data before modification
- Maintains data integrity
- Controls how data is accessed and modified
Code Maintainability
- Internal implementation can change without affecting external code
- External interface remains stable
- Reduces dependencies on internal details (reduces coupling between classes)
- Makes code easier to maintain and refactor
Implementation
Encapsulation is implemented through private, protected, and public attributes and methods.
Get and Set Methods
Property Decorators
Use explicit get_ and set_ methods to control access to private and protected attributes.class BankAccount:
def __init__(self, owner, balance, pin):
self.owner = owner # Public attribute
self._balance = balance # Protected attribute
self.__pin = pin # Private attribute
def get_pin(self):
return self.__pin
def set_pin(self, pin):
self.__pin = pin
def deposit(self, amount):
if amount > 0:
self._balance += amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self._balance:
self._balance -= amount
return True
return False
def get_balance(self):
return self._balance
Python’s @property decorator provides a cleaner, Pythonic way to create getters and setters.class BankAccount:
def __init__(self, username, password, balance, pin):
self.username = username
self._balance = balance
self.__password = password
self.__pin = pin
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, amount):
if amount >= 0:
self._balance = amount
else:
raise ValueError("Balance cannot be negative")
With @property, you can access balance like a regular attribute while still having validation logic:account = BankAccount("alice", "secret", 1000, 1234)
print(account.balance) # 1000
account.balance = 1500 # Uses the setter
account.balance = -100 # Raises ValueError
Python does not enforce access control at the language level like Java or C++. The _ and __ prefixes are conventions. Double-underscore attributes undergo name mangling (e.g., __pin becomes _BankAccount__pin), making them harder but not impossible to access from outside the class.