Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Tùy chỉnh và mở rộng lớp Enum trong Python
Trong bài viết này, mình sẽ tìm hiểu cách tùy chỉnh và mở rộng các lớp Enum (liệt kê) trong Python. Python Enum không chỉ đơn thuần là một danh sách các hằng số, mà chúng còn là những lớp có thể được mở rộng và tùy chỉnh với các phương thức đặc biệt (dunder methods) để tạo ra những hành vi riêng biệt. Qua việc triển khai các phương thức như __str__
, __eq__
, và __lt__
, bạn có thể thay đổi cách các giá trị Enum được so sánh, sắp xếp hoặc hiển thị, giúp tăng cường tính linh hoạt và sức mạnh của các lớp liệt kê trong Python.
Tùy chỉnh lớp Enum trong Python
Các liệt kê (enumerations) trong Python thực chất là các lớp (classes). Điều này có nghĩa là bạn có thể thêm các phương thức (methods) vào chúng hoặc triển khai các phương thức đặc biệt (dunder methods) để tùy chỉnh hành vi của chúng.
Ví dụ sau định nghĩa lớp liệt kê PaymentStatus
:
from enum import Enum class PaymentStatus(Enum): PENDING = 1 COMPLETED = 2 REFUNDED = 3
Lớp PaymentStatus
có ba thành viên: PENDING
, COMPLETED
, và REFUNDED
.
Bài viết này được đăng tại [free tuts .net]
Nếu bạn hiển thị thành viên PENDING
:
print(PaymentStatus.PENDING)
Kết quả sẽ là:
PaymentStatus.PENDING
Tùy chỉnh hiển thị chuỗi
Bạn có thể tùy chỉnh cách hiển thị chuỗi của các thành viên trong liệt kê bằng cách triển khai phương thức __str__
. Ví dụ:
from enum import Enum class PaymentStatus(Enum): PENDING = 1 COMPLETED = 2 REFUNDED = 3 def __str__(self): return f'{self.name.lower()}({self.value})' print(PaymentStatus.PENDING)
Kết quả sẽ là:
pending(1)
Triển khai phương thức __eq__
Khi so sánh thành viên của PaymentStatus
với một số nguyên, ví dụ như:
if PaymentStatus.PENDING == 1: print('The payment is pending.')
Sẽ không có gì được in ra, vì PaymentStatus.PENDING
không bằng với số nguyên 1
.
Để so sánh các thành viên của PaymentStatus
với số nguyên, bạn có thể triển khai phương thức __eq__
như sau:
from enum import Enum class PaymentStatus(Enum): PENDING = 1 COMPLETED = 2 REFUNDED = 3 def __str__(self): return f'{self.name.lower()}({self.value})' def __eq__(self, other): if isinstance(other, int): return self.value == other if isinstance(other, PaymentStatus): return self is other return False if PaymentStatus.PENDING == 1: print('The payment is pending.')
Trong phương thức __eq__
:
- Nếu giá trị so sánh là số nguyên, nó sẽ so sánh giá trị của thành viên với số nguyên.
- Nếu giá trị so sánh là một thể hiện của
PaymentStatus
, nó sẽ so sánh giá trị với thành viên bằng toán tửis
. - Nếu không, nó sẽ trả về
False
.
Kết quả của đoạn mã trên sẽ là:
The payment is pending.
Triển khai phương thức __lt__
Giả sử bạn muốn các thành viên của PaymentStatus
có thứ tự sắp xếp dựa trên giá trị của chúng, và bạn cũng muốn so sánh chúng với một số nguyên.
Bạn có thể triển khai phương thức __lt__
và sử dụng decorator @total_ordering
từ module functools
:
from enum import Enum from functools import total_ordering @total_ordering class PaymentStatus(Enum): PENDING = 1 COMPLETED = 2 REFUNDED = 3 def __str__(self): return f'{self.name.lower()}({self.value})' def __eq__(self, other): if isinstance(other, int): return self.value == other if isinstance(other, PaymentStatus): return self is other return False def __lt__(self, other): if isinstance(other, int): return self.value < other if isinstance(other, PaymentStatus): return self.value < other.value return False # So sánh với một số nguyên status = 1 if status < PaymentStatus.COMPLETED: print('The payment has not completed.') # So sánh với một thành viên khác status = PaymentStatus.PENDING if status >= PaymentStatus.COMPLETED: print('The payment is not pending.')
Triển khai phương thức __bool__
Mặc định, tất cả các thành viên của liệt kê đều có giá trị là True
. Ví dụ:
for member in PaymentStatus: print(member, bool(member))
Nếu bạn muốn thay đổi hành vi này, bạn có thể triển khai phương thức __bool__
. Giả sử bạn muốn các thành viên COMPLETED
và REFUNDED
có giá trị True
, còn PENDING
là False
. Đoạn mã sau minh họa cách làm điều này:
from enum import Enum from functools import total_ordering @total_ordering class PaymentStatus(Enum): PENDING = 1 COMPLETED = 2 REFUNDED = 3 def __str__(self): return f'{self.name.lower()}({self.value})' def __eq__(self, other): if isinstance(other, int): return self.value == other if isinstance(other, PaymentStatus): return self is other return False def __lt__(self, other): if isinstance(other, int): return self.value < other if isinstance(other, PaymentStatus): return self.value < other.value return False def __bool__(self): if self is self.COMPLETED: return True return False for member in PaymentStatus: print(member, bool(member))
Kết quả sẽ là:
pending(1) False completed(2) True refunded(3) False
Mở rộng lớp Enum trong Python
Python không cho phép mở rộng một lớp Enum nếu nó đã có các thành viên. Tuy nhiên, điều này không phải là một hạn chế thực sự. Bạn có thể định nghĩa một lớp cơ bản (base class) có phương thức nhưng không có thành viên, sau đó mở rộng lớp cơ bản này.
Ví dụ, đầu tiên, định nghĩa lớp cơ bản OrderedEnum
để sắp xếp các thành viên theo giá trị:
from enum import Enum from functools import total_ordering @total_ordering class OrderedEnum(Enum): def __lt__(self, other): if isinstance(other, OrderedEnum): return self.value < other.value return NotImplemented
Sau đó, định nghĩa lớp ApprovalStatus
kế thừa từ OrderedEnum
:
class ApprovalStatus(OrderedEnum): PENDING = 1 IN_PROGRESS = 2 APPROVED = 3
Cuối cùng, bạn có thể so sánh các thành viên của lớp ApprovalStatus
:
status = ApprovalStatus(2) if status < ApprovalStatus.APPROVED: print('The request has not been approved.')
Kết quả sẽ là:
The request has not been approved.
Kết bài
Việc triển khai các phương thức đặc biệt cho phép chúng ta tùy chỉnh hành vi của các lớp Enum trong Python, từ đó có thể dễ dàng quản lý và thao tác với các thành viên trong lớp liệt kê theo cách phù hợp với yêu cầu cụ thể của dự án. Ngoài ra, bằng cách định nghĩa một lớp Enum cơ bản không có thành viên và phương thức, chúng ta có thể mở rộng lớp này để tạo ra nhiều lớp Enum khác nhau, tận dụng lại các phương thức tùy chỉnh đã được định nghĩa. Cách tiếp cận này giúp mã nguồn trở nên linh hoạt, dễ bảo trì và tiết kiệm thời gian khi làm việc với các Enum có yêu cầu phức tạp.