Python描述符与类中同名属性引发无限循环的原因探究
为什么描述符存储属性名不能和描述符实例同名?
问题本质:描述符触发逻辑导致无限递归
先对比两种场景的代码逻辑:
正常工作的官方示例代码
class AgeDescriptor: def __get__(self, obj, objtype=None): return obj._age def __set__(self, obj, value): if value < 0: raise ValueError("年龄不能为负数") obj._age = value # 存储到_age,与描述符实例名age不同 class Person: age = AgeDescriptor() # 描述符实例名为age
当执行p = Person(); p.age = 20时,Python会识别到Person.age是数据描述符(实现了__set__方法),因此直接调用AgeDescriptor.__set__,将值存入对象私有字典__dict__中的_age字段——这一步操作不会触发描述符逻辑,所以运行正常。
引发无限循环的错误代码
如果把__set__里的obj._age = value改成obj.age = value:
class AgeDescriptor: def __set__(self, obj, value): if value < 0: raise ValueError("年龄不能为负数") obj.age = value # 尝试给age赋值,再次触发描述符逻辑
此时给p.age赋值时,Python先调用描述符的__set__方法;而在__set__内部执行obj.age = value时,Python会再次检测到Person.age是数据描述符,于是又调用__set__方法,最终形成无限递归循环,直到栈溢出报错。
和描述符实例名称的关系
这个问题完全和描述符实例的名称相关:
- 描述符的核心触发逻辑是:当访问/设置类中某一名称的属性时,若该属性是描述符实例,Python会优先执行描述符的
__get__/__set__方法,而非直接操作对象的__dict__。 - 若存储属性的名称和描述符实例名称一致(比如都是
age),那么在__set__中对该名称赋值时,必然会再次触发描述符的__set__方法,形成循环。
简言之,描述符实例的名称是Python触发其逻辑的“开关”,存储属性必须避开这个名称,否则会触发自递归。
内容的提问来源于stack exchange,提问作者Roviostar
相关产品推荐
相关产品推荐

