Chuyện lúc 2 giờ sáng: Khi Type Hints trở thành phao cứu sinh
Điện thoại mình rung cháy máy vào lúc 2 giờ sáng. Hệ thống xử lý thanh toán báo lỗi 500 hàng loạt. Sau 15 phút rà soát log, mình phát hiện thủ phạm là một dòng code cực kỳ đơn giản. Hàm format chuỗi nhận về None từ database thay vì string, dẫn đến lỗi kinh điển: AttributeError: 'NoneType' object has no attribute 'lower'.
Nếu dự án dùng Type Hints và chạy mypy, lỗi này đã bị chặn đứng ngay từ bước commit. Trong các dự án Enterprise với hàng trăm nghìn dòng code, Type Hints không chỉ để làm cảnh. Nó là một dạng tài liệu sống, giúp đồng nghiệp hiểu ngay ý đồ của bạn mà không cần bơi trong mớ logic hỗn độn.
Nâng cấp code trong 1 nốt nhạc
Hãy xem cách biến một hàm Python “hên xui” thành một hàm có cam kết rõ ràng. Thay vì để Python tự đoán kiểu dữ liệu, chúng ta sẽ chỉ định cụ thể.
# Cách viết cũ: Dễ gây lỗi tiềm ẩn
def get_user_status(user_id):
return "Active" if user_id > 0 else None
# Chuẩn Type Hint (Python 3.10+): Rõ ràng và an toàn
def get_user_status(user_id: int) -> str | None:
if user_id <= 0:
return None
return "Active"
Với user_id: int và -> str | None, bạn đã thiết lập một “hợp đồng” cho hàm. IDE sẽ cảnh báo ngay nếu bạn truyền nhầm một list hoặc một dictionary vào đây.
Union và Optional: Khắc tinh của lỗi Logic
Biến số trong thực tế thường linh hoạt. Một ID có thể là số nguyên, nhưng đôi khi lại là UUID dạng chuỗi.
Sử dụng Union (Toán tử |)
Từ Python 3.10, bạn không cần import Union rườm rà nữa. Hãy dùng dấu gạch đứng | để code trông sạch sẽ hơn.
def calculate_discount(price: int | float) -> float:
return price * 0.9
Optional: Đối phó với dữ liệu thiếu
Dữ liệu từ CSV hoặc API thường xuyên bị trống. Optional[str] (cách viết khác của str | None) buộc bạn phải xử lý trường hợp None trước khi thao tác. Điều này cực kỳ quan trọng khi xử lý tập dữ liệu lớn, ví dụ như file log 500MB với hàng triệu dòng dữ liệu không đồng nhất.
from typing import Optional
def find_email(user_id: int) -> Optional[str]:
db_result = query_user(user_id)
return db_result.email if db_result else None
Callable: Kiểm soát các hàm Callback
Việc truyền function vào một function khác rất phổ biến trong các framework như FastAPI hay Flask. Tuy nhiên, nếu không định nghĩa rõ, bạn sẽ rất dễ quên số lượng tham số mà callback yêu cầu.
Cú pháp chuẩn: Callable[[Danh_sách_tham_số], Kiểu_trả_về].
from typing import Callable
def process_data(data: list[int], callback: Callable[[int], str]) -> list[str]:
return [callback(item) for item in data]
def convert_to_currency(value: int) -> str:
return f"${value}"
# IDE sẽ báo lỗi nếu convert_to_currency thiếu hoặc thừa tham số
result = process_data([10, 20, 30], convert_to_currency)
Generic: Viết code một lần, dùng cho mọi nơi
Generic là vũ khí tối thượng để tái sử dụng code mà vẫn đảm bảo Type Safety. Thay vì tạo UserRepository và OrderRepository riêng biệt, bạn có thể tạo một class dùng chung.
Trong một dự án kho bãi mình từng tham gia, Generic đã giúp giảm 30% lượng code lặp lại:
from typing import TypeVar, Generic, List
T = TypeVar('T')
class Storage(Generic[T]):
def __init__(self):
self._items: List[T] = []
def add(self, item: T) -> None:
self._items.append(item)
# Khởi tạo kho lưu trữ riêng cho chuỗi
user_storage = Storage[str]()
user_storage.add("Admin")
# user_storage.add(123) # Mypy sẽ chặn dòng này ngay lập tức
Kinh nghiệm thực chiến để không bị “ngợp”
Áp dụng Type Hints vào dự án thực tế cần có chiến thuật. Đừng cố gắng làm hoàn hảo ngay từ đầu.
- Nói không với
Any: Lạm dụngAnykhiến Type Hints trở nên vô nghĩa. Nếu phải dùngAny, hãy tự hỏi liệu thiết kế của bạn có đang quá rắc rối không. - Runtime check với Pydantic: Type Hints chỉ có tác dụng kiểm tra tĩnh (static). Để validate dữ liệu thực tế từ người dùng, hãy kết hợp với Pydantic.
- Tích hợp CI/CD: Hãy cài đặt
mypyvào pipeline. Code không qua được bước check type thì tuyệt đối không cho merge vào nhánh chính. - Chiến thuật “vết dầu loang”: Với dự án cũ, hãy bắt đầu thêm Type Hints cho các module core hoặc các hàm xử lý logic quan trọng nhất.
Đầu tư thêm 15% thời gian để viết Type Hints sẽ giúp bạn tiết kiệm hàng tuần trời debug về sau. Chúc bạn có những giấc ngủ ngon không bị gián đoạn bởi lỗi 500!

