如何让类属性成为描述符且支持 setter/deleter

2026-01-27 00:00:00 作者:舞夢輝影
描述符类必须实现__set__和__delete__方法才能支持赋值与删除;仅含__get__为只读描述符。其必须作为类变量实例化(如attr = MyDescriptor()),且__set__需存值到WeakKeyDictionary或实例__dict__中以隔离状态,避免共享或内存泄漏。

描述符类必须实现 __set____delete__ 方法

仅定义 __get__ 的类是只读描述符;要支持赋值和删除,__set__(self, obj, value)__delete__(self, obj) 二者缺一不可。Python 在对实例属性赋值时,若发现同名类属性是描述符且实现了 __set__,就会跳过实例字典、直接调用该方法——这是描述符生效的前提。

常见错误是漏写 __set__ 却以为 @property 那套逻辑能复用;描述符协议和 @property 无关,后者只是内置描述符的语法糖,不能直接混用。

  • __set__ 必须接收三个参数:描述符实例、托管对象(即使用该属性的实例)、待赋的值
  • __delete__ 必须接收两个参数:描述符实例、托管对象
  • 若未实现 __set__,对该属性赋值会直接在实例上新建同名属性,覆盖描述符——此时 __get__ 后续也不再被调用

在类中声明描述符属性时不能加括号

描述符必须作为类变量(class variable)存在

,且必须是描述符类的实例,而非类本身。写成 attr = MyDescriptor() 是对的,写成 attr = MyDescriptor(没调用)或 attr = MyDescriptor() 放在 __init__ 里,都会失效。

典型误用场景:想给每个实例配独立描述符,结果把描述符实例化放到了 __init__ 中——这会让它变成普通实例属性,完全不触发描述符协议。

  • ✅ 正确:
    class MyClass:
        attr = MyDescriptor()  # 类变量,实例化一次
  • ❌ 错误:
    class MyClass:
        def __init__(self):
            self.attr = MyDescriptor()  # 实例属性,不触发协议
  • ❌ 错误:
    class MyClass:
        attr = MyDescriptor  # 没调用,只是类引用

setter/deleter 逻辑需自行管理底层存储位置

描述符本身不自动绑定到实例数据;你得决定值存哪:可以存在描述符实例里(共享)、实例的 __dict__ 里(隔离)、或用 weakref.WeakKeyDictionary 做映射。多数情况推荐后者,避免循环引用和内存泄漏。

如果把值直接存进描述符自己的属性(如 self._value),那所有托管实例将共享这个值——这通常不是想要的行为。

  • 推荐方式:用 weakref.WeakKeyDictionary 映射实例 → 值,确保实例销毁后自动清理
  • 简单替代:存入托管实例的 __dict__,但需约定键名(如 _mydescriptor_attr),防止命名冲突
  • 不推荐:存在描述符自身属性中,除非明确需要类级别共享状态

注意 __set__ 中对 None 或非法值的处理时机

描述符的 __set__ 是唯一可控的赋值入口,也是做类型校验、范围限制、事件通知的最佳位置。但要注意:如果在 __set__ 中抛异常(如 ValueError),赋值操作会立即失败,不会写入任何地方——这点比 @property 更严格,也更可靠。

容易忽略的是 del obj.attr 触发 __delete__ 后,若后续又访问 obj.attr__get__ 仍会被调用,但此时底层存储可能已清空。你需要在 __get__ 中判断值是否存在,否则可能抛 AttributeError 或返回意外默认值。

  • __set__ 中校验并规范化输入值(例如转为 int、截断字符串)
  • __delete__ 中清除对应存储,不要留空洞状态
  • __get__ 开头检查值是否存在,缺失时明确抛错或返回 sentinel(如 NotImplemented),别依赖隐式行为

描述符的 setter/deleter 看似只是多写两个方法,实际难点在于存储策略的选择和生命周期管理——特别是跨实例状态隔离与自动清理,这里出错往往没有明显报错,而是数据静默错乱。

猜你喜欢

联络方式:

400 9058 355

邮箱:8955556@qq.com

Q Q:8955556

微信二维码
在线咨询 拨打电话

电话

400 9058 355

微信二维码

微信二维码