Ojasa Mirai

Ojasa Mirai

Python

Loading...

Learning Level

🟢 Beginner🔵 Advanced
Classes & ObjectsMethods & SelfInstance VariablesClass VariablesConstructors & InitializationInheritance BasicsPolymorphismEncapsulationMagic Methods & Dunder
Python/Oop/Encapsulation

🔐 Encapsulation — Protecting Object Data

Encapsulation is the practice of bundling data and methods together while hiding internal details. It protects an object's data from direct external access and provides controlled access through methods. This improves security, maintainability, and allows you to change internal implementation without affecting external code.


🎯 What is Encapsulation?

Encapsulation combines data and methods into a single unit (a class) and hides internal details from the outside. The object exposes a public interface (methods) while keeping internal state (attributes) private. This protects data integrity and reduces coupling between objects.

class BankAccount:
    def __init__(self, owner, balance):
        self._balance = balance  # Private: prefix with underscore
        self._owner = owner

    # Public method to access private data safely
    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
            return True
        return False

    def withdraw(self, amount):
        if amount > 0 and amount <= self._balance:
            self._balance -= amount
            return True
        return False

    def get_balance(self):
        return self._balance

# Use public interface
account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.get_balance())  # Output: 1500

# Don't directly modify private data
# account._balance = -1000  # Bad practice, but Python allows it

🔒 Private Attributes

Private attributes are prefixed with an underscore (`_`). While Python doesn't enforce privacy (you can still access them), the underscore is a convention signaling "don't use this directly." This encourages using public methods instead.

class Student:
    def __init__(self, name, grade):
        self._name = name      # Private - don't access directly
        self._grade = grade    # Private - don't access directly
        self._gpa = 4.0        # Private - use getter
        self._courses = []     # Private - use methods

    # Public methods (getters)
    def get_name(self):
        return self._name

    def get_grade(self):
        return self._grade

    def get_gpa(self):
        return self._gpa

    # Public methods (setters with validation)
    def set_grade(self, grade):
        if grade in ['A', 'B', 'C', 'D', 'F']:
            self._grade = grade
            return True
        return False

    def add_course(self, course):
        self._courses.append(course)

    def get_courses(self):
        return self._courses.copy()  # Return copy to prevent external modification

student = Student("Alice", "A")
print(student.get_name())
print(student.get_grade())
student.set_grade("B")
student.add_course("Math")
print(student.get_courses())

🚪 Public Methods as Interface

Public methods form the interface that objects expose. They allow controlled access to private data and enforce business rules. Methods are the only way users should interact with object state.

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    # Getters - public read access
    def get_celsius(self):
        return self._celsius

    def get_fahrenheit(self):
        return (self._celsius * 9/5) + 32

    def get_kelvin(self):
        return self._celsius + 273.15

    # Setter - controlled write access with validation
    def set_celsius(self, value):
        if -273.15 <= value <= 1000:  # Validation
            self._celsius = value
            return True
        return False  # Invalid temperature

    def display(self):
        return f"Temperature: {self._celsius}°C ({self.get_fahrenheit()}°F)"

temp = Temperature(20)
print(temp.display())

# Set through validated method
if temp.set_celsius(25):
    print(temp.display())
else:
    print("Invalid temperature")

# Try invalid value
if temp.set_celsius(-300):
    print("Set succeeded")
else:
    print("Cannot set temperature below absolute zero")

🛡️ Protecting Data Integrity

Encapsulation protects data integrity by enforcing business rules. Methods ensure that data is only modified in valid ways.

class ShoppingCart:
    def __init__(self):
        self._items = []
        self._total_price = 0

    def add_item(self, name, price, quantity):
        # Validation before modification
        if price > 0 and quantity > 0:
            self._items.append({
                "name": name,
                "price": price,
                "quantity": quantity
            })
            self._total_price += price * quantity
            return True
        return False

    def remove_item(self, name):
        # Find and remove item
        for item in self._items:
            if item["name"] == name:
                self._total_price -= item["price"] * item["quantity"]
                self._items.remove(item)
                return True
        return False

    def get_total(self):
        return self._total_price

    def get_items(self):
        return self._items.copy()

    def clear_cart(self):
        self._items = []
        self._total_price = 0

