Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Mô tả Descriptors trong Python
Trong bài viết này, bạn sẽ tìm hiểu descriptors trong Python—một cơ chế mạnh mẽ cho phép bạn tùy chỉnh cách các thuộc tính của lớp được truy cập và gán giá trị. Bằng cách hiểu cách descriptors hoạt động và cách triển khai chúng, bạn có thể quản lý các thuộc tính phức tạp một cách hiệu quả, đồng thời tối ưu hóa khả năng tái sử dụng mã và giảm thiểu sự lặp lại không cần thiết. Hãy cùng tìm hiểu cách sử dụng descriptors để cải thiện chất lượng mã Python của bạn.
Giới thiệu về Python Descriptors
Giả sử bạn có một lớp Person
với hai thuộc tính là first_name
và last_name
:
class Person: def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name
Bạn muốn các thuộc tính first_name
và last_name
phải là các chuỗi không rỗng, nhưng với cách khai báo thông thường như trên, không có gì đảm bảo rằng các giá trị này sẽ hợp lệ.
Để đảm bảo tính hợp lệ của dữ liệu, bạn có thể sử dụng thuộc tính property
kết hợp với các phương thức getter và setter, như sau:
Bài viết này được đăng tại [free tuts .net]
class Person: def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name @property def first_name(self): return self._first_name @first_name.setter def first_name(self, value): if not isinstance(value, str): raise ValueError('First name phải là chuỗi') if len(value) == 0: raise ValueError('First name không được để trống') self._first_name = value @property def last_name(self): return self._last_name @last_name.setter def last_name(self, value): if not isinstance(value, str): raise ValueError('Last name phải là chuỗi') if len(value) == 0: raise ValueError('Last name không được để trống') self._last_name = value
Trong lớp Person
này, phương thức getter trả về giá trị của thuộc tính, trong khi phương thức setter kiểm tra tính hợp lệ trước khi gán giá trị cho thuộc tính.
Mặc dù cách này hoạt động tốt, nhưng nó lặp lại logic kiểm tra cho cả first_name
và last_name
. Nếu lớp có thêm nhiều thuộc tính cần kiểm tra chuỗi không rỗng, bạn sẽ phải lặp lại logic này nhiều lần.
Để tránh việc lặp lại code, bạn có thể tách phần logic kiểm tra ra một phương thức riêng. Tuy nhiên, Python cung cấp một giải pháp tốt hơn: sử dụng descriptors.
Định nghĩa một lớp Descriptor trong Python
Đầu tiên, hãy định nghĩa một lớp descriptor với ba phương thức là __set_name__
, __get__
, và __set__
:
class RequiredString: def __set_name__(self, owner, name): self.property_name = name def __get__(self, instance, owner): if instance is None: return self return instance.__dict__.get(self.property_name) def __set__(self, instance, value): if not isinstance(value, str): raise ValueError(f'{self.property_name} phải là chuỗi') if len(value) == 0: raise ValueError(f'{self.property_name} không được để trống') instance.__dict__[self.property_name] = value
Sau đó, sử dụng lớp RequiredString
trong lớp Person
:
class Person: first_name = RequiredString() last_name = RequiredString()
Nếu bạn gán một chuỗi rỗng hoặc giá trị không phải chuỗi cho các thuộc tính first_name
hoặc last_name
, sẽ có lỗi xảy ra:
try: person = Person() person.first_name = '' except ValueError as e: print(e)
Kết quả:
First name không được để trống
Bạn cũng có thể sử dụng lớp RequiredString
này cho bất kỳ lớp nào khác có các thuộc tính yêu cầu giá trị chuỗi không rỗng.
Ngoài RequiredString
, bạn có thể định nghĩa các descriptor khác để kiểm tra các kiểu dữ liệu khác như tuổi, email, hoặc số điện thoại. Đây chỉ là một ứng dụng đơn giản của descriptors.
Protocol của Descriptor trong Python
Trong Python, protocol của descriptor bao gồm ba phương thức:
__get__
: Lấy giá trị của thuộc tính.__set__
: Gán giá trị cho thuộc tính.__delete__
: Xóa thuộc tính.
Ngoài ra, descriptor có thể có phương thức __set_name__
để thiết lập tên thuộc tính khi một lớp được tạo ra.
Descriptor là gì?
Descriptor là một đối tượng của lớp mà nó triển khai một trong những phương thức thuộc protocol của descriptor.
Có hai loại descriptors:
- Data Descriptor: Là đối tượng của lớp triển khai các phương thức
__set__
và/hoặc__delete__
. - Non-data Descriptor: Là đối tượng chỉ triển khai phương thức
__get__
.
Cách Descriptor hoạt động
Hãy sửa đổi lớp RequiredString
để in ra các tham số mỗi khi phương thức __set_name__
, __get__
, và __set__
được gọi:
class RequiredString: def __set_name__(self, owner, name): print(f'__set_name__ được gọi với owner={owner} và name={name}') self.property_name = name def __get__(self, instance, owner): print(f'__get__ được gọi với instance={instance} và owner={owner}') if instance is None: return self return instance.__dict__.get(self.property_name) def __set__(self, instance, value): print(f'__set__ được gọi với instance={instance} và value={value}') if not isinstance(value, str): raise ValueError(f'{self.property_name} phải là chuỗi') if len(value) == 0: raise ValueError(f'{self.property_name} không được để trống') instance.__dict__[self.property_name] = value
Khi bạn khởi tạo lớp Person
, Python sẽ tự động gọi phương thức __set_name__
của các đối tượng descriptor first_name
và last_name
. Ví dụ:
class Person: first_name = RequiredString() last_name = RequiredString()
Kết quả:
__set_name__ được gọi với owner=<class '__main__.Person'> và name=first_name __set_name__ được gọi với owner=<class '__main__.Person'> và name=last_name
Phương thức __set__
Khi bạn gán một giá trị mới cho descriptor, Python sẽ gọi phương thức __set__
. Ví dụ:
person = Person() person.first_name = 'John'
Kết quả:
__set__ được gọi với instance=<__main__.Person object> và value=John
Phương thức __set__
lưu trữ giá trị vào từ điển __dict__
của đối tượng instance
.
Phương thức __get__
Khi bạn truy cập vào thuộc tính, Python sẽ gọi phương thức __get__
. Ví dụ:
person = Person() person.first_name = 'John' print(person.first_name)
Kết quả:
__get__ được gọi với instance=<__main__.Person object> và owner=<class '__main__.Person'> John
Phương thức __get__
trả về giá trị của thuộc tính từ từ điển __dict__
của đối tượng.
Kết bài
Descriptors là một công cụ mạnh mẽ trong Python, cho phép bạn kiểm soát và tùy chỉnh hành vi của thuộc tính trong các lớp thông qua việc triển khai các phương thức như __set__
, __get__
, và __delete__
. Chúng giúp cải thiện khả năng tái sử dụng mã, đơn giản hóa quá trình xác thực dữ liệu và giảm thiểu sự lặp lại trong các lớp phức tạp. Việc nắm vững cách sử dụng descriptors không chỉ giúp mã nguồn của bạn trở nên linh hoạt và dễ bảo trì hơn mà còn mở ra những tiềm năng mới trong việc quản lý và tối ưu hóa dữ liệu trong Python.