直接操作实例__dict__无法添加描述符,因描述符协议仅在类层级生效;动态添加/移除必须用setattr/delattr修改类属性,或采用可开关的描述符设计。

直接操作 __dict__ 会失败,因为描述符逻辑在类层级
描述符(如 property、自定义 __get__/__set__ 类)的生效依赖于 Python 的属性查找链:实例 → 类 → 父类。当你尝试在运行时往实例的 __dict__ 里塞一个描述符对象,它根本不会被触发——Python 只有在类字典中找到该属性且它是描述符时,才会调用其协议方法。
所以不能写 obj.__dict__['x'] = MyDescriptor() 来“给实例加描述符”,这只会存成普通数据。
动态添加描述符只能修改类的 __dict__(或等价操作)
必须把描述符对象设为类属性,而非实例属性。最直接的方式是给类的 __dict__ 赋值,但注意:__dict__ 是只读的 mappingproxy,不能直接赋值。得用 setattr() 或直接操作类的 __dict__ 所属的底层字典(不推荐),更安全的是:
setattr(MyClass, 'cached_value', property(lambda self: self._raw * 2))- 若需带 setter,建议封装成函数再绑定:
def make_cached_prop(name): def getter(self): return getattr(self, '_' + name, None) def setter(self, val): setattr(self, '_' + name, val * 10) return property(getter, setter)setattr(MyClass, 'scale', make_cached_prop('scale'))
- 使用
types.FunctionType动态构造方法并绑定到类时,也要配合setattr,否则不会进入类的属性查找路径
移除描述符就是从类上删属性,但要注意继承污染
执行 delattr(MyClass, 'observed') 可以移除描述符,但它只影响当前类。如果子类没重定义该属性,删除后访问仍会向上查到父类(若父类还有),或者触发 AttributeError(若父类也没了)。
常见陷阱:
- 多个类共享同一基类,
delattr会影响所有未覆盖该属性的子类 - 用
MyClass.__dict__.pop('prop', None)不起作用——__dict__是只读 proxy,必须用delattr或setattr配合None(但设为None不等于移除,只是值变了) - 若描述符实现了
__delete__,调用del obj.attr是删实例级缓存,不是删描述符本身
真正“按需启用/禁用”描述符的实用模式
硬删类属性风险高,更可控的做法是让描述符自己响应开关状态:
class ToggleableProperty:
def __init__(self, func, enabled=True):
self.func = func
self.enabled = enabled
def __get__(self, obj, owner):
if obj is None:
return self
if self.enabled:
return self.func(obj)
raise AttributeError("property disabled")启用/禁用:
MyClass.toggle_prop.enabled = False
或设计成类方法控制全局开关
这种模式避免了修改类结构,也绕开了多线程下 delattr/setattr 的竞态问题。真正的动态性不在“有没有这个属性”,而在于它的行为是否可变。
描述符的动态性本质是类层级的元操作,不是实例数据操作;一旦涉及多继承或热重载,setattr 和 delattr 的副作用比看起来更隐蔽。










