AiTechWorlds
AiTechWorlds
A class is a blueprint that bundles data and behavior together; an object is a concrete thing built from that blueprint. It works like a cookie cutter and the cookies it produces — the cutter (class) defines the shape once, and every cookie (object) it stamps out has that shape but its own filling. This is how most production Python code is structured.
Without classes, related data and functions live separately and things get messy:
# Without classes — brittle, scattered
user_name = "Alice"
user_age = 30
user_email = "alice@example.com"
def greet_user(name):
return f"Hello, {name}!"
def get_user_info(name, age, email):
return f"{name}, {age}, {email}"
# With classes — organized, self-contained
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def greet(self):
return f"Hello, {self.name}!"
def get_info(self):
return f"{self.name}, {self.age}, {self.email}"
alice = User("Alice", 30, "alice@example.com")
print(alice.greet()) # Hello, Alice!
print(alice.get_info()) # Alice, 30, alice@example.comclass BankAccount:
# Class attribute: shared by ALL instances
bank_name = "Python Bank"
interest_rate = 0.02
# __init__: called when creating an instance
def __init__(self, owner, balance=0):
# Instance attributes: unique to each instance
self.owner = owner
self.balance = balance
self._transaction_history = [] # Convention: _ means "private"
# Instance method: operates on the instance via self
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.balance += amount
self._transaction_history.append(f"Deposit: +{amount}")
return self.balance
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > self.balance:
raise ValueError(f"Insufficient funds: balance is {self.balance}")
self.balance -= amount
self._transaction_history.append(f"Withdrawal: -{amount}")
return self.balance
def apply_interest(self):
interest = round(self.balance * self.interest_rate, 2)
self.balance += interest
self._transaction_history.append(f"Interest: +{interest}")
return interest
def get_statement(self):
lines = [f"Account: {self.owner}", f"Bank: {self.bank_name}"]
lines.extend(self._transaction_history)
lines.append(f"Balance: ${self.balance:.2f}")
return "\n".join(lines)
# __str__: what print() shows
def __str__(self):
return f"BankAccount(owner={self.owner!r}, balance={self.balance:.2f})"
# __repr__: unambiguous string representation (for debugging)
def __repr__(self):
return f"BankAccount(owner={self.owner!r}, balance={self.balance!r})"
# Usage
account = BankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(200)
account.apply_interest()
print(account.get_statement())
print(account) # Calls __str__
print(repr(account)) # Calls __repr__
# Accessing class attributes
print(BankAccount.bank_name) # Via class
print(account.bank_name) # Via instance (inherits from class)class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@property
def fahrenheit(self):
"""Property: accessed like an attribute, not a method call."""
return self.celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
@classmethod
def from_fahrenheit(cls, fahrenheit):
"""Alternative constructor — a class method creates instances."""
celsius = (fahrenheit - 32) * 5/9
return cls(celsius) # cls is the class itself
@classmethod
def from_kelvin(cls, kelvin):
return cls(kelvin - 273.15)
@staticmethod
def celsius_to_fahrenheit(celsius):
"""Static method: utility function that doesn't need instance or class."""
return celsius * 9/5 + 32
def __str__(self):
return f"{self.celsius:.1f}°C / {self.fahrenheit:.1f}°F"
# Usage
boiling = Temperature(100)
print(boiling) # 100.0°C / 212.0°F
# Alternative constructors
freezing = Temperature.from_fahrenheit(32)
print(freezing) # 0.0°C / 32.0°F
body_temp = Temperature.from_kelvin(310)
print(body_temp) # 36.9°C / 98.3°F
# Property setter
t = Temperature(20)
t.fahrenheit = 68 # Sets via property setter
print(t.celsius) # 20.0
# Static method — no instance needed
print(Temperature.celsius_to_fahrenheit(37)) # 98.6For classes that mainly hold data, @dataclass removes boilerplate:
from dataclasses import dataclass, field
from typing import List
@dataclass
class Student:
name: str
grade: int
scores: List[float] = field(default_factory=list)
@property
def average(self):
return sum(self.scores) / len(self.scores) if self.scores else 0
def add_score(self, score):
self.scores.append(score)
def __post_init__(self):
"""Called after __init__ — validate or transform data."""
if not 1 <= self.grade <= 12:
raise ValueError(f"Grade must be 1-12, got {self.grade}")
self.name = self.name.strip().title()
# Dataclasses automatically generate __init__, __repr__, __eq__
s = Student("alice smith", 10, [88, 92, 76])
print(s) # Student(name='Alice Smith', grade=10, scores=[88, 92, 76])
print(s.average) # 85.33
print(s == Student("Alice Smith", 10, [88, 92, 76])) # Truefrom dataclasses import dataclass, field
from typing import Dict, Optional
from datetime import datetime
@dataclass
class Product:
sku: str
name: str
price: float
category: str
def discounted_price(self, discount_pct):
return round(self.price * (1 - discount_pct / 100), 2)
def __str__(self):
return f"{self.name} (${self.price:.2f})"
class Inventory:
def __init__(self):
self._stock: Dict[str, int] = {}
self._products: Dict[str, Product] = {}
self._log = []
def add_product(self, product: Product, quantity: int):
self._products[product.sku] = product
self._stock[product.sku] = self._stock.get(product.sku, 0) + quantity
self._log.append(f"{datetime.now()}: Added {quantity}× {product.name}")
def sell(self, sku: str, quantity: int) -> float:
if sku not in self._products:
raise ValueError(f"Unknown SKU: {sku}")
if self._stock[sku] < quantity:
raise ValueError(f"Only {self._stock[sku]} in stock")
product = self._products[sku]
self._stock[sku] -= quantity
revenue = product.price * quantity
self._log.append(f"{datetime.now()}: Sold {quantity}× {product.name}")
return revenue
def low_stock(self, threshold=10) -> list:
return [
(self._products[sku], qty)
for sku, qty in self._stock.items()
if qty <= threshold
]
def total_value(self) -> float:
return sum(
self._products[sku].price * qty
for sku, qty in self._stock.items()
)
def __len__(self):
return sum(self._stock.values())
def __contains__(self, sku):
return sku in self._products
# Usage
inv = Inventory()
laptop = Product("LAP001", "MacBook Pro", 2499.99, "Electronics")
mouse = Product("MOU001", "Magic Mouse", 79.99, "Electronics")
inv.add_product(laptop, 5)
inv.add_product(mouse, 50)
revenue = inv.sell("LAP001", 2)
print(f"Revenue: ${revenue:.2f}")
print(f"Total inventory value: ${inv.total_value():,.2f}")
print(f"Total items in stock: {len(inv)}")
print(f"'LAP001' in inventory: {'LAP001' in inv}")Classes are the backbone of organized Python code. Once you understand them intuitively, you'll see that almost every library you use — pandas DataFrames, scikit-learn models, Django views — is built from classes.
Next lesson: Inheritance & Polymorphism — extending and reusing classes.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises