Lớp gói dữ liệu cùng những gì bạn làm với dữ liệu đó
Lớp (class) cho phép bạn định nghĩa một loại đối tượng (một người dùng, một tài khoản ngân hàng, một cấu hình, một request) cùng mọi việc mà đối tượng đó có thể làm. Mỗi đối tượng cụ thể là một instance của lớp. Các hành động được gọi là phương thức (method).
Đây là lớp đơn giản nhất có thể:
Phân tích từng phần:
class User:bắt đầu định nghĩa một lớp. Theo quy ước, tên lớp viết kiểu PascalCase.__init__là hàm khởi tạo. Nó tự động chạy khi bạn tạo một instance mới, và thiết lập các thuộc tính của instance bằngself.whatever = ....greetlà một phương thức.selflà cách một phương thức tham chiếu tới instance mà nó được gọi trên đó.User("Rosa", "rosa@example.com")tạo ra một instance. Python gọi__init__vớiselflà instance mới, và chuyển các đối số còn lại vào.user.greet()gọi phương thức. Python tự động truyềnuservào làmself.
self chỉ là đối số đầu tiên
self không phải là từ khóa. Đó là tên mà người ta đặt cho tham số đầu tiên của phương thức instance, tức là instance mà phương thức đang chạy trên đó. Bạn có thể đặt tên khác; ai cũng dùng self đơn giản vì ai cũng dùng nó.
Mọi phương thức instance đều nhận self làm tham số đầu tiên. Nhờ đó phương thức biết cần đọc hoặc thay đổi instance nào.
Thêm hành vi
Phương thức có thể đọc và thay đổi trạng thái:
__repr__ là một phương thức đặc biệt khác, một điểm móc trả lời câu hỏi "khi in ra thì hiển thị đối tượng này thế nào?". Viết một __repr__ tốt sẽ giúp ích rất nhiều khi gỡ lỗi.
Thuộc tính lớp và thuộc tính instance
Thuộc tính khai báo trong thân lớp (bên ngoài __init__) được dùng chung cho mọi instance. Thuộc tính gán cho self thì riêng cho từng instance:
Hãy dùng thuộc tính lớp cho những giá trị thật sự dùng chung: hằng số, bộ đếm, cấu hình. Dữ liệu riêng của từng instance thì đặt trong __init__.
Kế thừa
Một lớp có thể kế thừa từ lớp khác, nhận sẵn các phương thức và thuộc tính của lớp đó rồi thêm mới hoặc ghi đè khi cần:
super() cho bạn truy cập lớp cha, thường được dùng trong __init__ để khởi tạo các thuộc tính của lớp cha trước khi thêm thuộc tính của riêng bạn:
Kế thừa hữu ích nhưng rất dễ bị lạm dụng. Người mới học Python hay dùng kế thừa trong khi composition (giữ một đối tượng làm thuộc tính) sẽ rõ ràng hơn. Hãy bắt đầu với cấu trúc lớp phẳng và chỉ dùng kế thừa khi thật sự có quan hệ "là một".
Trang kế thừa trong Python đi sâu hơn: mở rộng phương thức của lớp cha bằng super(), isinstance và issubclass, lớp cơ sở trừu tượng, đa kế thừa và thứ tự phân giải phương thức.
Dataclass: lựa chọn mặc định dễ chịu hơn cho bản ghi
Khi bạn chủ yếu muốn một lớp chứa vài thuộc tính, kèm khả năng so sánh bằng và hiển thị, @dataclass giúp bạn bớt rất nhiều code lặp lại:
Hãy so với việc tự viết __init__, __repr__ và __eq__. Dataclass là lựa chọn đầu tiên đúng đắn khi đáng lẽ bạn sẽ viết class Thing chỉ có mỗi __init__ gán thuộc tính.
Property: thuộc tính được tính toán
Đôi khi bạn muốn một "thuộc tính" mà thật ra được tính ra. @property khiến việc đó trông như truy cập thuộc tính:
Hãy dùng property ở chỗ bạn định viết obj.get_something(): thay bằng property sẽ khiến API tự nhiên hơn. Đừng dùng nó để che giấu những thao tác tốn kém; người dùng luôn nghĩ truy cập thuộc tính là nhanh.
Riêng tư theo quy ước
Python không có private thật sự. Theo quy ước, thuộc tính bắt đầu bằng dấu gạch dưới được ngầm hiểu là riêng tư, tức là người đọc không nên đụng tới chúng từ bên ngoài lớp:
class Cache:
def __init__(self):
self._store = {}
def put(self, key, value):
self._store[key] = value
def get(self, key, default=None):
return self._store.get(key, default)
Tên có hai dấu gạch dưới (__name) kích hoạt name mangling, cơ chế thêm tên lớp vào tên thuộc tính. Đó là công cụ để tránh trùng tên khi kế thừa, không phải biện pháp bảo mật.
Khi nào không nên dùng lớp
Nếu bạn chỉ có một nhóm dữ liệu liên quan, một dataclass, một dict thông thường hoặc một named tuple là đủ. Nếu bạn chỉ có một hàm, nó không cần lớp nào để chứa. Hãy dùng lớp khi bạn có cả trạng thái lẫn hành vi gắn với nhau, đặc biệt khi bạn sẽ có nhiều instance cùng loại.
Một ví dụ cuối
Một bộ đệm log nhỏ, hoạt động giống tệp:
Hãy để ý __len__, thêm một phương thức "ma thuật" nữa. Định nghĩa nó khiến len(logger) hoạt động, vì len(x) thực chất là lời gọi x.__len__(). Python có hàng chục điểm móc như vậy; bạn sẽ dần làm quen khi cần.
Câu hỏi thường gặp
Lớp trong Python là gì?
Lớp (class) là bản thiết kế để tạo ra các đối tượng gói dữ liệu (thuộc tính) và hành vi (phương thức) lại với nhau. class User: định nghĩa lớp User; User(...) tạo ra một instance. Lớp là công cụ chính của Python để mô hình hóa những thứ vừa có trạng thái vừa có hành vi.
self trong Python có nghĩa là gì?
self là tham số đầu tiên của mọi phương thức instance. Khi bạn gọi user.greet(), Python truyền instance user vào làm self. Bên trong phương thức, self.name truy cập thuộc tính name của chính user đó. Tên self là quy ước, không phải từ khóa, nhưng mọi lập trình viên Python đều dùng nó.
Python có phải ngôn ngữ hướng đối tượng không?
Có. Mọi thứ trong Python đều là đối tượng, kể cả số, chuỗi và hàm. Bạn có thể lập trình hướng đối tượng khi phù hợp, nhưng Python không bắt buộc. Nhiều chương trình Python thực tế dùng lớp khá dè dặt, và ưu tiên hàm cùng cấu trúc dữ liệu thông thường khi có thể.