cart = ShoppingCart()
cart.add_item("Book", 15.99, 2)
cart.add_item("Pen", 1.50, 5)
print(f"Total: ${cart.get_total():.2f}")

cart.remove_item("Pen")
print(f"After removal: ${cart.get_total():.2f}")

🔑 Using Properties (Advanced Access)

Python's `@property` decorator provides a Pythonic way to create getters and setters that look like attribute access but actually call methods.

class Person:
    def __init__(self, first_name, last_name):
        self._first_name = first_name
        self._last_name = last_name
        self._age = 0

    # Create a property (getter)
    @property
    def age(self):
        return self._age

    # Create a setter
    @age.setter
    def age(self, value):
        if 0 <= value <= 150:
            self._age = value
        else:
            raise ValueError("Age must be between 0 and 150")

    @property
    def full_name(self):
        return f"{self._first_name} {self._last_name}"

# Use like attributes but with validation
person = Person("John", "Doe")
person.age = 30  # Calls setter
print(f"{person.full_name} is {person.age}")

# Invalid assignment is prevented
try:
    person.age = 200  # Raises error
except ValueError as e:
    print(f"Error: {e}")

📚 Real-World Examples

Bank Account with Encapsulation

class SecureBankAccount:
    def __init__(self, owner, pin, balance=0):
        self._owner = owner
        self._pin = pin
        self._balance = balance
        self._transaction_history = []

    def _verify_pin(self, pin):
        return self._pin == pin

    def deposit(self, amount, pin):
        if not self._verify_pin(pin):
            return "Invalid PIN"
        if amount <= 0:
            return "Deposit must be positive"

        self._balance += amount
        self._transaction_history.append(f"+${amount}")
        return f"Deposited ${amount}. New balance: ${self._balance}"

    def withdraw(self, amount, pin):
        if not self._verify_pin(pin):
            return "Invalid PIN"
        if amount <= 0:
            return "Withdrawal must be positive"
        if amount > self._balance:
            return "Insufficient funds"

        self._balance -= amount
        self._transaction_history.append(f"-${amount}")
        return f"Withdrew ${amount}. New balance: ${self._balance}"

    def get_balance(self, pin):
        if not self._verify_pin(pin):
            return "Invalid PIN"
        return self._balance

    def get_history(self, pin):
        if not self._verify_pin(pin):
            return "Invalid PIN"
        return self._transaction_history

account = SecureBankAccount("Alice", "1234", 1000)
print(account.deposit(500, "1234"))
print(account.withdraw(200, "1234"))
print(account.get_balance("1234"))
print(account.withdraw(100, "0000"))  # Wrong PIN

Database Connection Manager

class DatabaseConnection:
    def __init__(self, host, port, database):
        self._host = host
        self._port = port
        self._database = database
        self._connected = False
        self._connection = None

    def connect(self):
        if not self._connected:
            self._connection = f"Connected to {self._host}:{self._port}/{self._database}"
            self._connected = True
            return "Connection established"
        return "Already connected"

    def disconnect(self):
        if self._connected:
            self._connection = None
            self._connected = False
            return "Connection closed"
        return "Not connected"

    def execute_query(self, query):
        if not self._connected:
            return "Error: Not connected"
        return f"Executing: {query}"

    def is_connected(self):
        return self._connected

    def get_connection_info(self):
        if self._connected:
            return f"Connected to {self._host}:{self._port}"
        return "Not connected"

db = DatabaseConnection("localhost", 5432, "myapp")
print(db.connect())
print(db.execute_query("SELECT * FROM users"))
print(db.is_connected())
print(db.disconnect())

✅ Key Takeaways

ConceptRemember
EncapsulationBundle data and methods, hide internal details
Private AttributePrefix with underscore (_attribute)
Public MethodProvide controlled access to private data
GetterMethod that returns private data safely
SetterMethod that validates before modifying data
Data IntegrityBusiness rules enforced through methods
InterfacePublic methods form the contract with users
@propertyPython decorator for property-like getters/setters

🔗 What's Next?

Now let's explore magic methods (dunder methods) that give your objects special abilities.

Next: Magic Methods & Dunder →


Ready to practice? Try challenges or explore more concepts


Resources

Python Docs

Ojasa Mirai

Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.

Learn Deep • Build Real • Verify Skills • Launch Forward

Courses

PythonFastapiReactJSCloud

© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn