Python 描述符与属性访问

描述符协议驱动 property/方法绑定与属性校验;理解 get/set/delete 与查找顺序。

#type / concept #status / growing #tech / dev #resource / python #tech / lang / python

[!info] 关联笔记

Python 描述符与属性访问

这个概念为什么出现

@property、方法绑定、ORM 字段——底层多是描述符。不懂描述符,属性“读的是类上的函数却变成方法”会像魔法。

[!abstract] 一句话理解 描述符是实现 __get__/__set__/__delete__ 的对象;放在类上时介入属性访问,property 是其应用。

最小可运行示例

先把示例放进业务场景,再看代码:

场景:单价必须非负的属性校验

# price_descriptor_demo.py
# 业务意图:用 property 校验单价。
# 教学点:描述符应用;读写分离。

class LineItem:
    def __init__(self, sku: str, unit_price_cents: int) -> None:
        self.sku = sku
        self.unit_price_cents = unit_price_cents

    @property
    def unit_price_cents(self) -> int:
        return self._unit_price_cents

    @unit_price_cents.setter
    def unit_price_cents(self, value: int) -> None:
        if value < 0:
            raise ValueError("price must be >= 0")
        self._unit_price_cents = value


def main() -> None:
    item = LineItem("SKU-1", 100)
    print(item.unit_price_cents)
    try:
        item.unit_price_cents = -1
    except ValueError as exc:
        print("blocked:", exc)


if __name__ == "__main__":
    main()

建议运行:

python price_descriptor_demo.py

期望输出:

100
blocked: price must be >= 0

结合场景再看三个关注点

  1. 赋值走 setter
  2. 存储用 _unit_price_cents 避免递归。
  3. 函数放在类上经描述符变成绑定方法。

核心概念与准确模型

  • 数据描述符 vs 非数据描述符
  • 查找顺序:数据描述符优先于实例 dict
  • property/classmethod/staticmethod/functions

边界情况与反直觉行为

  1. 只定义 __get__ 的非数据描述符可被实例 dict 遮蔽
  2. __getattribute__ 重写极危险
  3. slots + 描述符 组合细节

常见误区

[!warning] 常见误区:为一切字段写 property 错误理解:封装必须样板。
正确模型:有不变量再引入。

工程实践

  • 公开不变量用 property/描述符
  • 框架作者才常写通用描述符
  • 文档化副作用

本节总结

描述符是属性系统的机制核心。日常用 property;读库时能认出协议。

自测题

  1. property 是什么?
  2. 为何 setter 写入 _x
参考答案
  1. 描述符的便捷封装。
  2. 避免再次触发 setter 造成递归。

延伸阅读与资料来源

资料类型支撑内容
Descriptor HowToHOWTO权威
创建于 2026/7/15 更新于 2026/7/15