Python中的描述符与属性访问顺序
字数 808 2025-11-08 23:27:58
Python中的描述符与属性访问顺序
描述符是Python中一个强大的特性,它允许对象自定义属性访问的行为。理解描述符如何与常规属性访问规则交互至关重要。
1. 描述符协议基础
描述符是实现了特定协议方法的类,这些方法包括:
__get__(self, obj, type=None): 获取属性值时调用__set__(self, obj, value): 设置属性值时调用__delete__(self, obj): 删除属性时调用
根据实现的协议方法,描述符分为两类:
- 数据描述符:实现了
__set__或__delete__方法 - 非数据描述符:只实现了
__get__方法
2. 属性访问的优先级顺序
当通过实例访问属性时,Python按照以下顺序查找:
- 数据描述符优先级最高
- 如果该属性是类中的数据描述符,直接调用描述符的
__get__方法 - 数据描述符会覆盖实例字典中的同名属性
- 如果该属性是类中的数据描述符,直接调用描述符的
class DataDescriptor:
def __get__(self, obj, type=None):
return "来自数据描述符的值"
def __set__(self, obj, value):
pass
class MyClass:
attr = DataDescriptor() # 数据描述符
obj = MyClass()
obj.__dict__['attr'] = '实例字典中的值'
print(obj.attr) # 输出:来自数据描述符的值
3. 实例字典查找
- 实例字典查找
- 如果属性不是数据描述符,查找实例的
__dict__ - 这是最常见的属性访问情况
- 如果属性不是数据描述符,查找实例的
class MyClass:
pass
obj = MyClass()
obj.attr = '实例属性值'
print(obj.attr) # 输出:实例属性值
4. 非数据描述符查找
- 非数据描述符查找
- 如果类中有同名的非数据描述符,调用其
__get__方法 - 非数据描述符的优先级低于实例属性
- 如果类中有同名的非数据描述符,调用其
class NonDataDescriptor:
def __get__(self, obj, type=None):
return "来自非数据描述符的值"
class MyClass:
attr = NonDataDescriptor() # 非数据描述符
obj = MyClass()
print(obj.attr) # 输出:来自非数据描述符的值
obj.attr = '实例属性值' # 现在实例字典中有该属性
print(obj.attr) # 输出:实例属性值(覆盖了非数据描述符)
5. 类属性查找
- 类属性查找
- 如果在实例和描述符中都没找到,查找类属性
- 包括类变量、方法等
class MyClass:
class_attr = '类属性值'
obj = MyClass()
print(obj.class_attr) # 输出:类属性值
6. 继承链查找
- 继承链查找
- 如果在当前类中没找到,按照MRO顺序在父类中查找
- 包括父类中的描述符、类属性等
7. 完整的查找顺序总结
属性访问的完整优先级顺序为:
- 数据描述符(最高优先级)
- 实例字典(
obj.__dict__) - 非数据描述符
- 类属性(
cls.__dict__) - 父类继承链(按照MRO)
__getattr__方法(如果定义)
8. 实际应用示例
class DataDescriptor:
def __get__(self, obj, type=None):
return "数据描述符"
def __set__(self, obj, value):
print("设置数据描述符")
class NonDataDescriptor:
def __get__(self, obj, type=None):
return "非数据描述符"
class Example:
data_desc = DataDescriptor() # 数据描述符
non_data_desc = NonDataDescriptor() # 非数据描述符
class_attr = "类属性" # 普通类属性
# 测试访问顺序
obj = Example()
# 1. 数据描述符优先
print(obj.data_desc) # 输出:数据描述符
# 2. 实例属性覆盖非数据描述符
print(obj.non_data_desc) # 输出:非数据描述符
obj.non_data_desc = "实例属性"
print(obj.non_data_desc) # 输出:实例属性
# 3. 类属性访问
print(obj.class_attr) # 输出:类属性
理解这个优先级顺序对于编写高级Python代码、设计框架和调试属性相关的问题非常重要。