python通过描述符协议识别property:定义了__get__、__set__或__delete__的类即为描述符,property类内置实现这三个方法;@property将函数包装为property实例并存入类属性字典,访问时触发__get__调用原getter。

property 是怎么被 Python 解释器识别的
Python 不是靠语法糖“猜”你写了 @property,而是靠描述符协议(descriptor protocol)。只要一个类定义了 __get__、__set__ 或 __delete__ 中的任意一个,它就是一个描述符。而 property 类本身就在内部实现了这三个方法。
所以当你写 @property 时,实际是把函数包装成一个 property 实例,并赋值给类属性——这个属性名从此就不再指向原函数,而是指向那个描述符对象。
- 类属性字典里存的是
property对象,不是你的 getter 函数 - 实例访问该属性时,触发的是
property.__get__(),它再调用你传进去的 getter 函数 - 如果你没定义
__set__,那对这个属性赋值就会走默认逻辑:直接往实例__dict__写,绕过 setter —— 这就是为什么没写 setter 却能赋值成功(但通常不是你想要的)
为什么 property 方法里的 self 指向实例而不是类
因为 property.__get__(instance, owner) 被调用时,解释器会把当前实例(或 None)作为第一个参数传进去。你在 getter 函数里写的 self,其实是 instance 的别名。
这和普通实例方法一致,但容易混淆的点在于:getter 看起来像类定义里的函数,但它运行时的绑定对象是实例,不是类。
立即学习“Python免费学习笔记(深入)”;
- 如果在 getter 里误用了类变量(比如
MyClass.count),要小心——它不会自动变成实例状态 - 若 getter 依赖其他实例属性,确保这些属性已在
__init__中初始化,否则可能触发AttributeError - 不推荐在 getter 里做重操作(如 IO、复杂计算),因为属性访问看起来无害,实则可能很慢
property 和显式 getter/setter 方法的区别不只是写法
表面上只是少写括号,底层行为差异直接影响封装性和调试路径。
调用 obj.value 和 obj.get_value() 在字节码层面完全不同:前者走属性查找链(__getattribute__ → 描述符协议),后者是普通方法调用。这意味着:
- 无法用
hasattr(obj, 'value')判断 property 是否存在(它总返回True,哪怕 getter 抛异常) - 继承时,子类覆盖父类
property必须重新定义整个property对象,不能只重写 getter;否则会丢失 setter/delete 行为 - 动态添加 property(如用
setattr(cls, 'x', property(...)))可行,但要注意时机:必须在类创建后、实例化前,否则已有实例不受影响
property 的性能开销在哪,什么情况下该避免用
每次访问 property 属性,都会多一层函数调用 + 描述符协议分发。对高频访问字段(比如循环内、数值计算中间量),这点开销会被放大。
更隐蔽的问题是缓存缺失:property 默认不缓存结果。如果你的 getter 是纯计算且结果稳定,重复调用等于重复劳动。
- 简单场景下(如返回
self._x * 2),性能差异可忽略;但涉及数据库查询、网络请求或递归计算时,务必评估 - 想缓存?别自己手写
_cached_value,优先考虑functools.cached_property(Python 3.8+),它专为这种场景设计,且线程安全 - 如果只是想做类型检查或范围限制,用
__setattr__配合__slots__可能比一堆 property 更轻量
真正难处理的,是那些既需要懒加载、又依赖外部状态、还要求线程安全的 property——这时候往往得跳出 property 范式,改用显式初始化或专用管理器。










