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

Python描述符(Descriptor)行为异常及使用困惑求助

Python描述符问题解析与修复

问题描述

我为理解Python描述符写了示例代码,运行时Jupyter内核崩溃;修改nombre或apellido后输出空字典,虽然在__set__方法里能打印self.name,但描述符实例是空的,搞不懂该怎么用描述符简化属性。

问题代码

class Descriptor:
    
    
    def __set_name__(self,obj,name): 
        self.name=name
        
    def __set__(self,obj,valor): 
        
        if (self.name).title()=='Nombre':
            if type(valor)!=str:
                raise TypeError('No es un string')
            else:
                print(f'Estamos cambiando el atributo nombre de {obj} a {valor}')
                setattr(obj,self.name,valor) 
                
        else: print('Hola')
            
    def __get__(self,obj,owner):
        
        return getattr(obj,self.name)  
    
class Prueba:
    
    nombre=Descriptor()
    apellido=Descriptor()
    print(nombre.__dict__)
    print(apellido.__dict__)
    
    def __init__(self,nombre,apellido):
        self.nombre=nombre
        self.apellido=apellido
            
        
baz=Prueba('foo','bar')

print(baz.__dict__)

问题输出

{}
{}
Hola
Hola
{}

问题原因

  1. __set_name__执行时机错误:在Prueba类定义阶段直接打印描述符实例的__dict__,此时__set_name__还未执行(该方法在类完全创建后才会为每个描述符实例调用),所以输出空字典。
  2. 无限递归导致内核崩溃:调用setattr(obj, self.name, valor)时,会再次触发描述符的__set__方法,形成无限递归,耗尽栈空间导致Jupyter内核崩溃。
  3. 属性设置逻辑不完整:__set__方法只处理了nombre属性的设置,apellido属性仅打印'Hola',未实际完成赋值,所以实例字典为空。
  4. __get__方法存在报错风险:当实例未初始化对应属性时,getattr会抛出AttributeError,且同样可能触发不必要的调用循环。

修复后的代码

class Descriptor:
    def __set_name__(self, obj, name):
        # 用带下划线的私有属性存储,避免与公开属性名冲突,防止递归调用
        self._attr_name = f"_{name}"

    def __set__(self, obj, valor):
        # 通用字符串类型校验,让描述符可复用
        if not isinstance(valor, str):
            raise TypeError(f"{self._attr_name.lstrip('_')}必须是字符串类型")
        print(f"正在修改{obj}的属性{self._attr_name.lstrip('_')}为{valor}")
        # 赋值到私有属性,绕过描述符拦截
        setattr(obj, self._attr_name, valor)

    def __get__(self, obj, owner):
        if obj is None:
            # 类访问描述符时返回自身
            return self
        # 从私有属性取值,默认返回None避免报错
        return getattr(obj, self._attr_name, None)


class Prueba:
    nombre = Descriptor()
    apellido = Descriptor()

    def __init__(self, nombre, apellido):
        self.nombre = nombre
        self.apellido = apellido


baz = Prueba('foo', 'bar')
print(baz.__dict__)
# 测试属性修改
baz.nombre = 'Alice'
baz.apellido = 'Smith'
print(baz.nombre, baz.apellido)
print(baz.__dict__)

修复说明

  • 避免递归:将属性值存储到实例的私有属性(如_nombre、_apellido),调用setattr/getattr时不会触发描述符的__set__/__get__,彻底解决递归问题。
  • 合理利用__set_name__:类创建完成后,__set_name__会自动绑定对应的私有属性名,此时描述符实例的__dict__会包含_attr_name。
  • 通用化逻辑:不再针对特定属性名做判断,让描述符可以复用在多个属性上,实现“用描述符简化属性”的需求。
  • 兼容类访问场景:当通过类(如Prueba.nombre)访问描述符时,返回描述符实例本身,避免报错。

内容的提问来源于stack exchange,提问作者Jaime Yepes de Paz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 12:07:42