Prompts-MCP 686 skills · ai / design / design-pattern / framework / fundamentals / habit / lang / tech-selection /mcp/sse
lang lang/python/oop/descriptor-protocol.md high

py-oop-descriptor-protocol

用描述符协议(__get__/__set__/__set_name__)把字段的校验或惰性计算抽成可复用对象——property、ORM 字段背后的机制。Use when 多个字段要复用同一套取值/校验逻辑 / 想理解 property 原理。

描述符descriptor__set_name____get__ __set__字段复用
paths
  • *.py
  • py/**/*.py

Python · 描述符协议

规则

dunder 触发时机 作用
__set_name__(self, owner, name) 类创建时自动调用 拿到自己被赋的属性名,免得手动传
__get__(self, obj, objtype) instance.attr 返回值;obj 为 None 时表示从类访问
__set__(self, obj, value) instance.attr = v 校验后存进实例(定义了它即“数据描述符”,优先级高于实例 __dict__

铁律:状态存进 obj.__dict__(每个实例独立),不要存在描述符实例上——描述符是类属性,所有实例共享一份,存上去会串数据。当一套校验逻辑要在多个字段重复时才上描述符;单个字段用 @property 更简单。

正例

class Positive:
    """可复用:任何需要“正数”校验的字段都挂它。"""

    def __set_name__(self, owner, name: str) -> None:
        self._name = f"_{name}"              # ✅ 自动得到字段名

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self                       # 从类访问返回描述符自身
        return getattr(obj, self._name)

    def __set__(self, obj, value: float) -> None:
        if value <= 0:
            raise ValueError(f"{self._name[1:]} must be > 0, got {value}")
        setattr(obj, self._name, value)       # ✅ 存进实例 __dict__,互不干扰


class Order:
    price = Positive()                        # 一行复用校验
    quantity = Positive()

Order().price = -5                            # ValueError: price must be > 0

@property 本身就是数据描述符——单字段够用时不必自造:

class Order:
    @property
    def total(self) -> float:                 # property == 描述符的便捷封装
        return self.price * self.quantity

反例

# ❌ 状态存在描述符实例上 → 类级共享,所有实例互相覆盖
class Positive:
    def __set__(self, obj, value):
        self.value = value                    # 应 setattr(obj, self._name, value)
    def __get__(self, obj, objtype=None):
        return self.value                     # 两个 Order 实例会读到同一个值

# ❌ 手动硬编码字段名 → 改名要改两处,易错
class Positive:
    def __init__(self, name):
        self._name = "_" + name               # 用 __set_name__ 自动拿即可

# ❌ 只有单个字段也上描述符 → 过度设计,@property 三行解决

理由:描述符是定义在上的对象,实例共享它;把数据存在 self(描述符)上必然串实例。__set_name__ 让描述符自动知道字段名,避免重复书写。它的价值是“一套逻辑复用到多个字段”,单字段场景 property 更轻。

自检

  • [ ] 状态用 setattr(obj, ...) 存进实例,没存在描述符自身?
  • [ ] 用 __set_name__ 自动获取字段名,没手动传字符串?
  • [ ] __get__ 处理了 obj is None(从类访问)的情况?
  • [ ] 确实是多字段复用同一逻辑才用描述符,单字段已改用 @property

相关