Python - Descriptors

Python Descriptors

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.

How Python Descriptors Work?

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).

Descriptor Methods

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.

1. The __get__() Method

__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.

Syntax

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.
   """

Parameters

Dưới đây là các tham số của phương thức này −

  • self: The descriptor instance.
  • instance: The instance of the class where the attribute is accessed. It is None when the attribute is accessed through the class rather than an instance.
  • owner: The class that owns the descriptor.

Example

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

2. The __set__() Method

__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ị.

Syntax

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.
    """

Parameters

Dưới đây là các tham số của phương thức này −

  • self: The descriptor instance.
  • instance: The instance of the class where the attribute is being set.
  • value: The value being assigned to the attribute.

Example

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>

3. The __delete__() Method

__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ỏ.

Syntax

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.
    """

Parameters

Dưới đây là các tham số của phương thức này −

  • self: The descriptor instance.
  • instance: The instance of the class where the attribute is being deleted.

Example

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

Types of Python Descriptors

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à −

  • Data Descriptors
  • Non-data Descriptors

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.

1. Data Descriptors

Data descriptors là một loại mô tả trong Python định nghĩa cả phương thức __get__() __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__() __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.

Example

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

2. Non-data Descriptors

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.

Example

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

Data Descriptors Vs. Non-data Descriptors

Hiểu sự khác biệt giữa Data Descriptors 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.