Giao diện
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
TypeVarvàGenericcho code tái sử dụng - Định nghĩa
Protocolcho 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 resultBà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
passBà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
passGợi ý
Gợi ý Bài 1
find_user(users: list[dict[str, Any]], user_id: int) -> Optional[dict[str, Any]]Optional[X]tương đươngX | 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 genericSequence[T]chấp nhận list, tuple, range — bất kỳ sequence nào- IDE sẽ hiểu
Stack[int]chỉ chấp nhậnint
Gợi ý Bài 3
- Protocol không cần kế thừa — structural typing (duck typing)
@runtime_checkablecho phép dùngisinstance(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