Tối ưu hóa Class trong Python với Class Method – P1
Các Class trong Python cung cấp một framework tuyệt vời để tạo các đối tượng có thể xử lý cấu trúc dữ liệu, quy trình, thuật toán hoặc các mô hình học máy phức tạp. Lập trình hướng đối tượng (OOP) cung cấp rất nhiều mô-đun và khả năng tái sử dụng, cho phép các nhà khoa học dữ liệu và kỹ sư máy học phát triển các cơ sở mã linh hoạt và có thể mở rộng. Cá nhân tôi thấy việc cấu trúc mã của mình dưới dạng các Class và đối tượng cực kì hữu ích cho công việc phát triển phần mềm bất kể đó là thêm tính năng mới, sửa đổi các tính năng hiện có hay fix các bug khó chịu.
Trong Python, nhìn chung có ba loại phương thức cho Class : (Câu này đi phỏng vấn hay được hỏi nè)
- Instance methods
- Static methods
- Class methods
Hãy cùng đi vào từng loại phương thức nhé !
- Instance methods (những phương thức bạn xác định với self làm đối số đầu tiên) lấy một instance của Class làm đầu vào ngầm định và cho phép người dùng tương tác với các thuộc tính của lớp. Instance method cực kỳ mạnh mẽ vì chúng có thể truy cập và sửa đổi dữ liệu cũng như cấu hình trong một instance để thực hiện các phép tính và triển khai logic phức tạp, đồng thời có khả năng đọc và bảo trì lại code cao.
- Static methods (được xác định bằng decorator @staticmethod) thuộc về một Class chứ không phải là một thể hiện của Class và không có quyền truy cập vào instance hoặc các thuộc tính của nó thông qua self. Trường hợp sử dụng phổ biến nhất cho các phương thức này là xác định các hàm tiện ích có ý nghĩa trong ngữ cảnh của một lớp cụ thể.
- Và cuối cùng, có Class methods, được liên kết với Class chứ không phải với thể hiện của Class, nghĩa là chúng có thể sửa đổi trạng thái Class sao cho nó áp dụng cho tất cả các instance. Trong blog này, tôi sẽ chỉ tập trung vào các phương thức phân loại và tiềm năng của chúng để tăng cường code của mình với các lợi ích bổ sung của OOP. Tôi sẽ chia sẻ một số mẹo đặc biệt hướng tới các ứng dụng khoa học dữ liệu và máy học mà tôi hy vọng bạn có thể áp dụng cho quy trình làm việc tuyệt vời hàng ngày của mình.
Đầu tiên chúng ta cần tìm hiểu :
Class Methods là gì ?
Như chúng ta vừa đề cập, một Class method là một phương thức được liên kết với chính Class đó chứ không phải instance của class đó. Điều này có nghĩa là nó có thể thay đổi trạng thái của Class sao cho áp dụng được cho tất cả các phiên bản hiện tại hoặc tương lai của Class đó.
Một ví dụ nổi tiếng và thực tế đó chính là khi chúng ta muốn tạo một singleton class.
Singleton là một mẫu thiết kế cho phép bạn hạn chế một Class chỉ có một instance.
class Singleton(object):
"""Ví dụ về Singleton class chỉ có thể tạo 1 instance."""
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is not None:
raise Exception("Singleton class can only have one instance.")
cls._instance = object.__new__(cls, *args, **kwargs)
return cls._instance
# Tạo instance đầu tiên
single = Singleton()
# Thử tạo instance thứ 2
double = Singleton()
# Error Output:
# Exception: Singleton class can only have one instance.
Đây là một ví dụ khi tôi muốn khởi tạo double
nhưng không thành công vì chỉ có thể tạo 1 instance cho một Singleton class do phiên bản trước đó đã tồn tại bằng cách kiểm tra trạng thái của thuộc tính Class là _instance. Chúng ta có thể kiểm tra thuộc tính này để thấy rõ :
Singleton._instance
# Output:
# <__main__.Singleton at 0x7f7c10491f30>
Nhưng làm thế nào chúng ta có thể thay đổi trạng thái của toàn bộ Singleton Class ?
Nhìn vào định nghĩa của phương thức __new__, bạn có thể thấy rằng nó lấy cls (biểu diễn thông thường của đối tượng lớp) làm đối số đầu tiên. Điều này có nghĩa là __new__ là một Class method và nó có thể thay đổi trạng thái của Class, tría ngược với Instance Method thông thường bạn chỉ có thể thay đổi trạng thái của một instance của Class. Vì vậy khi chúng tôi tạo phiên bản đầu tiên (gọi ngầm là __new__ ) chúng ta có thể thay đổi điều gì đó cơ bản về chính lớp đó để cho biết rằng chúng ta đã sử dụng nó một lần.
Và đó là toàn bộ ý tưởng đằng sau Class method. Chúng cho phép chúng ta định nghĩa các phương thức trong một Class được liên kết với chính Class đó chứ không phải instance của nó, do đó chúng cho phép chúng ta sửa đổi hành vi của Class và làm cho nó linh hoạt hơn.
Trong khoa học dữ liệu và học máy, tính linh hoạt này là vô giá, các phương thức lớp cung các cách thay thế hiệu quả hơn để khởi tạo các lớp quản lý việc xử lí dữ liệu, cấu hình mô hình hoặc kết nối cơ sở dữ liệu. Điều này sẽ dấn tới code được clean hơn, dễ bảo trì hơn và có khả năng mở rộng cao hơn – hãy lưu ý, bạn sẽ nghe tôi nói điều này nhiều lần trong suốt bài viết này :))
Bây giờ chúng ta hãy xem xét một số trường hợp sử dụng thực tế trong đó @classmethods tỏ ra đặc biệt hữu ích nhé
Class Method được liên kết với chính lớp đó chứ không phải instance của Class. Điều này có nghĩa là họ có thể thay đổi trạng thái của lớp sao cho nó được áp dụng trên tất cả các phiên bản hiện tại hoặc tương lai của Class.
Cách sử dụng Class Method trong các dự án liên quan tới Data
Các Class dùng để xử lí dữ liệu là các Class mang tính điển hình của đa số các dự án. Thử tưởng tượng bạn có Class DataProcessor xử lý một số danh sách tác vụ xử lý dữ liệu phức tạp. Cách thông thường để tạo một instance của Classs này bao gồm việc khởi tạo nó bằng dữ liệu tồn tại trong bộ nhớ rồi xử lí nó.
Code của nó sẽ kiểu như vậy :
class DataProcessor:
def __init__(self, data):
self.data = data # Lấy data từ bộ nhớ
def process_data(self):
# Xử lí dữ liệu trong bộ nhớ
...
processor = DataProcessor(data=data)
processor.process_data()
Tuyệt vời! Bây giờ hãy tưởng tượng bạn muốn làm cho Class linh hoạt hơn để có thể đọc tệp csv từ đĩa. Nếu bạn chỉ thêm một phương thức đọc tệp, bạn sẽ gặp vấn đề khi khởi tạo Class. Bạn sẽ phải khởi tạo Class của mình bằng một số đối tượng dữ liệu trống, sau đó chạy phương thức tải dữ liệu để ghi đè lên dữ liệu đó. Ví dụ như:
class DataProcessor:
def __init__(self, data):
self.data = data # Lấy data từ bộ nhớ
def process_data(self):
# Xử lí dữ liệu trong bộ nhớ
...
def from_csv(self, filepath):
self.data = pd.read_csv(filepath)
processor = DataProcessor(data=None)
processor.from_csv("path_to_your_file.csv")
processor.process_data()
Code này chạy được đấy, nhưng nó rất kém hiệu quả, quá dài dòng và thành thật mà nói thì code này cực kì rác.
Dưới đây là cách tốt hơn để làm điều đó với @classmethods
. Chúng ta có thể định nghĩa một Class Method, from_csv()
, hoạt động như một hàm tạo thay thế. Nó cần một tập hợp đầu vào thay thế (ở đây là đường dẫn tệp thay vì dữ liệu trong bộ nhớ) và cho phép chúng tôi load trực tiếp dữ liệu từ file CSV khi tôi tạo một phiên bản của DataProcessor. Ví dụ như :
class DataProcessor:
def __init__(self, data):
self.data = data
def process_data(self):
...
@classmethod
def from_csv(cls, filepath):
data = pd.read_csv(filepath)
return cls(data)
# Khởi tạo và sử dụng Class với Class Method
processor = DataProcessor.from_csv("path_to_your_file.csv")
processor.process_data()
Ngon rồi! Hãy xem cách sử dụng .from_csv()
giúp việc khởi tạo Class trở nên sạch sẽ và hiệu quả hơn nhiều như thế nào? Giống như có một cửa sổ bí mật để vào Class, khi bạn không muốn sử dụng cửa chính phải không? Và tất nhiên, tùy thuộc vào trường hợp sử dụng của bạn, bạn sẽ cần phải quyết định điều gì đi qua cửa chính và điều gì đi qua cửa sổ (tức là nếu Class lấy dữ liệu trong bộ nhớ hoặc từ đường dẫn tệp theo mặc định).
Đương nhiên, khái niệm về các hàm tạo Class thay thế này có thể được mở rộng. Ví dụ: nếu bạn muốn tải dữ liệu từ các file định dạng parquet, bạn có thể thêm một phương thức Class khác cho việc đó.
class DataProcessor:
def __init__(self, data):
self.data = data
def process_data(self):
...
@classmethod
def from_csv(cls, filepath):
...
@classmethod
def from_parquet(cls, filepath):
data = pd.read_parquet(filepath)
return cls(data)
Và, nếu muốn tổng quát hơn, chúng ta có thể định nghĩa một phương thức export là from_file
để phát hiện loại tệp bạn đang truyền vào và gọi trình tải thích hợp cho nó. Siêu tuyệt vời phải không?