Python Series track

Week 01 · Advanced Track

Week 1 — Type Hints & Dataclasses

Add static type information to Python code with type hints, use TypeVar for generic functions, model data with @dataclass, and define structural subtypes with Protocol.

Advanced Track2 min readFree lesson

Level: Advanced | Prerequisite: Intermediate Weeks 7-8 (OOP)

Learning Goals

  • Learn the syntax of type hints (int, str, list[int], Optional)
  • Perform static type checking with mypy
  • Write data classes concisely with @dataclass
  • Understand the pitfalls of field(default_factory=list)

1. Basics of Type Hints

No impact at runtime, but greatly helps IDE autocompletion, static analysis, and documentation.

def greet(name: str, age: int = 20) -> str:
    return f"{name}({age})"

count: int = 0
items: list[str] = []

2. typing Module

from typing import Optional, Union, Any, Callable

def find_user(uid: int) -> Optional[dict]:
    """Optional[X] = X | None"""
    ...

def parse(value: Union[str, int]) -> int:
    """str or int (in Python 3.10+ str | int)"""
    ...

handler: Callable[[int], str] = lambda x: str(x)

From Python 3.10+:

def find_user(uid: int) -> dict | None: ...
def parse(value: str | int) -> int: ...

3. Container Types

nums: list[int] = [1, 2, 3]
scores: dict[str, int] = {"Alice": 90}
pair: tuple[int, str] = (1, "a")
unique: set[int] = {1, 2, 3}

4. mypy

pip install mypy
mypy main.py
mypy --strict main.py    #Strict mode

5. dataclass

Automatically generate __init__, __repr__, __eq__.

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

p = Point(1, 2)
print(p)              #Point(x=1, y=2) — __repr__ automatic
print(p == Point(1, 2))   #True — __eq__ automatic

6. Dataclass Options

from dataclasses import dataclass, field

@dataclass
class Book:
    title: str
    author: str
    tags: list[str] = field(default_factory=list)   #Variable default value
    price: float = 0.0

b = Book("Python", "Guido")
b.tags.append("language")

⚠️ tags: list[str] = [] is shared by all instances — never use it. Always use field(default_factory=list).

7. frozen dataclass

Immutable object. Hashable → can be used as set/dict keys.

@dataclass(frozen=True)
class Point:
    x: int
    y: int

p = Point(1, 2)
# p.x = 99   # FrozenInstanceError
{Point(1, 2)}   # OK

Key Examples (examples/)

File Content
01_type_hints.py Default type hint
02_typing_module.py Optional, Union, Callable
03_dataclass.py Dataclass default
04_dataclass_advanced.py field, frozen

⚠️ Common Mistakes

  1. Leave mutable default as is — use field(default_factory=list)
  2. Misinterpretation of Optional meaning — Optional[int] = int | None. Not simply "optional argument"
  3. Misunderstanding that type hints are validated at runtime — they're just hints. Runtime validation requires separate libraries like pydantic

❓ FAQ

Q1. Do I have to add all type hints? A. Public APIs (function signatures) are highly recommended. Internal variables can be inferred sufficiently.

Q2. What's the difference between dataclass and pydantic.BaseModel? A. dataclass is standard, pydantic is strong for runtime validation and serialization. If there's a lot of data processing, use pydantic.

Q3. What happens if I use Any? A. mypy gives up checking. If possible, use a more specific type.

📝 Exercises (exercises/)

  • exercise1.md — adds type hints to intermediate code
  • exercise2.md — Book/Member/Loan dataclass
  • exercise3.md — 5 Optional/list/dict signatures

Next Week

Week 2. Decorators and Context Managers

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

examples/01_type_hints.pyPython
def greet(name: str, age: int = 20) -> str:
    return f"{name}({age})"

def add(a: int, b: int) -> int:
    return a + b

count: int = 0
items: list[str] = []
scores: dict[str, int] = {}

print(greet("Alice"))
print(add(3, 5))
examples/02_typing_module.pyPython
from typing import Optional, Union, Any, Callable

def find_user(uid: int) -> Optional[dict]:
    if uid == 1:
        return {"name": "Alice"}
    return None

def parse_int(value: Union[str, int]) -> int:
    return int(value)

#Python 3.10+ new syntax
def find_user2(uid: int) -> dict | None:
    if uid == 1:
        return {"name": "Alice"}
    return None

def apply(fn: Callable[[int], int], x: int) -> int:
    return fn(x)

print(find_user(1))
print(find_user(99))
print(parse_int("42"))
print(apply(lambda n: n * 2, 5))
examples/03_dataclass.pyPython
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

