Python 简明教程

Python - Descriptors

Python Descriptors

Python Descriptors 是一种定制对象属性的访问、赋值和删除的方式。它们通过定义获取、设置和删除属性值的方法,提供一个管理属性行为的强大机制。描述符经常用于实现属性、方法和属性验证。

descriptor 是至少实现 getsetdelete 中的一种方法的任何对象。这些方法用于控制属性值的访问和修改方式。

How Python Descriptors Work?

当访问某个实例上的某个属性时,Python 会查找该实例的 class 中的该属性。如果找到了该属性,并且它是一个描述符,那么 Python 将调用相应的描述符方法代替简单地返回该属性的值。这将允许描述符控制属性访问期间所发生的事情。

描述符协议是一种低级机制,在 Python 的许多高级功能中,例如属性、方法、静态方法和类方法中使用。描述符可以用来实现懒加载、类型检查和计算属性等模式。

Descriptor Methods

Python Descriptors 包含三个主要方法,即 get ()、 set () 和 delete 。正如我们之前所讨论的,这些方法分别控制属性访问、赋值和删除的行为。

1. The get() Method

描述符中的 get() method 是 Python 中描述符协议的关键部分。它被调用用于从实例或类中检索属性值。理解 get() method 的工作方式对于创建可以以复杂的方式管理属性访问的自定义描述符至关重要。

以下为 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.
   """

下面是该方法的参数:

  1. self: The descriptor instance.

  2. instance: 访问该属性的类的实例。如果通过类而不是实例访问该属性,则它为无。

  3. owner: 拥有该描述符的类。

以下为 get () 方法的简单示例,其中它在访问 obj.attr 时返回存储的值 _value

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 是 Python 中描述符协议的一部分,它用于控制设置属性值的行为。当由描述符管理的属性被赋值新值时, set() method 将被调用,从而允许用户定制或强制执行赋值规则。

以下是 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.
    """

下面是该方法的参数:

  1. self: The descriptor instance.

  2. instance: 设置该属性的类的实例。

  3. value: 分配给属性的值。

以下是 set () 方法的基本示例,它确保分配给 attr 的值是一个整数 -

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 允许我们控制当属性从实例中删除时发生的事情。这对于在删除属性时管理资源、清理或执行约束很有用。

以下是 Python Descriptor delete() method 的语法 -

def __delete__(self, instance):
    """
    instance: the instance of the class from which the attribute is being deleted.
    """

下面是该方法的参数:

  1. self: The descriptor instance.

  2. instance: 删除属性的类的实例。

以下是 set () 方法的基本示例,它确保分配给 attr 的值是一个整数 -

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

在 Python 中, descriptors 可以根据它们实现的方法大致分为两种类型。它们是 -

  1. Data Descriptors

  2. Non-data Descriptors

让我们详细了解两种类型的 python 描述符,以便更好地理解。

1. Data Descriptors

Data descriptors 是 Python 中的一种描述符类型,它定义了 get()*and *set() 方法。这些描述符优先于实例属性,这意味着描述符的 get()*and *set() 方法总是被调用,即使存在具有相同名称的实例属性。

下面是一个数据描述符的示例,它确保属性始终是整数,并记录访问和修改操作 -

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 是 Python 中的一种描述符类型,它只定义 get() 方法。与数据描述符不同,非数据描述符可以被实例属性覆盖。这意味着如果存在具有相同名称的实例属性,则它将优先于非数据描述符。

以下是一个非数据描述符的示例,如果属性未在实例中设置,则它提供一个默认值 -

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

理解 Data DescriptorsNon-data Descriptors 之间的差异对于有效利用它们的这些功能至关重要。

最后我们可以说 Descriptors 在 Python 中提供了一个强大的机制来管理属性访问和修改。理解数据描述符和非数据描述符之间的差异及其适当的用例对于创建鲁棒且可维护的 Python 代码至关重要。

通过利用描述符协议,开发人员可以实现高级行为,如类型检查、缓存和只读属性。