DEV Community

Thiruvengadam Sakthivel
Thiruvengadam Sakthivel

Posted on

#01 – Classes & Objects in Python

Welcome to Object-Oriented Programming (OOP) in Python! Up until now, we’ve handled data using individual variables, functions, and collections. OOP allows you to bundle related data (attributes) and behaviors (methods) together into clean, reusable custom types. 🧩


1. Classes, Objects, __init__, & self 🏛️

  • Class: The blueprint or template for creating objects (e.g., the architectural plan for a house).
  • Object (Instance): The concrete entity created in memory from that blueprint (e.g., the actual house built on a specific lot).
  • __init__ Method: A special method (constructor) that Python runs automatically whenever you instantiate a new object to set up its starting state.
  • self Keyword: A reference pointing directly to the current instance of the class, allowing methods to access and modify that specific object's data.
┌─────────────────────────────────────────────────────────────────┐
│                          CLASS BLUEPRINT                        │
│                     class User(name, email)                     │
└────────────────────────────────┬────────────────────────────────┘
                                 │
                   Instantiates (Creates Instances)
                                 │
            ┌────────────────────┴────────────────────┐
            ▼                                         ▼
┌─────────────────────────┐               ┌─────────────────────────┐
│    OBJECT INSTANCE #1   │               │    OBJECT INSTANCE #2   │
│  name: "Alice"          │               │  name: "Bob"            │
│  email: "alice@dev.com" │               │  email: "bob@dev.com"   │
└─────────────────────────┘               └─────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

🌱 Easy Starter Example

class Dog:
    # Constructor method initializing instance state
    def __init__(self, name: str, breed: str):
        self.name = name    # Store name on this specific instance
        self.breed = breed  # Store breed on this specific instance

    # Instance method
    def bark(self) -> None:
        print(f"{self.name} says: Woof! 🐾")

# Instantiating two distinct objects from the Dog class
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Luna", "Border Collie")

dog1.bark()  # Output: Buddy says: Woof! 🐾
dog2.bark()  # Output: Luna says: Woof! 🐾

Enter fullscreen mode Exit fullscreen mode

🏛️ Real-World Example: User Session Identity Manager

from datetime import datetime

class UserSession:
    def __init__(self, username: str, role: str = "member"):
        self.username = username
        self.role = role
        self.login_time = datetime.now()
        self.is_authenticated = True

    def logout(self) -> None:
        self.is_authenticated = False
        print(f"Session terminated for user '{self.username}'.")

    def get_status(self) -> str:
        status = "Active" if self.is_authenticated else "Logged Out"
        return f"[{self.username}] Role: {self.role} | Status: {status}"

# Usage
session_alex = UserSession("alex_dev", role="admin")
print(session_alex.get_status())  # Output: [alex_dev] Role: admin | Status: Active
session_alex.logout()             # Output: Session terminated for user 'alex_dev'.

Enter fullscreen mode Exit fullscreen mode

2. Instance vs. Class Attributes ⚙️

Attributes define the state of your data, but they live in two distinct scopes:

Attribute Type Where It Is Defined Scope & Behavior
Instance Attribute Inside __init__ using self.variable Unique to each individual object. Modifying it on one instance does not affect others.
Class Attribute Directly inside the class body, outside methods Shared across all instances of the class. Useful for constants or global defaults.
┌─────────────────────────────────────────────────────────────────┐
│                      CLASS: CommercialProduct                   │
│  default_tax_rate = 0.08  ◄─── (CLASS ATTRIBUTE: Shared by all) │
└────────────────────────────────┬────────────────────────────────┘
                                 │
             ┌───────────────────┴───────────────────┐
             ▼                                       ▼
┌─────────────────────────┐             ┌─────────────────────────┐
│   INSTANCE A (Item A)   │             │   INSTANCE B (Item B)   │
│  name: "Headphones"     │             │  name: "Keyboard"       │
│  base_price: 100.00     │             │  base_price: 150.00     │
└─────────────────────────┘             └─────────────────────────┘
 (INSTANCE ATTRIBUTES: Unique per instance)