p = Point(1, 2)
print(p)
print(p == Point(1, 2))
print(p == Point(1, 3))

@dataclass
class User:
    name: str
    age: int = 20   #Default value

u = User("Alice")
print(u)
examples/04_dataclass_advanced.pyPython
from dataclasses import dataclass, field, asdict

@dataclass
class Book:
    title: str
    author: str
    tags: list[str] = field(default_factory=list)
    price: float = 0.0

b = Book("Python", "Guido")
b.tags.append("language")
print(b)
print(asdict(b))   #Convert to dictionary

#frozen — immutable
@dataclass(frozen=True)
class Point:
    x: int
    y: int

p = Point(1, 2)
print({p, Point(1, 2), Point(3, 4)})   #Goes into set

try:
    p.x = 99
except Exception as e:
    print("frozen error:", e)

Assignment

Goal

Add type hints to the Intermediate Week 7 BankAccount class or the Week 10 flashcard code.

Requirements

  • Type hints on all method signatures
  • Pass mypy --strict yourfile.py

Installation

pip install mypy

Assignment 2. Book / Member / Loan dataclass

Requirements

  • Write the three core models of the library system in @dataclass
@dataclass
class Book: ...     # title, author, isbn, is_borrowed=False
@dataclass
class Member: ...   # member_id, name, borrowed: list[str]
@dataclass
class Loan: ...     # isbn, member_id, borrowed_date, due_date
  • Loan.is_overdue() methods (today > due_date)

Assignment 3. Optional/list/dict signatures

Goal

Write the signatures of the following functions with type hints (implementation is optional).

  1. find_book(isbn: str) -> Optional[Book]
  2. list_books(tag: Optional[str] = None) -> list[Book]
  3. borrow_count_by_member() -> dict[str, int]
  4. register(member: Member, books: list[Book]) -> bool
  5. apply_filter(items: list[Book], pred: Callable[[Book], bool]) -> list[Book]

Each function should have a docstring + clear type hints.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

solution1.pyPython
"""Add type hints to BankAccount in intermediate week 7"""

class BankAccount:
    def __init__(self, owner: str, balance: float = 0) -> None:
        self.owner: str = owner
        self.balance: float = balance

    def deposit(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Deposit is positive")
        self.balance += amount

    def withdraw(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Withdrawal is positive")
        if amount > self.balance:
            raise ValueError(f"Insufficient balance: {self.balance} < {amount}")
        self.balance -= amount

    def transfer(self, other: "BankAccount", amount: float) -> None:
        self.withdraw(amount)
        other.deposit(amount)

a = BankAccount("Alice", 1000)
b = BankAccount("Bob")
a.transfer(b, 300)
print(a.balance, b.balance)
solution2.pyPython
from dataclasses import dataclass, field
from datetime import date

@dataclass
class Book:
    title: str
    author: str
    isbn: str
    is_borrowed: bool = False

@dataclass
class Member:
    member_id: str
    name: str
    borrowed: list[str] = field(default_factory=list)

@dataclass
class Loan:
    isbn: str
    member_id: str
    borrowed_date: date
    due_date: date

    def is_overdue(self) -> bool:
        return date.today() > self.due_date

#Demo
b = Book("Python", "Guido", "978-1")
m = Member("M001", "Hong Gil-dong")
loan = Loan("978-1", "M001", date(2026, 5, 1), date(2026, 5, 15))
print(b)
print(m)
print(loan, "Overdue:", loan.is_overdue())
solution3.pyPython
from typing import Optional, Callable
from dataclasses import dataclass

@dataclass
class Book:
    title: str
    isbn: str
    tags: list[str]

@dataclass
class Member:
    member_id: str
    name: str

def find_book(isbn: str) -> Optional[Book]:
    """Find a book by ISBN. If not found, None"""
    ...

def list_books(tag: Optional[str] = None) -> list[Book]:
    """Book list filtered by tag. If None, all"""
    ...

def borrow_count_by_member() -> dict[str, int]:
    """Number of loans per member"""
    ...

def register(member: Member, books: list[Book]) -> bool:
    """Lend all books to a member. Success status"""
    ...

def apply_filter(items: list[Book], pred: Callable[[Book], bool]) -> list[Book]:
    """Filter with a condition function"""
    return [b for b in items if pred(b)]

#Demo
books = [Book("A", "1", ["py"]), Book("B", "2", ["js"])]
print(apply_filter(books, lambda b: "py" in b.tags))

Learn this lesson with a mentor

Work through the Python Series track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now