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. -
selfKeyword: 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" │
└─────────────────────────┘ └─────────────────────────┘
🌱 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! 🐾
🏛️ 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'.
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)
🌱 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
🏛️ 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
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
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}")
Step 3: Run & Verify Execution
Run the script using your terminal:
uv run domain_models.py
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
Top comments (0)