Enter fullscreen mode Exit fullscreen mode

🌱 Easy Starter Example

class Employee:
    # Class Attribute (shared across all employee instances)
    company_name = "TechCorp Global"

    def __init__(self, name: str, salary: float):
        # Instance Attributes (unique to each employee)
        self.name = name
        self.salary = salary

emp1 = Employee("Alice", 85000)
emp2 = Employee("Bob", 92000)

print(emp1.company_name)  # Output: TechCorp Global
print(emp2.company_name)  # Output: TechCorp Global

# Changing the class attribute updates it for ALL instances
Employee.company_name = "TechCorp AI Solutions"
print(emp1.company_name)  # Output: TechCorp AI Solutions

Enter fullscreen mode Exit fullscreen mode

🏛️ Real-World Example: Product Pricing Engine with Tax Rates

class CommercialProduct:
    # Class attribute: Standard VAT/Sales tax applied across all product lines
    default_tax_rate: float = 0.08  # 8% tax

    def __init__(self, product_id: str, name: str, base_price: float):
        self.product_id = product_id
        self.name = name
        self.base_price = base_price

    def calculate_final_price(self) -> float:
        """Computes price including the class-level tax rate."""
        final_price = self.base_price * (1.0 + CommercialProduct.default_tax_rate)
        return round(final_price, 2)

item_a = CommercialProduct("SKU-101", "Wireless Headphones", 100.00)
print(f"Price for {item_a.name}: ${item_a.calculate_final_price()}")  # Output: $108.0

# Adjust regional tax rate globally across all products
CommercialProduct.default_tax_rate = 0.10  # Updated to 10%
print(f"Updated price for {item_a.name}: ${item_a.calculate_final_price()}")  # Output: $110.0

Enter fullscreen mode Exit fullscreen mode

3. Practice Challenge: Building User, Product, & BankAccount Classes 🏋️

Here is a clean, modular Python script containing three core domain classes: User, Product, and BankAccount.

Step 1: Initialize Your Workspace

Inside your terminal workspace:

uv init oop_day1 && cd oop_day1
touch domain_models.py

Enter fullscreen mode Exit fullscreen mode

Step 2: Implement domain_models.py

Open domain_models.py and add the three complete class implementations:

# domain_models.py
from datetime import datetime


# ==========================================
# 1. USER CLASS
# ==========================================
class User:
    """Represents an application user profile."""

    def __init__(self, username: str, email: str, role: str = "member"):
        self.username = username
        self.email = email
        self.role = role
        self.is_active: bool = True
        self.created_at: datetime = datetime.now()

    def deactivate(self) -> None:
        """Deactivates the user account."""
        self.is_active = False
        print(f"User '{self.username}' account has been deactivated.")

    def activate(self) -> None:
        """Activates the user account."""
        self.is_active = True
        print(f"User '{self.username}' account is now active.")

    def get_summary(self) -> str:
        status = "Active" if self.is_active else "Inactive"
        return f"User: {self.username} | Email: {self.email} | Role: {self.role} | Status: {status}"


# ==========================================
# 2. PRODUCT CLASS
# ==========================================
class Product:
    """Represents an e-commerce product catalog item."""

    # Class attribute: Shared tax rate across all inventory items
    category_tax_rate: float = 0.07  # 7% standard tax

    def __init__(self, product_id: str, name: str, price: float, stock_quantity: int = 0):
        self.product_id = product_id
        self.name = name
        self.price = price
        self.stock_quantity = stock_quantity

    def apply_discount(self, percentage: float) -> None:
        """Reduces the base product price by a percentage (e.g., 15 for 15%)."""
        if 0 < percentage < 100:
            discount_amount = self.price * (percentage / 100)
            self.price -= discount_amount
            print(f"Applied {percentage}% discount to {self.name}. New Price: ${self.price:.2f}")
        else:
            print("Invalid discount percentage specified.")

    def restock(self, amount: int) -> None:
        """Increases the inventory count."""
        if amount > 0:
            self.stock_quantity += amount
            print(f"Restocked {amount} units of '{self.name}'. Total Stock: {self.stock_quantity}")

    def get_total_price_with_tax(self) -> float:
        """Calculates total unit price including tax."""
        total = self.price * (1.0 + Product.category_tax_rate)
        return round(total, 2)


