Python Descriptors là một cách để tùy chỉnh quyền truy cập, gán và xóa các thuộc tính của đối tượng. Chúng cung cấp một cơ chế mạnh mẽ để quản lý hành vi của các thuộc tính bằng cách định nghĩa các phương thức để lấy, thiết lập và xóa giá trị của chúng. Descriptors thường được sử dụng để triển khai các thuộc tính, phương thức và xác thực thuộc tính.
Một descriptor là bất kỳ đối tượng nào thực hiện ít nhất một trong các phương thức như __get__, __set__ và __delete__. Các phương thức này kiểm soát cách giá trị của thuộc tính được truy cập và sửa đổi.
Khi một thuộc tính được truy cập trên một thể hiện, Python sẽ tìm thuộc tính đó trong lớp của thể hiện. Nếu thuộc tính được tìm thấy và nó là một descriptor, thì Python sẽ gọi phương thức descriptor thích hợp thay vì chỉ trả về giá trị của thuộc tính. Điều này cho phép descriptor kiểm soát những gì xảy ra trong quá trình truy cập thuộc tính.
Giao thức mô tả (descriptor protocol) là một cơ chế cấp thấp được sử dụng bởi nhiều tính năng cấp cao trong Python như thuộc tính (properties), phương thức (methods), phương thức tĩnh (static methods) và phương thức lớp (class methods). Các mô tả (descriptors) có thể được sử dụng để triển khai các mẫu như tải lười (lazy loading), kiểm tra kiểu (type checking) và thuộc tính tính toán (computed properties).
Python Descriptors bao gồm ba phương thức chính, đó là __get__(), __set__() và __delete__(). Như chúng ta đã thảo luận ở trên, các phương thức này kiểm soát hành vi của việc truy cập, gán và xóa thuộc tính, tương ứng.
__get__() method trong các mô tả là một phần quan trọng của giao thức mô tả trong Python. Nó được gọi để lấy giá trị của một thuộc tính từ một thể hiện hoặc từ lớp. Hiểu cách mà __get__() method hoạt động là rất quan trọng để tạo ra các mô tả tùy chỉnh có thể quản lý việc truy cập thuộc tính theo những cách tinh vi.
Cú pháp sau đây là Python Descriptor __get__ method −
def __get__(self, instance, owner): """ instance: the instance that the attribute is accessed through, or None when accessed through the owner class. owner: the owner class where the descriptor is defined. """
Dưới đây là các tham số của phương thức này −
Dưới đây là ví dụ cơ bản về phương thức __get__() trong đó nó trả về giá trị đã lưu _value khi obj.attr được truy cập −
class Descriptor: def __get__(self, instance, owner): if instance is None:return self return instance._value class MyClass: attr = Descriptor() def __init__(self, value): self._value = value obj = MyClass(42) print(obj.attr)
Output
42
__set__() method là một phần của giao thức mô tả trong Python và được sử dụng để kiểm soát hành vi của việc thiết lập giá trị của một thuộc tính. Khi một thuộc tính được quản lý bởi một mô tả được gán một giá trị mới, thì __set__() method sẽ được gọi, cho phép người dùng tùy chỉnh hoặc thực thi các quy tắc cho việc gán giá trị.
Cú pháp sau đây của Python Descriptor __set__() method −
def __set__(self, instance, value): """ instance: the instance of the class where the attribute is being set. value: the value to assign to the attribute. """
Dưới đây là các tham số của phương thức này −
Dưới đây là ví dụ cơ bản về phương thức __set__() trong đó đảm bảo rằng giá trị gán cho attr là một số nguyên −
class Descriptor: def __set__(self, instance, value): if not isinstance(value, int): raise TypeError("Value must be an integer") instance._value = value class MyClass: attr = Descriptor() def __init__(self, value): self.attr = value obj = MyClass(42) print(obj.attr) obj.attr = 100 print(obj.attr)
Output
<__main__.Descriptor object at 0x000001E5423ED3D0> <__main__.Descriptor object at 0x000001E5423ED3D0>
__delete__() method trong giao thức mô tả cho phép chúng ta kiểm soát những gì xảy ra khi một thuộc tính bị xóa khỏi một thể hiện. Điều này có thể hữu ích cho việc quản lý tài nguyên, dọn dẹp hoặc thực thi các ràng buộc khi một thuộc tính bị loại bỏ.
Cú pháp sau đây là Python Descriptor __delete__() method −
def __delete__(self, instance): """ instance: the instance of the class from which the attribute is being deleted. """
Dưới đây là các tham số của phương thức này −
Dưới đây là ví dụ cơ bản về phương thức __set__() trong đó đảm bảo rằng giá trị gán cho attr là một số nguyên −
class LoggedDescriptor: def __init__(self, name): self.name = name def __get__(self, instance, owner): return instance.__dict__.get(self.name) def __set__(self, instance, value): instance.__dict__[self.name] = value def __delete__(self, instance): if self.name in instance.__dict__: print(f"Deleting {self.name} from {instance}") del instance.__dict__[self.name] else: raise AttributeError(f"{self.name} not found") class Person: name = LoggedDescriptor("name") age = LoggedDescriptor("age") def __init__(self, name, age): self.name = name self.age = age # Example usage p = Person("Tutorialspoint", 30) print(p.name) print(p.age) del p.name print(p.name) del p.age print(p.age)
Output
Tutorialspoint 30 Deleting name from <__main__.Person object at 0x0000021A1A67E2D0> None Deleting age from <__main__.Person object at 0x0000021A1A67E2D0> None
Trong Python, descriptors có thể được phân loại rộng rãi thành hai loại dựa trên các phương thức mà chúng triển khai. Chúng là −
Hãy cùng xem xét hai loại trình mô tả trong Python một cách chi tiết để hiểu rõ hơn.
Data descriptors là một loại mô tả trong Python định nghĩa cả phương thức __get__() và __set__() . Những mô tả này có ưu tiên hơn các thuộc tính của thể hiện, điều này có nghĩa là các phương thức __get__() và __set__() của mô tả luôn được gọi, ngay cả khi một thuộc tính thể hiện với cùng tên tồn tại.
Dưới đây là ví dụ về một bộ mô tả dữ liệu đảm bảo rằng một thuộc tính luôn là một số nguyên và ghi lại các thao tác truy cập và sửa đổi −
class Integer: def __get__(self, instance, owner): print("Getting value") return instance._value def __set__(self, instance, value): print("Setting value") if not isinstance(value, int): raise TypeError("Value must be an integer") instance._value = value def __delete__(self, instance): print("Deleting value") del instance._value class MyClass: attr = Integer() # Usage obj = MyClass() obj.attr = 42 print(obj.attr) obj.attr = 100 print(obj.attr) del obj.attr
Output
Setting value Getting value 42 Setting value Getting value 100 Deleting value
Non-data descriptors là một loại mô tả trong Python chỉ định nghĩa phương thức __get__() . Khác với các mô tả dữ liệu, các mô tả không dữ liệu có thể bị ghi đè bởi các thuộc tính của thể hiện. Điều này có nghĩa là nếu có một thuộc tính thể hiện với cùng tên thì nó sẽ ưu tiên hơn so với mô tả không dữ liệu.
Dưới đây là một ví dụ về một mô tả không phải dữ liệu (non-data descriptor) cung cấp một giá trị mặc định nếu thuộc tính không được thiết lập trên thể hiện (instance) −
class Default: def __init__(self, default): self.default = default def __get__(self, instance, owner): return getattr(instance, '_value', self.default) class MyClass: attr = Default("default_value") # Usage obj = MyClass() print(obj.attr) obj._value = "Tutorialspoint" print(obj.attr)
Output
default_value Tutorialspoint
Hiểu sự khác biệt giữa Data Descriptors và Non-data Descriptors của các Descriptors trong Python là rất quan trọng để tận dụng hiệu quả các khả năng của chúng.
Criteria | Data Descriptors | Non-Data Descriptors |
---|---|---|
Definition | Implements both __get__(), __set__() methods, and the __delete__() method optionally. | Implements only __get__() method. |
Methods |
__get__(self, instance, owner)
__set__(self, instance, value) __delete__(self, instance) (optional) |
__get__(self, instance, owner) |
Precedence | Takes precedence over instance attributes. | Overridden by instance attributes. |
Use Cases |
Attribute validation and enforcement,
Managed attributes (e.g., properties), Logging attribute access and modification, Enforcing read-only attributes. |
Method binding,
Caching and, Providing default values.. |
Cuối cùng, chúng ta có thể nói rằng Descriptors trong Python cung cấp một cơ chế mạnh mẽ để quản lý việc truy cập và sửa đổi thuộc tính. Hiểu rõ sự khác biệt giữa các mô tả dữ liệu và mô tả không phải dữ liệu cũng như các trường hợp sử dụng phù hợp của chúng là điều cần thiết để tạo ra mã Python mạnh mẽ và dễ bảo trì.
Bằng cách tận dụng giao thức mô tả, các nhà phát triển có thể triển khai các hành vi nâng cao như kiểm tra kiểu, lưu trữ và thuộc tính chỉ đọc.