属性描述符(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
优先级规则
- 数据描述符 > 实例属性 > 非数据描述符。
- 若描述符和实例属性同名,数据描述符优先拦截访问,非数据描述符会被实例属性覆盖。
内置示例
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
完整调用顺序总结
- Data descriptor(类或基类中定义的
__get__和__set__) - 实例
__dict__(直接赋值的实例属性) - Non-data descriptor(类或基类中定义的仅
__get__) - 类或基类的
__dict__(普通类属性) __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
6148

被折叠的 条评论
为什么被折叠?



