You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 07:10:02