在 Python 的对象模型中,描述符(descriptor)并不是一种对象类型,也不是独立存在的实体,而是对象在属性解析机制中可能承担的一种运行期语义角色。

只有实现了描述符协议、并被绑定在类属性位置上的对象,才会在运行期扮演描述符语义角色。

在这一语义框架下,所谓“数据描述符”与“非数据描述符”,也不是对象的分类,而是描述符协议在属性解析机制中的两种优先级形态。

一、描述符协议与描述符语义

描述符协议由以下方法构成,对象只需实现其中一个或多个,即可能参与属性解析过程。

__delete__(self, instance)          # 参与属性删除解析

当类命名空间中的某个属性对象实现了其中一个或多个方法时,并被绑定在类属性位置上,描述符协议就会在属性解析过程中介入。

根据实现方法的不同,描述符语义角色可分为两类:

1、数据描述符(Data Descriptor)

实现了 __set__() 或 __delete__()(通常也实现 __get__())。

2、非数据描述符(Non-Data Descriptor)

只实现了 __get__(),未实现 __set__() 与 __delete__()。

这一划分并非概念标签,而是直接决定了属性解析时的优先级规则。

二、数据描述符

数据描述符语义的核心特征是:

• 在属性解析顺序中优先于实例 __dict__

• 实例无法通过同名属性绕过其控制

因此,数据描述符表达了强约束属性的语义。

示例:属性验证

p.price = -5            # ValueError

在上述示例中,在脱离属性解析上下文时,Positive() 与任何普通实例对象并无区别。由于它实现了 __get__ 与 __set__ 且被绑定在 Product.__dict__['price'] 上,因此在属性访问解析中承担数据描述符语义。

即使实例 p.__dict__ 中存在同名键,属性解析仍会优先遵循数据描述符语义,因此无法绕过该描述符的控制。

这不是“特殊规则”,而是属性设置与访问机制对描述符协议的明确约定。

三、非数据描述符

非数据描述符语义的核心特征是:

• 在属性解析顺序中低于实例 __dict__

• 可被实例同名属性遮蔽

非数据描述符的语义更像是一种默认访问行为,可被实例属性覆盖的兜底逻辑。

示例:可被实例遮蔽的读取逻辑

print(d.attr)           # 实例属性

这里并不是描述符“失效”,而是属性解析顺序中,实例 __dict__ 的优先级高于非数据描述符语义。

四、属性解析顺序与优先级(机制视角)

在不重写 __getattribute__ 的前提下,obj.attr 的简化解析顺序可以概括为:

1、类命名空间中的数据描述符语义

2、实例 obj.__dict__

3、类命名空间中的非数据描述符语义

4、类命名空间中的普通属性

5、若仍失败,进入 __getattr__ 补救路径

需要再次强调,这里区分的不是“对象类型”,而是对象在属性解析机制中所承担的语义角色。

五、典型应用场景

1、数据描述符

• property(受控读取与写入)

• ORM 字段(Django / SQLAlchemy)

• 属性校验、计算属性、只读属性

示例:property

property 并不是“魔法属性”,而是一个在属性位置上承担数据描述符语义的对象。

2、非数据描述符

• 函数对象(方法绑定)

• staticmethod

• classmethod

示例:staticmethod

print(u.add)            # 被覆盖了

这里体现的并不是 staticmethod 的接口约束能力,而是非数据描述符语义在属性解析顺序中的既定位置。

六、补充说明与边界澄清

1、描述符不是安全机制

描述符并不阻止绕过约束:

obj.__dict__["_value"] = -999

这种行为始终是可能的。

描述符的目标是语言级一致性与约束表达,而非防御性安全。

2、描述符逻辑的复用方式

描述符语义通常通过普通类来实现,并在不同宿主类中分别创建对象实例,绑定到属性位置,从而复用同一套属性管理逻辑。这是框架设计中的核心能力之一。

3、调试与理解建议

当属性行为不符合预期时,优先检查:

cls.__dict__

理解“对象 + 位置 + 协议”的组合,通常能迅速定位问题。

小结

数据描述符与非数据描述符并不是对象类型的区分,而是描述符协议在属性解析机制中的两种优先级语义。实现了写入或删除协议的对象,在类属性位置上承担数据描述符语义,从而获得对属性行为的控制权;仅实现读取协议的对象,则承担可被实例遮蔽的非数据描述符语义。

理解二者的差异,是理解 Python 属性机制、方法绑定与框架设计的基础。

打开网易新闻 查看精彩图片

点赞有美意,赞赏是鼓励