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 {}
问题原因
__set_name__执行时机错误:在Prueba类定义阶段直接打印描述符实例的__dict__,此时__set_name__还未执行(该方法在类完全创建后才会为每个描述符实例调用),所以输出空字典。- 无限递归导致内核崩溃:调用
setattr(obj, self.name, valor)时,会再次触发描述符的__set__方法,形成无限递归,耗尽栈空间导致Jupyter内核崩溃。 - 属性设置逻辑不完整:
__set__方法只处理了nombre属性的设置,apellido属性仅打印'Hola',未实际完成赋值,所以实例字典为空。 __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
相关产品推荐
相关产品推荐

