Python中的属性描述符与描述符协议
字数 1450 2025-12-05 09:57:24

Python中的属性描述符与描述符协议

描述符是Python中一种高级特性,它允许对象自定义属性访问行为。描述符协议由三个特殊方法组成:__get____set____delete__。任何实现了至少其中一个方法的类,都可以称为描述符。描述符常用于实现属性验证、延迟计算、数据绑定等功能。


1. 描述符的基本概念

  • 描述符:是一个类,其实例被另一个类的类属性所引用,并通过描述符协议控制对该属性的访问。
  • 描述符类型
    • 数据描述符:实现了 __set____delete__ 方法(通常同时实现 __get__)。
    • 非数据描述符:只实现了 __get__ 方法。
  • 关键点:数据描述符的优先级高于实例字典,而非数据描述符的优先级低于实例字典。

2. 描述符协议方法详解

a) __get__(self, instance, owner)

  • 当从实例或类访问描述符属性时调用。
  • 参数:
    • self:描述符实例本身。
    • instance:访问属性的实例。如果通过类访问,则为 None
    • owner:拥有该描述符的类。
  • 返回值:可以是任意值,通常与属性访问的预期类型一致。

b) __set__(self, instance, value)

  • 当对描述符属性赋值时调用。
  • 参数:
    • self:描述符实例。
    • instance:操作属性的实例。
    • value:要设置的值。
  • 无返回值。

c) __delete__(self, instance)

  • 当删除描述符属性(使用 del)时调用。
  • 参数:
    • self:描述符实例。
    • instance:操作属性的实例。
  • 无返回值。

3. 创建描述符的步骤

步骤1:定义描述符类
假设我们创建一个验证正整数的描述符:

class PositiveInteger:
    def __get__(self, instance, owner):
        # 返回存储的值
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        if not isinstance(value, int) or value <= 0:
            raise ValueError("必须为正整数")
        # 将值存储在实例字典中
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

步骤2:在目标类中使用描述符

class Product:
    # 类属性为描述符实例
    quantity = PositiveInteger()
    price = PositiveInteger()

    def __init__(self, name, quantity, price):
        self.name = name
        self.quantity = quantity
        self.price = price

但此时 PositiveInteger 中的 self.name 未定义。我们需要让描述符知道它被赋值的属性名。

步骤3:添加属性名绑定
通常通过元类或 __set_name__ 方法(Python 3.6+)实现:

class PositiveInteger:
    def __set_name__(self, owner, name):
        # 设置描述符对应的属性名
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        if not isinstance(value, int) or value <= 0:
            raise ValueError("必须为正整数")
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

现在 Product 类可以正常工作:

p = Product("apple", 10, 5)  # 有效
p.quantity = 20              # 有效
p.quantity = -5              # 抛出 ValueError

4. 数据描述符 vs 非数据描述符

  • 数据描述符:实现了 __set____delete__
    示例:上面的 PositiveInteger 是数据描述符。
  • 非数据描述符:只实现 __get__
    示例:@property 装饰器创建的属性是非数据描述符(除非定义了 setter)。

优先级测试

class DataDescriptor:
    def __get__(self, instance, owner):
        return "数据描述符"
    def __set__(self, instance, value):
        pass

class NonDataDescriptor:
    def __get__(self, instance, owner):
        return "非数据描述符"

class Test:
    data = DataDescriptor()
    non_data = NonDataDescriptor()

obj = Test()
obj.__dict__['data'] = '实例属性'
obj.__dict__['non_data'] = '实例属性'

print(obj.data)       # 输出:数据描述符(数据描述符优先)
print(obj.non_data)   # 输出:实例属性(非数据描述符优先级低)

5. 描述符的应用场景

  1. 属性验证:如上面的 PositiveInteger
  2. 延迟计算:将耗时计算的结果缓存。
  3. 数据绑定:在GUI框架中自动更新界面。
  4. 实现 @property:property 本身是基于描述符实现的装饰器。

6. 描述符与 property 的关系

@property 是描述符的语法糖。以下两种方式等价:

# 使用 property
class MyClass:
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value

# 使用描述符(近似实现)
class MyClass:
    x = property(lambda self: self._x, lambda self, v: setattr(self, '_x', v))

property 是一个内置的数据描述符类。


7. 注意事项

  • 描述符实例是类属性,所有实例共享同一个描述符对象。
  • 通过 __dict__ 存储实例数据,避免描述符自身的数据在实例间共享。
  • 使用 __set_name__ 简化属性名绑定(Python 3.6+)。
  • 数据描述符优先级高,可能覆盖实例字典中的同名属性。

通过以上步骤,你可以理解描述符如何工作,并能够自定义描述符来实现高级属性控制。

Python中的属性描述符与描述符协议 描述符是Python中一种高级特性,它允许对象自定义属性访问行为。描述符协议由三个特殊方法组成: __get__ 、 __set__ 和 __delete__ 。任何实现了至少其中一个方法的类,都可以称为描述符。描述符常用于实现属性验证、延迟计算、数据绑定等功能。 1. 描述符的基本概念 描述符 :是一个类,其实例被另一个类的类属性所引用,并通过描述符协议控制对该属性的访问。 描述符类型 : 数据描述符 :实现了 __set__ 或 __delete__ 方法(通常同时实现 __get__ )。 非数据描述符 :只实现了 __get__ 方法。 关键点 :数据描述符的优先级高于实例字典,而非数据描述符的优先级低于实例字典。 2. 描述符协议方法详解 a) __get__(self, instance, owner) 当从实例或类访问描述符属性时调用。 参数: self :描述符实例本身。 instance :访问属性的实例。如果通过类访问,则为 None 。 owner :拥有该描述符的类。 返回值:可以是任意值,通常与属性访问的预期类型一致。 b) __set__(self, instance, value) 当对描述符属性赋值时调用。 参数: self :描述符实例。 instance :操作属性的实例。 value :要设置的值。 无返回值。 c) __delete__(self, instance) 当删除描述符属性(使用 del )时调用。 参数: self :描述符实例。 instance :操作属性的实例。 无返回值。 3. 创建描述符的步骤 步骤1:定义描述符类 假设我们创建一个验证正整数的描述符: 步骤2:在目标类中使用描述符 但此时 PositiveInteger 中的 self.name 未定义。我们需要让描述符知道它被赋值的属性名。 步骤3:添加属性名绑定 通常通过元类或 __set_name__ 方法(Python 3.6+)实现: 现在 Product 类可以正常工作: 4. 数据描述符 vs 非数据描述符 数据描述符 :实现了 __set__ 或 __delete__ 。 示例:上面的 PositiveInteger 是数据描述符。 非数据描述符 :只实现 __get__ 。 示例: @property 装饰器创建的属性是非数据描述符(除非定义了 setter)。 优先级测试 : 5. 描述符的应用场景 属性验证 :如上面的 PositiveInteger 。 延迟计算 :将耗时计算的结果缓存。 数据绑定 :在GUI框架中自动更新界面。 实现 @property :property 本身是基于描述符实现的装饰器。 6. 描述符与 property 的关系 @property 是描述符的语法糖。以下两种方式等价: property 是一个内置的数据描述符类。 7. 注意事项 描述符实例是 类属性 ,所有实例共享同一个描述符对象。 通过 __dict__ 存储实例数据,避免描述符自身的数据在实例间共享。 使用 __set_name__ 简化属性名绑定(Python 3.6+)。 数据描述符优先级高,可能覆盖实例字典中的同名属性。 通过以上步骤,你可以理解描述符如何工作,并能够自定义描述符来实现高级属性控制。