1. Introduction
Encapsulation is a cornerstone of object-oriented programming (OOP) that bundles data (attributes) and the methods that manipulate it into a single unit — a class — while restricting direct access to the data.
Think of encapsulation as a secure vault. Inside the vault, you store sensitive items (data) and provide specific tools (methods) to interact with them. Only authorized personnel (the class’s methods) can access or modify the contents, while outsiders (external code) must use the provided interface.
In Python, encapsulation uses naming conventions:
- Public: No prefix, accessible everywhere (e.g., data).
- Protected: Single underscore (_data), a hint for internal or subclass use, though not enforced.
- Private: Double underscore (__data), triggering name mangling to discourage direct access.
2. Encapsulation in Python: Public, Protected, and Private
Example 1: Public Members — Weather Data
class WeatherStation:
def __init__(self, temperature):
self.temp = temperature # Public attribute
def report(self):
print(f"Current temperature: {self.temp}°C")
# Using the class
station = WeatherStation(25)
print(station.temp) # Direct access
station.temp = -500 # Oops, unrealistic value
print(station.temp)
station.report()
Output:
25
-500
Current temperature: -500°C
Detailed Explanation:
- temp is public, so anyone can read or write it (station.temp = -500).
- Pros: Simple and straightforward for small, safe scenarios.
- Cons: No validation means temp can be set to absurd values (e.g., -500°C), breaking the realism of a weather system.
Example 2: Protected Members — Inventory Tracker
class Inventory:
def __init__(self, stock):
self._stock = stock # Protected attribute
def check_stock(self):
print(f"Items in stock: {self._stock}")
def restock(self, amount):
self._stock += amount
print(f"Restocked {amount}. New stock: {self._stock}")
# Using the class
shop = Inventory(100)
print(shop._stock) # Accessible, but discouraged
shop._stock = -10 # Can still modify, though it’s "protected"
print(shop._stock)
shop.check_stock()
shop.restock(50)
Output:
100
-10
Items in stock: -10
Restocked 50. New stock: 40
Detailed Explanation:
- _stock with a single underscore is “protected,” signaling it’s for internal use or subclasses.
- Python doesn’t block access (shop._stock = -10 works), but the underscore tells developers, “Hey, use the methods instead!”
- Pros: Suggests a boundary, useful for teamwork or documentation.
- Cons: No real protection — negative stock is still possible, which doesn’t make sense for an inventory.
Example 3: Private Members — Patient Records
class PatientRecord:
def __init__(self, name, age):
self.__name = name # Private attribute
self.__age = None # Private attribute
self.set_age(age) # Use setter for validation
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def set_age(self, new_age):
if isinstance(new_age, int) and 0 <= new_age <= 120:
self.__age = new_age
else:
print(f"Error: Age must be an integer between 0 and 120. Keeping {self.__age}")
def display(self):
print(f"Patient: {self.__name}, Age: {self.__age}")
# Using the class
patient = PatientRecord("Emma", 30)
print(patient.get_name()) # Access via getter
patient.set_age(35) # Update via setter
patient.display()
patient.set_age(-5) # Invalid age
# print(patient.__age) # AttributeError
patient.display()
Output:
Emma
Patient: Emma, Age: 35
Error: Age must be an integer between 0 and 120. Keeping 35
Patient: Emma, Age: 35
Detailed Explanation:
- __name and __age are private,, making direct access tricky (patient.__age fails).
- get_name, get_age, and set_age provide a controlled interface. set_age validates input, ensuring age stays realistic (0–120).
- Pros: Protects sensitive data (like patient info) and enforces rules.
3. Why Encapsulation Matters: A Problem and Solution
Let’s apply encapsulation to a real-world data problem.
Problem: Managing a Budget Tracker
You’re creating a Budget class to track expenses. Without encapsulation, users could set the budget to negative values or bypass spending limits, breaking financial logic.
class Budget:
def __init__(self, total):
self.total = total # Public attribute
def spend(self, amount):
self.total -= amount
print(f"Spent {amount}. Remaining: {self.total}")
# Using the class
budget = Budget(1000)
print(f"Initial budget: {budget.total}")
budget.spend(300)
budget.total = -500 # Direct manipulation!
print(f"After tampering: {budget.total}")
Output:
Initial budget: 1000
Spent 300. Remaining: 700
After tampering: -500
Issue:
- total is public, so it can be set to invalid values (e.g., -500), which doesn’t make sense for a budget.
Solution: Encapsulation with Private Members
class Budget:
def __init__(self, total):
self.__total = None # Private attribute
self.set_total(total) # Validate initial value
def get_total(self):
return self.__total
def set_total(self, amount):
if isinstance(amount, (int, float)) and amount >= 0:
self.__total = amount
else:
print(f"Error: Budget must be non-negative. Keeping {self.__total}")
def spend(self, amount):
if amount > 0 and amount <= self.__total:
self.__total -= amount
print(f"Spent {amount}. Remaining: {self.__total}")
else:
print(f"Error: Invalid spend amount or insufficient funds")
# Using the class
budget = Budget(1000)
print(f"Initial budget: {budget.get_total()}")
budget.spend(300)
budget.set_total(-500) # Invalid attempt
# budget.__total = 2000 # AttributeError
budget.spend(200)
print(f"Final budget: {budget.get_total()}")
Output:
Initial budget: 1000
Spent 300. Remaining: 700
Error: Budget must be non-negative. Keeping 700
Spent 200. Remaining: 500
Final budget: 500
Explanation of the Solution:
- __total is private, preventing direct changes (budget.__total = 2000 fails).
- set_total ensures the budget stays non-negative, and spend checks for valid withdrawals.
- get_total provides read-only access, keeping the interface clean.
- Benefit: The budget stays logical and secure, avoiding negative or inconsistent states.
'Python Intermediate and Advanced' 카테고리의 다른 글
Python Intermediate_015_ Sorting with .sort() and Lambda in Python (0) | 2025.04.05 |
---|---|
Python Intermediate_014: Understanding @staticmethod in Python (0) | 2025.04.05 |
Python Intermediate_013: Understanding @classmethod in Python (0) | 2025.04.05 |
Python Intermediate_012:Python’s Magic Methods in Classes(Overview) (0) | 2025.04.05 |
Python Intermediate_011: Recursive Functions in Python (0) | 2025.04.05 |