Python中的属性描述符,数据描述符和非数据描述符

属性描述符(Descriptors)

属性描述符是实现了特定协议(__get____set____delete__)的类,用于管理属性的访问。描述符分为数据描述符和非数据描述符,区别在于是否实现了__set__方法。

数据描述符(Data Descriptor)

数据描述符同时实现__get____set__方法,优先级高于实例属性。适用于需要控制属性读写行为的场景。

class DataDescriptor:
    def __get__(self, instance, owner):
        return f"DataDescriptor __get__: {instance._value}"
    
    def __set__(self, instance, value):
        instance._value = value * 2  # 修改赋值行为

class MyClass:
    attr = DataDescriptor()  # 类属性为数据描述符

obj = MyClass()
obj.attr = 10  # 触发 __set__
print(obj.attr)  # 输出: DataDescriptor __get__: 20

非数据描述符(Non-data Descriptor)

非数据描述符仅实现__get__方法,优先级低于实例属性。适用于只读属性或延迟计算场景。

class NonDataDescriptor:
    def __get__(self, instance, owner):
        return f"NonDataDescriptor __get__: {instance._value}"

class MyClass:
    attr = NonDataDescriptor()  # 类属性为非数据描述符

obj = MyClass()
obj._value = 10  # 直接操作实例属性
print(obj.attr)  # 输出: NonDataDescriptor __get__: 10
obj.attr = 20    # 覆盖描述符(变为实例属性)
print(obj.attr)  # 输出: 20

优先级规则

  1. 数据描述符 > 实例属性 > 非数据描述符。
  2. 若描述符和实例属性同名,数据描述符优先拦截访问,非数据描述符会被实例属性覆盖。

内置示例

Python内置的@property装饰器本质是数据描述符:

class MyClass:
    @property
    def attr(self):
        return self._value
    
    @attr.setter
    def attr(self, value):
        self._value = value + 1

obj = MyClass()
obj.attr = 5
print(obj.attr)  # 输出: 6

Python 属性访问顺序详解

在 Python 中,当访问一个实例的属性时,会遵循特定的查找顺序。以下是完整的属性访问机制:

Data Descriptor 数据描述符优先

如果 age 是定义在类 User 或其基类中的 data descriptor(实现了 __get____set__ 方法),则优先调用 descriptor 的 __get__ 方法。Data descriptor 的优先级高于实例字典。

class DataDescriptor:
    def __get__(self, obj, objtype):
        print("Data descriptor __get__")
        return 42
    def __set__(self, obj, value):
        print("Data descriptor __set__")

class User:
    age = DataDescriptor()  # data descriptor
实例字典检查

如果 age 不是 data descriptor,接下来检查实例的 __dict__ 中是否存在该属性。如果存在,直接返回实例字典中的值。

user = User()
user.__dict__['age'] = 10
print(user.age)  # 输出 10(如果没有 data descriptor)
Non-data Descriptor 或类字典

如果实例字典中没有 age,则检查类或基类的字典:

  • 如果是 non-data descriptor(仅实现 __get__),调用其 __get__ 方法。
  • 如果不是 descriptor,直接返回类字典中的值。
class NonDataDescriptor:
    def __get__(self, obj, objtype):
        print("Non-data descriptor __get__")
        return 42

class User:
    age = NonDataDescriptor()  # non-data descriptor
__getattr__ 兜底

如果以上步骤均未找到属性,且类定义了 __getattr__ 方法,则调用该方法作为兜底逻辑。

class User:
    def __getattr__(self, name):
        print(f"Fallback to __getattr__ for {name}")
        return 0

完整调用顺序总结

  1. Data descriptor(类或基类中定义的 __get____set__
  2. 实例 __dict__(直接赋值的实例属性)
  3. Non-data descriptor(类或基类中定义的仅 __get__
  4. 类或基类的 __dict__(普通类属性)
  5. __getattr__(如果定义)

示例验证

以下代码演示了完整的调用顺序:

class DataDescriptor:
    def __get__(self, obj, objtype):
        print("Data descriptor __get__")
        return 42
    def __set__(self, obj, value):
        pass

class NonDataDescriptor:
    def __get__(self, obj, objtype):
        print("Non-data descriptor __get__")
        return 24

class User:
    age = DataDescriptor()  # 第1步:data descriptor
    name = NonDataDescriptor()  # 第3步:non-data descriptor
    def __getattr__(self, name):
        print(f"Fallback to __getattr__ for {name}")
        return 0

user = User()
user.__dict__['age'] = 10  # 测试实例字典优先级(实际被 data descriptor 覆盖)
user.__dict__['name'] = "Alice"  # 第2步:实例字典优先于 non-data descriptor

print(user.age)  # 输出 Data descriptor __get__ → 42
print(user.name)  # 输出 "Alice"(实例字典优先)
print(user.unknown)  # 触发 __getattr__(如果定义)

#输出结果
Data descriptor __get__
42
Alice
Fallback to __getattr__ for unknown
0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值