描述符必须通过__get__、__set__或__delete__接管属性行为,核心是用方法控制访问逻辑而非直接存取value;需类型检查、范围限制、自动转换、延迟计算或日志通知时应使用描述符;值应存于实例__dict__而非描述符自身以避免共享;__get__中instance为none时表示类访问,应返回描述符对象;数据描述符(含__set__)优先于实例属性,非数据描述符则被实例属性屏蔽。

描述符不是“用 value 还是用 get/set”的选择题,而是必须通过 __get__ 和 __set__(或 __delete__)来接管属性行为。所谓 “value” 是普通属性直接存取的值,而描述符的核心价值恰恰在于不让你直接碰 value,而是用方法控制怎么读、怎么写、怎么删。
什么时候该写描述符而不是直接设 value?
当你需要在属性访问时插入逻辑,比如:
- 类型检查:赋值前验证是不是 int/str/正数等
- 范围限制:年龄不能负数、分数不能超 100
- 自动转换:把输入字符串转成日期再存
- 延迟计算:属性值依赖其他字段,每次 get 才算一次
- 日志或通知:每次修改都记录日志或触发回调
为什么不能把值存在描述符自己身上?
如果在描述符实例里存 value(比如 self._value = value),那所有使用这个描述符的实例都会共享同一个值——这通常不是你想要的。正确做法是把值存到实例的 __dict__ 里,例如:
obj.__dict__[self.name] = value(配合 __set_name__ 自动记下属性名)。
__get__ 里 instance 为 None 是什么情况?
当通过类访问属性时发生,比如 Person.age 而不是 p.age。这时应返回描述符对象本身(方便做类级操作或文档说明),而不是报错或硬返回某个值。这是支持类访问和实例访问统一接口的关键细节。
数据描述符 vs 非数据描述符,影响访问优先级
只要实现了 __set__(或 __delete__),就是数据描述符,它会覆盖实例字典中同名属性;只实现 __get__ 的非数据描述符,则会被实例属性屏蔽。所以如果你写了 __set__ 却发现赋值没走它,先检查是不是不小心在实例上直接设置了同名属性(如 p._age = 99),那后续 p.age 就不会触发描述符了。











