Skip to content

Thực hành: Type Hints & Protocols

🎯 Mục tiêu

🎯 Sau bài thực hành này, bạn sẽ:

  • Thêm type hints chính xác cho functions và classes
  • Sử dụng TypeVarGeneric cho code tái sử dụng
  • Định nghĩa Protocol cho structural typing (duck typing có kiểu)

Bài 1: Type Hints Cơ Bản

Thêm type hints đầy đủ cho các hàm sau.

python
from typing import Optional, Union
# TODO: Thêm type hints cho tất cả parameters và return types
def find_user(users, user_id):
    """Tìm user theo ID, trả về None nếu không tìm thấy."""
    for user in users:
        if user["id"] == user_id:
            return user
    return None
def calculate_discount(price, discount_percent, max_discount=None):
    """Tính giá sau giảm, có giới hạn giảm tối đa."""
    discount = price * discount_percent / 100
    if max_discount is not None:
        discount = min(discount, max_discount)
    return price - discount
def merge_configs(*configs):
    """Gộp nhiều dict config, dict sau ghi đè dict trước."""
    result = {}
    for config in configs:
        result.update(config)
    return result

Bài 2: TypeVar và Generic

Viết hàm generic hoạt động với nhiều kiểu dữ liệu.

python
from typing import TypeVar, Generic, Sequence
T = TypeVar("T")
class Stack(Generic[T]):
    """Stack generic — hoạt động với bất kỳ kiểu nào."""
    def __init__(self) -> None:
        self._items: list[T] = []
    def push(self, item: T) -> None:
        # TODO: Implement
        pass
    def pop(self) -> T:
        # TODO: Implement, raise IndexError nếu rỗng
        pass
    def peek(self) -> T:
        # TODO: Trả về phần tử trên cùng mà không xóa
        pass
    @property
    def is_empty(self) -> bool:
        return len(self._items) == 0
def first_or_default(items: Sequence[T], default: T) -> T:
    # TODO: Trả về phần tử đầu tiên hoặc default
    pass

Bài 3: Protocol — Structural Typing

Định nghĩa Protocol cho duck typing có type safety.

python
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
    """Bất kỳ object nào có method draw() đều là Drawable."""
    def draw(self, x: int, y: int) -> None: ...
class Renderable(Protocol):
    """Protocol cho objects có thể render thành string."""
    def render(self) -> str: ...
    @property
    def width(self) -> int: ...
# TODO: Tạo class Circle và Text implement các Protocol trên
# Không cần kế thừa — chỉ cần implement đúng methods
def render_all(items: list[Renderable]) -> str:
    """Render tất cả items thành một string."""
    # TODO: Implement
    pass

Gợi ý

Gợi ý Bài 1
  • find_user(users: list[dict[str, Any]], user_id: int) -> Optional[dict[str, Any]]
  • Optional[X] tương đương X | None (Python 3.10+)
  • *configs: dict[str, Any] cho variadic arguments
Gợi ý Bài 2
  • TypeVar("T") tạo biến kiểu, Generic[T] cho class generic
  • Sequence[T] chấp nhận list, tuple, range — bất kỳ sequence nào
  • IDE sẽ hiểu Stack[int] chỉ chấp nhận int
Gợi ý Bài 3
  • Protocol không cần kế thừa — structural typing (duck typing)
  • @runtime_checkable cho phép dùng isinstance(obj, Protocol)
  • Class chỉ cần có đúng methods và signature là thoả mãn Protocol

Lời giải tham khảo

Xem lời giải
python
from typing import TypeVar, Generic, Optional, Any
T = TypeVar("T")
def find_user(users: list[dict[str, Any]], user_id: int) -> Optional[dict[str, Any]]:
    for user in users:
        if user["id"] == user_id:
            return user
    return None
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []
    def push(self, item: T) -> None:
        self._items.append(item)
    def pop(self) -> T:
        if self.is_empty:
            raise IndexError("Stack rỗng")
        return self._items.pop()
    def peek(self) -> T:
        if self.is_empty:
            raise IndexError("Stack rỗng")
        return self._items[-1]
    @property
    def is_empty(self) -> bool:
        return len(self._items) == 0