← Back to Python series
⚙️
Intermediate
class · __init__ · Methods · Properties · dataclass

Week 7 — OOP Part 1: Classes & Instances

Model real-world entities with classes. Learn to define attributes in __init__, write instance methods, use properties for validation, and simplify data classes with @dataclass.

OOPclassinstance__init__propertydataclass
Duration
3 hours
Level
📊 Intermediate
Prerequisite
🎯 Basic Week 9
OUTCOME
Model a bank account with deposit, withdraw, and balance validation

What you'll learn

  • 1Define a class with __init__ and instance attributes
  • 2Write instance methods that operate on self
  • 3Use @property for computed attributes and validation
  • 4Distinguish class attributes from instance attributes
  • 5Create simple data containers with @dataclass

1. Classes and Instances

python
class Dog:
    species = "Canis lupus familiaris"  # class attribute

    def __init__(self, name, age):
        self.name = name   # instance attribute
        self.age = age

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

    def __repr__(self):
        return f"Dog(name={self.name!r}, age={self.age})"

fido = Dog("Fido", 3)
print(fido.bark())
print(repr(fido))
print(Dog.species)

2. Properties

python
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance   # private by convention

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

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("Balance cannot be negative")
        self._balance = value

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Insufficient funds")
        self.balance -= amount

acct = BankAccount("Alice", 1000)
acct.deposit(500)
acct.withdraw(200)
print(acct.balance)  # 1300

3. @dataclass

python
from dataclasses import dataclass, field

@dataclass
class Point:
    x: float
    y: float

    def distance_to(self, other):
        return ((self.x-other.x)**2 + (self.y-other.y)**2)**0.5

p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance_to(p2))  # 5.0
print(p1)                  # Point(x=0, y=0)

4. Common Mistakes

  1. Forgetting self as the first parameter of instance methods.
  2. Using a mutable default (list/dict) in __init__ signature — use field(default_factory=list) with dataclass.
  3. Confusing class attributes (shared by all instances) with instance attributes (unique per instance).

💻 Examples

Run these examples and check the output yourself.

01_bank_account.pyBankAccount with full transaction history
CODE
class BankAccount:
    def __init__(self, owner):
        self.owner = owner
        self._balance = 0.0
        self._history = []

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

    def deposit(self, amount):
        if amount <= 0: raise ValueError("Amount must be positive")
        self._balance += amount
        self._history.append(f"+{amount:.2f}")

    def withdraw(self, amount):
        if amount > self._balance: raise ValueError("Insufficient funds")
        self._balance -= amount
        self._history.append(f"-{amount:.2f}")

    def statement(self):
        print(f"Account: {self.owner}")
        for tx in self._history:
            print(f"  {tx}")
        print(f"  Balance: {self._balance:.2f}")

acct = BankAccount("Alice")
acct.deposit(1000)
acct.deposit(500)
acct.withdraw(200)
acct.statement()
▶ Output
Account: Alice
  +1000.00
  +500.00
  -200.00
  Balance: 1300.00

📝 Exercises

Try them yourself first, then open the solution to compare.

Exercise 1

Student class

Goal: Model a student with name, scores list, and a computed grade property.

Requirements
  • __init__(name)
  • add_score(score) method
  • average property (float)
  • grade property: A/B/C/D/F based on average
  • __repr__ returning Student(name, avg=XX.X, grade=X)
Toggle solution
SOLUTION
class Student:
    def __init__(self, name):
        self.name = name
        self._scores = []

    def add_score(self, score):
        self._scores.append(score)

    @property
    def average(self):
        return sum(self._scores) / len(self._scores) if self._scores else 0.0

    @property
    def grade(self):
        a = self.average
        if a >= 90: return "A"
        if a >= 80: return "B"
        if a >= 70: return "C"
        if a >= 60: return "D"
        return "F"

    def __repr__(self):
        return f"Student({self.name!r}, avg={self.average:.1f}, grade={self.grade})"

s = Student("Alice")
for sc in [90, 85, 92]: s.add_score(sc)
print(s)
▶ Output
Student('Alice', avg=89.0, grade=B
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