enum và dataclasses — Hằng số có tên và Data Class
Học Enum thay thế chuỗi hằng, IntEnum + auto() cho đánh số tự động và sắp xếp, @dataclass tự sinh boilerplate, và field(default_factory=list) qua ví dụ thật.
Hai module để làm cho ý nghĩa của giá trị trở nên rõ ràng. enum cho bạn thay thế các string literal rải rác trong code bằng hằng số có tên, nên lỗi gõ sai sẽ lộ ra lúc runtime thay vì lặng lẽ trôi qua. dataclasses cho cách định nghĩa "class chỉ chứa dữ liệu" trong một dòng, loại bỏ boilerplate __init__ / __eq__ / __repr__.
Enum — thay thế string literal bằng hằng số có tên
Khi các string literal như "paid" / "shipped" rải rác trong code, lỗi gõ sai kiểu "shippped" lọt qua đến runtime, autocomplete không giúp được, và refactor dễ vỡ. Enum giải quyết điều này bằng định nghĩa tập hằng số làm class — các tên bạn định nghĩa autocomplete được trong editor, lỗi gõ sai raise AttributeError ngay lập tức, lặp qua mọi member hoạt động sẵn, và bạn được giá trị an toàn để dùng làm khóa dict hay nhãn switch.
# Các chuỗi trạng thái rải rác trong codeif status =="paid":ship_order(order_id)elif status =="shippped": # Lỗi gõ — đáng lẽ là "shipped", nhưng bạn không thấy đến runtimenotify_shipped(order_id)elif status =="cancelled":refund(order_id)
String literal so với EnumChuỗi trần không autocomplete và lỗi gõ trôi tới runtime. Enumchỉ cho phép các tên bạn đã định nghĩa, nên bạn được autocomplete, phát hiện lỗi gõ, và lặp toàn bộ member trong một gói.
from enum import EnumclassOrderStatus(Enum):PENDING="pending"PAID="paid"SHIPPED="shipped"CANCELLED="cancelled"# Truy cập member: mỗi member mang một name và một valueprint(OrderStatus.PAID) # OrderStatus.PAIDprint(OrderStatus.PAID.name) # 'PAID'print(OrderStatus.PAID.value) # 'paid'# So sánh với ==status = OrderStatus.PAIDif status == OrderStatus.PAID:print("Đã nhận thanh toán")
Định nghĩa trạng thái đơn hàng dưới dạng Enum và đọc thông tin của các member.
① Hãy import Enum từ module enum
② Hãy định nghĩa class Enum OrderStatus với bốn member PENDING / PAID / SHIPPED / CANCELLED (giá trị là các chuỗi viết thường "pending" / "paid" / "shipped" / "cancelled")
③ Hãy in OrderStatus.PAID.name dưới dạng PAID name: ◯◯
④ Hãy in OrderStatus.PAID.value dưới dạng PAID value: ◯◯
⑤ Hãy in tất cả tên member dưới dạng All members: ◯ (có thể lặp với for s in OrderStatus)
(Nếu code chạy đúng, phần giải thích sẽ hiện ra.)
Python Editor
Chạy code để xem đầu ra
Xây hàm so sánh các member Enum trong if/elif và trả về thông điệp theo trạng thái. Rẽ nhánh trên so sánh member-tới-member thay vì string literal là cách viết an toàn hơn.
① Hãy định nghĩa OrderStatus từ Thực hành 1
② Hãy định nghĩa hàm get_message(status) dùng if/elif trên từng member để trả về: PENDING → "Đang chờ thanh toán", PAID → "Đang chuẩn bị giao hàng", SHIPPED → "Đã giao", CANCELLED → "Đã hủy"
③ Hãy in get_message(OrderStatus.PAID) dưới dạng PAID: ◯
④ Hãy in get_message(OrderStatus.SHIPPED) dưới dạng SHIPPED: ◯
Python Editor
Chạy code để xem đầu ra
IntEnum và auto — đánh số tự động và so sánh số
IntEnum là Enum kế thừa từ int, nên các member so sánh và làm phép số học như integer. Đây là lựa chọn đúng cho hằng số mà thứ tự số quan trọng — độ ưu tiên, level, rank. auto() là hàm gán giá trị tự động thay vì viết bằng tay: xếp hàng các lệnh gọi auto() và chúng thành 1, 2, 3, 4... theo thứ tự định nghĩa.
Khi con số cụ thể không quan trọng (chỉ thứ tự quan trọng), auto() an toàn hơn vì thêm hoặc bớt member không cần viết lại số bằng tay.
IntEnum + autoauto() đánh số tự động 1, 2, 3, .... IntEnumso sánh như int, nên Priority.LOW < Priority.HIGH hoạt động — thứ tự số mã hóa độ ưu tiên.
Định nghĩa IntEnum Priority với auto() và sắp xếp nhiều tác vụ từ ưu tiên cao đến thấp. Xác nhận trong tình huống thực tế rằng auto() đánh số tự động và IntEnum có thể so sánh và sắp xếp như int.
① Hãy import IntEnum và auto từ enum
② Hãy định nghĩa class IntEnum Priority với bốn member LOW / MEDIUM / HIGH / URGENT (giá trị đều là auto())
③ Hãy xây list tất cả giá trị member và in dưới dạng Đánh số: ◯ (xác nhận auto() gán 1, 2, 3, 4)
④ Hãy sắp xếp list tác vụ [("Dọn dẹp", Priority.LOW), ("Trả lời email", Priority.HIGH), ("Chuẩn bị tài liệu", Priority.URGENT), ("Xem xét", Priority.MEDIUM)]từ ưu tiên cao đến thấp, rồi dưới tiêu đề Thứ tự thực hiện: in mỗi tác vụ dưới dạng - tên (priority)
Python Editor
Chạy code để xem đầu ra
@dataclass — định nghĩa class chứa dữ liệu trong một dòng
Viết bằng tay một "class thuần chỉ có field" nghĩa là định nghĩa __init__ để gán thuộc tính, __eq__ (dunder method được gọi cho so sánh ==) cho bằng nhau theo nội dung, __repr__ (dunder method được gọi bởi print và REPL) để hiển thị dễ đọc, v.v. — cùng pattern boilerplate xếp hàng mỗi lần. Một decorator @dataclass duy nhất sinh tất cả thứ đó tự động từ các khai báo field có type hint.
Nói cách khác, chỉ cần xếp hàng các thuộc tính có type hint là xây xong cả class — bạn được cùng tính năng với code ít hơn nhiều so với class viết tay.
@dataclass tự sinh cái gìChỉ cần khai báo các field có type hint và bạn được __init__ / __eq__ / __repr__ miễn phí. field(default_factory=list) cho bạn an toàn đặt mặc định mutable như list rỗng, và frozen=True biến class thành bất biến (chặn đổi thuộc tính).
Dùng default_factory cho mặc định mutable
Cố mặc định một list với @dataclass bằng cách viết items: list = [] raise SyntaxError (hoặc cảnh báo deprecation). Đó là một biện pháp phòng vệ cho bug kinh điển "mọi instance cùng chia sẻ một list". Hãy dùng field(default_factory=list) thay vào đó, tạo list rỗng mới cho mỗi instance, an toàn. Cùng ý cho dict / set: field(default_factory=dict).
Định nghĩa Order với @dataclass và xác nhận __eq__ và __repr__ tự sinh.
① Hãy import dataclass và field từ dataclasses
② Hãy định nghĩa class Order được decorate với @dataclass — bốn field: id: int, customer: str, items: list = field(default_factory=list), is_paid: bool = False
③ Hãy tạo instance với Order(id=1234, customer="Minh", items=["apple", "banana"]) và in trực tiếp (__repr__ tự sinh kích hoạt)
④ Hãy tạo instance thứ hai với cùng nội dung và in kết quả so sánh với == dưới dạng Bằng: True / False
⑤ Hãy đổi is_paid của instance đầu tiên thành True, rồi so sánh lại với == và in dưới dạng Bằng sau thay đổi: True / False
Python Editor
Chạy code để xem đầu ra
QUIZ
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 1Lợi thế của việc thay thế so sánh string literal "paid" bằng Enum là gì?
Câu 2Lợi thế của auto() trong IntEnum là gì?
Câu 3Cách đúng để mặc định một field thành list trong @dataclass là gì?