JSTAcademy
0 XP
Dashboard
Crash Courses
Classes & Object-Oriented Programming
16 min
PhD+185 XP
Crash Courses · PhD

Classes & Object-Oriented Programming

Model real-world systems using classes, inheritance, __dunder__ methods, and dataclasses.
16 min read+185 XP on completionCert: Python Crash Course
Tap any word in the text below to start reading from there.

Classes & Object-Oriented Programming

Defining a Class

class Product:
    """A product in an inventory system."""

    # Class variable — shared across ALL instances
    currency = "USD"
    product_count = 0

    def __init__(self, name, price, stock=0):
        """Initialize a Product instance."""
        # Instance variables — unique to each object
        self.name = name
        self.price = price
        self.stock = stock
        Product.product_count += 1

    def __str__(self):
        """Human-readable string — used by print()."""
        return f"{self.name} (${self.price:.2f})"

    def __repr__(self):
        """Unambiguous string — used in debugging/REPL."""
        return f"Product(name={self.name!r}, price={self.price}, stock={self.stock})"

    def apply_discount(self, percent):
        """Return a new price after applying discount."""
        if not 0 < percent < 100:
            raise ValueError(f"Discount must be between 0 and 100, got {percent}")
        return self.price * (1 - percent / 100)

    def restock(self, quantity):
        if quantity <= 0:
            raise ValueError("Restock quantity must be positive")
        self.stock += quantity
        return self.stock


laptop = Product("MacBook Pro", 2499.00, stock=5)
keyboard = Product("Mechanical Keyboard", 149.99)

print(laptop)               # MacBook Pro ($2499.00)
print(repr(laptop))         # Product(name='MacBook Pro', price=2499.0, stock=5)
print(laptop.apply_discount(10))   # 2249.1
print(Product.product_count)       # 2

---

@property — Computed Attributes

class Circle:
    def __init__(self, radius):
        if radius <= 0:
            raise ValueError("Radius must be positive")
        self._radius = radius   # _ prefix = "private by convention"

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("Radius must be positive")
        self._radius = value

    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

    @property
    def circumference(self):
        import math
        return 2 * math.pi * self._radius


c = Circle(5)
print(c.area)           # 78.53981...
print(c.circumference)  # 31.41592...
c.radius = 10           # uses the setter
c.radius = -1           # ValueError!

---

Inheritance

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

    def describe(self):
        return f"{self.name} is a {self.species}"

    def speak(self):
        raise NotImplementedError("Subclasses must implement speak()")


class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name, species="Canis lupus familiaris")
        self.breed = breed

    def speak(self):
        return f"{self.name} says: Woof!"

    def fetch(self, item="ball"):
        return f"{self.name} fetches the {item}!"


class Cat(Animal):
    def speak(self):
        return f"{self.name} says: Meow."


dog = Dog("Rex", "German Shepherd")
cat = Cat("Luna", "Felis catus")

print(dog.describe())    # Rex is a Canis lupus familiaris
print(dog.speak())       # Rex says: Woof!
print(dog.fetch("stick"))
print(cat.speak())       # Luna says: Meow.

# isinstance — checks class hierarchy
print(isinstance(dog, Animal))   # True
print(isinstance(dog, Dog))      # True
print(isinstance(dog, Cat))      # False

---

dataclasses — Less Boilerplate

from dataclasses import dataclass, field
from typing import List

@dataclass
class Transaction:
    amount: float
    description: str
    transaction_type: str   # "credit" or "debit"

    def __post_init__(self):
        if self.amount <= 0:
            raise ValueError("Transaction amount must be positive")
        if self.transaction_type not in ("credit", "debit"):
            raise ValueError(f"Invalid type: {self.transaction_type}")

@dataclass
class Account:
    owner: str
    account_number: str
    _balance: float = field(default=0.0, repr=False)
    _transactions: List[Transaction] = field(default_factory=list, repr=False)

    @property
    def balance(self):
        return self._balance

    def deposit(self, amount, description="Deposit"):
        t = Transaction(amount, description, "credit")
        self._balance += amount
        self._transactions.append(t)
        return self._balance

    def withdraw(self, amount, description="Withdrawal"):
        if amount > self._balance:
            raise ValueError(f"Insufficient funds (balance: {self._balance:.2f})")
        t = Transaction(amount, description, "debit")
        self._balance -= amount
        self._transactions.append(t)
        return self._balance

    def history(self):
        for t in self._transactions:
            sign = "+" if t.transaction_type == "credit" else "-"
            print(f"  {sign}${t.amount:.2f}  {t.description}")

acc = Account("Jordan", "ACC-001")
acc.deposit(1000, "Initial deposit")
acc.deposit(500, "Client payment")
acc.withdraw(200, "Rent")
print(f"Balance: ${acc.balance:.2f}")
acc.history()
0%