foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • quiz
Python
  • Understand encapsulation and data hiding
  • Use private attributes and methods
  • Create property getters and setters
  • Understand abstraction and abstract classes

Encapsulation and Abstraction

Imagine you're driving a car. You don't need to know how the engine works internally – you just use the steering wheel, pedals, and dashboard. The complex machinery is hidden from you, and you interact with it through a simple interface.

This is encapsulation and abstraction in action. They protect data integrity and hide complexity, making your code safer and easier to use.


Encapsulation: Protecting Data

Encapsulation bundles data and methods together, and controls access to that data:

class BankAccount:
    def __init__(self, owner, initial_balance):
        self.owner = owner
        self.__balance = initial_balance  # Private attribute
        self.__account_number = self.__generate_account_number()
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return f"Deposited ${amount}"
        return "Invalid amount"
    
    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            return f"Withdrew ${amount}"
        return "Insufficient funds or invalid amount"
    
    def get_balance(self):
        return f"Balance: ${self.__balance}"
    
    def __generate_account_number(self):
        """Private method - internal implementation detail."""
        import random
        return f"ACC{random.randint(10000, 99999)}"

Accessing Private Attributes

account = BankAccount("Alice", 1000)

# Public access works
print(account.get_balance())  # "Balance: $1000"
print(account.owner)          # "Alice"

# Direct access to private attributes (not recommended)
print(account._BankAccount__balance)  # $1000 (name mangling)

# But this is better - use the public interface
account.deposit(500)
print(account.get_balance())  # "Balance: $1500"

Name Mangling

Python uses name mangling to make private attributes harder to access:

class Example:
    def __init__(self):
        self.__private = "secret"
        self._protected = "semi-private"
        self.public = "open"

obj = Example()

# Name mangling in action
print(obj.__dict__)
# {'_Example__private': 'secret', '_protected': 'semi-private', 'public': 'open'}

# Accessing mangled name (not recommended)
print(obj._Example__private)  # "secret"

Convention:

  • __attribute → Private (name mangled)
  • _attribute → Protected (convention only)
  • attribute → Public

Properties: Controlled Access

Properties allow you to control how attributes are accessed and modified:

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        """Getter for celsius."""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        """Setter for celsius with validation."""
        if value < -273.15:
            raise ValueError("Temperature cannot be below absolute zero")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """Computed property - no setter needed."""
        return (self._celsius * 9/5) + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        """Setter that converts Fahrenheit to Celsius."""
        self._celsius = (value - 32) * 5/9

Using Properties

temp = Temperature(20)

print(temp.celsius)      # 20 (getter)
print(temp.fahrenheit)   # 68.0 (computed)

temp.celsius = 25        # setter
print(temp.fahrenheit)   # 77.0

temp.fahrenheit = 86     # Fahrenheit setter
print(temp.celsius)      # 30.0 (converted back)

# Validation works
# temp.celsius = -300  # ValueError.

Abstraction: Hiding Complexity

Abstraction focuses on what an object does, not how it does it:

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    """Abstract base class for payment processing."""
    
    @abstractmethod
    def process_payment(self, amount, card_info):
        """Process a payment - must be implemented by subclasses."""
        pass
    
    @abstractmethod
    def refund_payment(self, transaction_id, amount):
        """Refund a payment - must be implemented by subclasses."""
        pass
    
    def log_transaction(self, transaction_id, amount):
        """Concrete method - shared implementation."""
        print(f"Transaction {transaction_id}: ${amount}")

class StripeProcessor(PaymentProcessor):
    def process_payment(self, amount, card_info):
        # Complex Stripe API logic here...
        print(f"Processing ${amount} via Stripe")
        return "stripe_txn_123"
    
    def refund_payment(self, transaction_id, amount):
        # Complex refund logic...
        print(f"Refunding ${amount} for {transaction_id}")
        return True

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount, card_info):
        # Complex PayPal API logic...
        print(f"Processing ${amount} via PayPal")
        return "paypal_txn_456"
    
    def refund_payment(self, transaction_id, amount):
        # Complex refund logic...
        print(f"Refunding ${amount} for {transaction_id}")
        return True

Using Abstraction

# Client code doesn't need to know implementation details
def checkout(processor, amount, card_info):
    transaction_id = processor.process_payment(amount, card_info)
    processor.log_transaction(transaction_id, amount)
    return transaction_id

# Works with any payment processor
stripe = StripeProcessor()
paypal = PayPalProcessor()

checkout(stripe, 99.99, "card_info")   # Uses Stripe
checkout(paypal, 49.99, "card_info")   # Uses PayPal

Email System Example

from abc import ABC, abstractmethod

class EmailService(ABC):
    """Abstract email service."""
    
    @abstractmethod
    def send_email(self, to, subject, body):
        pass
    
    def validate_email(self, email):
        """Concrete validation method."""
        return "@" in email and "." in email

class SMTPEmailService(EmailService):
    def __init__(self, smtp_server, username, password):
        self.__smtp_server = smtp_server  # Private
        self.__username = username
        self.__password = password
    
    def send_email(self, to, subject, body):
        if not self.validate_email(to):
            raise ValueError("Invalid email address")
        # SMTP logic here...
        print(f"Sent email to {to} via SMTP")

class MockEmailService(EmailService):
    def __init__(self):
        self.__sent_emails = []  # Private storage
    
    def send_email(self, to, subject, body):
        if not self.validate_email(to):
            raise ValueError("Invalid email address")
        # Store for testing
        self.__sent_emails.append({"to": to, "subject": subject, "body": body})
        print(f"Mock email sent to {to}")
    
    def get_sent_emails(self):
        """Getter for testing."""
        return self.__sent_emails.copy()

# Usage
def send_welcome_email(service, user_email):
    """High-level function - doesn't know implementation."""
    service.send_email(
        user_email,
        "Welcome!",
        "Thanks for joining our platform."
    )

# Production
smtp_service = SMTPEmailService("smtp.gmail.com", "user", "pass")
send_welcome_email(smtp_service, "user@example.com")

# Testing
mock_service = MockEmailService()
send_welcome_email(mock_service, "test@example.com")
print("Emails sent in test:", len(mock_service.get_sent_emails()))

Benefits of Encapsulation and Abstraction

1. Data Protection

class UserAccount:
    def __init__(self, username, password):
        self.username = username
        self.__password_hash = self.__hash_password(password)
    
    def __hash_password(self, password):
        """Private method for password hashing."""
        # Complex hashing logic...
        return f"hashed_{password}"
    
    def check_password(self, password):
        """Public interface for password checking."""
        return self.__password_hash == f"hashed_{password}"

user = UserAccount("alice", "secret123")
print(user.check_password("secret123"))  # True
# user.__password_hash is protected from direct access

2. Interface Stability

class DatabaseConnection:
    def __init__(self, connection_string):
        self.__connection = self.__create_connection(connection_string)
    
    def query(self, sql):
        """Public interface - implementation can change."""
        return self.__execute_query(sql)
    
    def __create_connection(self, conn_str):
        """Private - can change without breaking client code."""
        # Implementation details...
        return f"connection_to_{conn_str}"
    
    def __execute_query(self, sql):
        """Private - can change without breaking client code."""
        # Implementation details...
        return f"results_for_{sql}"

# Client code only depends on the public query() method
db = DatabaseConnection("postgresql://...")
results = db.query("SELECT * FROM users")

Key Points to Remember

  • Encapsulation = Bundle data and methods together, control access using private attributes (__attribute)
  • Properties = Controlled attribute access using @property and @attribute.setter
  • Abstraction = Hide complexity, show interface using abstract base classes with @abstractmethod
  • Benefits = Data protection, maintainability, flexibility
  • Convention: _protected, __private, public

You've learned the core principles of OOP. Python has some special methods that make objects behave like built-in types. In the next lesson, we'll explore special methods and properties – the magic behind len(), +, and more.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service