Bạn đã nắm vững các kiến thức cơ bản về class và function, nhưng Python thường cung cấp nhiều cách để giải quyết cùng một vấn đề. Tiến độ code của bạn sẽ chững lại khi bạn bắt đầu đắn đo suy nghĩ:
- Mình nên truyền dữ liệu này dưới dạng
dict(từ điển) haydataclass? - Chỗ này dùng List Comprehension (tạo list) hay Generator (trình phát sinh lazy)?
- Mình nên kiểm tra key có tồn tại hay không bằng lệnh
if, hay cứ dùngtry/except? - Hàm này nên được gắn
@staticmethod, hay chỉ là một hàm độc lập viết bên ngoài class? - Mình nên viết rõ cấu trúc từng tham số, hay cứ dùng gọn
**kwargs?
Bài viết này là Phần 2 của bộ hướng dẫn tư duy (checklist). Dưới đây là 5 quyết định thiết kế Python thường gặp mỗi ngày với các ví dụ cụ thể.
1) Dictionary vs dataclass
Dùng dict khi:
- Các keys (khóa) phụ thuộc vào thời điểm chạy (runtime) (ví dụ: gộp dữ liệu theo User ID động).
- Dữ liệu đơn giản, không có cấu trúc cố định và được luân chuyển nhanh trong hệ thống.
- Bạn cần serialize (chuyển đổi) xuống JSON ngay lập tức.
Dùng dataclass khi:
- Bạn biết chính xác các trường (fields) từ trước (schema cố định).
- Bạn muốn tránh lỗi gõ sai chữ (được IDE gợi ý và kiểm tra kiểu với
mypy). - Bạn dự định gắn các hành vi (methods) vào cục dữ liệu đó sau này.
Ví dụ: Cái bẫy “gõ sai” với dicts
| |
Ví dụ: Dataclass mang lại sự an toàn
| |
2) List Comprehension (Eager) vs Generator (Lazy)
Dùng List Comprehension [...] khi:
- Bạn cần biết số lượng kết quả ngay (
len()). - Bạn cần lặp qua danh sách dữ liệu đó nhiều lần.
- Tập dữ liệu đủ nhỏ để có thể nằm gọn trong RAM.
- Bạn cần sắp xếp (sort) hoặc cắt lát (slice) dữ liệu từ cuối lên.
Dùng Generator Expression (...) hoặc yield khi:
- Bạn phải xử lý tập dữ liệu khổng lồ (logs, file lớn, kết quả từ database).
- Bạn muốn nối chuỗi (chaining) nhiều bước xử lý với nhau (pipelines).
- Bạn có thể dừng vòng lặp sớm (ví dụ: bẻ khóa vòng lặp ngay khi tìm thấy kết quả đầu tiên).
Ví dụ: Đừng tải toàn bộ file vào bộ nhớ
| |
3) try/except (EAFP) vs if/else (LBYL)
Python luôn đề cao triết lý EAFP: “Dễ dàng xin tha thứ hơn là xin phép” (Easier to Ask for Forgiveness than Permission). Trong khi hầu hết các ngôn ngữ khác thích LBYL: “Nhìn kỹ trước khi nhảy” (Look Before You Leap).
Dùng if/else (LBYL) khi:
- Trường hợp bị lỗi/hỏng xảy ra thường xuyên (ví dụ: chiếm 30%). Catch Exception sẽ rất chậm đổi với các lỗi diễn ra liên tục.
- Việc kiểm tra điều kiện (check) tiêu tốn ít chi phí tính toán.
Dùng try/except (EAFP) khi:
- “Con đường hạnh phúc” (Happy path) chiếm tới 99% thời gian chạy.
- Bạn muốn tránh Race Conditions (ví dụ: một file bị luồng khác xóa mất sau khi bạn đã dùng if kiểm tra nó tồn tại, nhưng xảy ra trước khi bạn kịp mở nó).
- Code nhìn gọn gàng và phẳng hơn, không bị lồng quá nhiều cấp độ
if/else.
Ví dụ: Tránh Race Conditions với EAFP
| |
4) @staticmethod vs Hàm cấp Module (Module-Level Function)
Dùng module-level function khi:
- Hàm không cần tới
self(biến thực thể) haycls(biến lớp). Trong Python, bạn không cần phải nhồi nhét mọi thứ vào trong class giống như Java hay C#.
Dùng @staticmethod khi (Rất hiếm):
- Về mặt logic, hàm chắc chắn và khắt khe thuộc về không gian tên (namespace) của class đó, và nếu mang nó ra ngoài sẽ khiến người dùng API bối rối.
- Bạn đang muốn gom nhóm nó một cách chặt chẽ với các class method cụ thể khác.
Ví dụ: Cứ dùng hàm bình thường
| |
5) Khai báo rõ tham số vs chỉ dùng *args, **kwargs
Viết tham số rõ ràng khi:
- Bạn đang xây dựng một public API, service class, hay tầng logic lõi (business logic).
- Tính khám phá cực kì quan trọng (developer khác gọi hàm cần biết chính xác nên truyền cái gì vào).
- Bạn muốn dùng type-checking hỗ trợ bởi IDE.
Dùng *args, **kwargs khi:
- Bạn viết wrapper, decorator, hoặc middleware trung gian, với mục đích mù táng (blind pass-through) đẩy mọi giá trị nhận được qua cho hàm kế tiếp.
- Bạn viết hàm con kế thừa (subclassing) và đẩy tất cả argument lên qua
super().__init__().
Ví dụ: Nỗi bực dọc từ **kwargs bị ẩn
| |
Ví dụ: Use-case hoàn hảo cho kwargs
| |
Bảng tra cứu thu gọn (Phiên bản 2)
Dict vs Dataclass:
- Key linh hoạt & payload không cấu trúc → Dict
- Schema cố định & IDE hỗ trợ → Dataclass
List vs Generator:
- Cần đếm len() & lặp nhiều vòng → List
- Dữ liệu khổng lồ & cần chạy step-by-step → Generator
If/else vs Try/except:
- Thường xuyên tịt ngòi & phí kiểm tra rẻ → If/else
- Chạy ngoan 99% & phòng chống race conditions → Try/except
Static method vs Function:
- Không cần
selfhaycls→ Đá nó ra khỏi class và biến thành Python function!
Explicit params vs **kwargs:
- Core logic & API lõi → Tham số rõ ràng (Explicit)
- Decorators & Wrappers ->
*args, **kwargs