# ==========================================
# 3. BANK ACCOUNT CLASS
# ==========================================
class BankAccount:
    """Simulates a financial bank account with transaction guards."""

    # Class attribute: Track total accounts created system-wide
    total_accounts_opened: int = 0

    def __init__(self, account_number: str, owner_name: str, initial_balance: float = 0.0):
        self.account_number = account_number
        self.owner_name = owner_name
        self.balance = initial_balance

        # Increment global account counter when an instance is created
        BankAccount.total_accounts_opened += 1

    def deposit(self, amount: float) -> float:
        """Deposits funds into the account balance."""
        if amount <= 0:
            print("Deposit amount must be greater than $0.00.")
            return self.balance

        self.balance += amount
        print(f"[+] Deposited ${amount:.2f} into account {self.account_number}. Current Balance: ${self.balance:.2f}")
        return self.balance

    def withdraw(self, amount: float) -> float:
        """Withdraws funds with overdraft protection checks."""
        if amount <= 0:
            print("Withdrawal amount must be greater than $0.00.")
            return self.balance

        if amount > self.balance:
            print(f"[!] Transaction Declined: Insufficient funds on account {self.account_number}. Available: ${self.balance:.2f}")
            return self.balance

        self.balance -= amount
        print(f"[-] Withdrew ${amount:.2f} from account {self.account_number}. Remaining Balance: ${self.balance:.2f}")
        return self.balance

    def get_statement(self) -> str:
        return f"Account #{self.account_number} ({self.owner_name}) - Current Balance: ${self.balance:.2f}"


# ==========================================
# TEST EXECUTION SUITE
# ==========================================
if __name__ == "__main__":
    print("--- 1. Testing User Class ---")
    user1 = User("sara_m", "sara@company.org", role="Admin")
    print(user1.get_summary())
    user1.deactivate()
    print(user1.get_summary())

    print("\n--- 2. Testing Product Class ---")
    laptop = Product("SKU-902", "Developer Laptop", price=1200.00, stock_quantity=10)
    print(f"Base Price: ${laptop.price:.2f} | Total with Tax: ${laptop.get_total_price_with_tax():.2f}")
    laptop.apply_discount(10)  # 10% off
    laptop.restock(5)

    print("\n--- 3. Testing BankAccount Class ---")
    acc1 = BankAccount("ACC-001", "Sara Miller", initial_balance=500.00)
    acc2 = BankAccount("ACC-002", "John Doe", initial_balance=100.00)

    acc1.deposit(250.00)
    acc1.withdraw(100.00)
    acc1.withdraw(1000.00)  # Should trigger overdraft warning

    print(f"\nTotal Bank Accounts Active: {BankAccount.total_accounts_opened}")

Enter fullscreen mode Exit fullscreen mode

Step 3: Run & Verify Execution

Run the script using your terminal:

uv run domain_models.py

Enter fullscreen mode Exit fullscreen mode

Output Summary

--- 1. Testing User Class ---
User: sara_m | Email: sara@company.org | Role: Admin | Status: Active
User 'sara_m' account has been deactivated.
User: sara_m | Email: sara@company.org | Role: Admin | Status: Inactive

--- 2. Testing Product Class ---
Base Price: $1200.00 | Total with Tax: $1284.00
Applied 10.0% discount to Developer Laptop. New Price: $1080.00
Restocked 5 units of 'Developer Laptop'. Total Stock: 15

--- 3. Testing BankAccount Class ---
[+] Deposited $250.00 into account ACC-001. Current Balance: $750.00
[-] Withdrew $100.00 from account ACC-001. Remaining Balance: $650.00
[!] Transaction Declined: Insufficient funds on account ACC-001. Available: $650.00

Total Bank Accounts Active: 2

Enter fullscreen mode Exit fullscreen mode

Top comments (0)