property 是描述符而非语法糖,其本质是通过 get__、__set__、__delete 方法实现属性访问控制,必须定义在类级别,遵循描述符协议,且 getter/setter 调用返回新 property 实例。

Python 的 property 不是语法糖,而是一个描述符(descriptor)的典型应用,其核心在于绑定到类属性上的 __get__、__set__ 和 __delete__ 方法被自动触发。 它让方法调用看起来像普通属性访问,但背后有明确的控制逻辑和执行时机,理解这一点才能避免常见陷阱(比如在 __init__ 中误用、或混淆实例与类级别的行为)。
property 是描述符,不是装饰器本身
虽然常用 @property 语法,但 property 是一个内置类,其实例是“数据描述符”。当它被赋值给类属性时(如 name = property(getter, setter)),就满足了描述符协议:拥有 __get__ 等方法。Python 在属性查找时,会优先检查类字典中该名称是否为描述符;如果是,就跳过实例字典,直接调用其 __get__。
- 访问
obj.attr时,若attr是 property,则调用attr.__get__(obj, type(obj)) - 赋值
obj.attr = val触发attr.__set__(obj, val)(前提是定义了 setter) - 删除
del obj.attr触发attr.__delete__(obj) - 如果未定义 setter,尝试赋值会抛出
AttributeError,因为默认__set__实现直接报错
getter/setter/deleter 方法如何绑定到同一个 property 对象
@prop.getter、@prop.setter 并非重新定义新对象,而是返回一个新的 property 实例,复用了原对象的其他方法,并替换了对应函数。例如:
class C:
def __init__(self, x):
self._x = x
<pre class='brush:python;toolbar:false;'>@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
立即学习“Python免费学习笔记(深入)”;
等价于:
class C:
def __init__(self, x):
self._x = x
<pre class='brush:python;toolbar:false;'>def _get_x(self):
return self._x
def _set_x(self, value):
self._x = value
x = property(_get_x)
x = x.setter(_set_x) # 返回新 property,含 getter + setter
立即学习“Python免费学习笔记(深入)”;
- 每次
.setter或.getter调用都生成新property对象,因此不能链式多次调用同一对象的 setter(会覆盖) - 底层靠
property构造时传入的fget、fset、fdel参数保存函数引用 - 这些函数在实例上调用时,自动接收
self(即访问该属性的实例)
property 只作用于类级别,对实例无效
property 必须定义在类上,不能动态添加到单个实例的 __dict__ 中并期望生效。因为属性访问机制只在类层级查找描述符:
-
obj.x→ 查找type(obj).__dict__['x']→ 若是 property,走描述符协议 -
obj.__dict__['x'] = property(...)不起作用:这只是往实例字典塞了个对象,不参与属性查找流程 - 若想运行时添加受控属性,需修改类本身(如
C.x = property(...)),或使用__getattr__手动拦截 - 继承关系中,子类可覆盖父类的 property(通过重新定义同名 property),也可用
super().xxx在自定义逻辑中调用父级 getter/setter
常见误区与调试建议
property 行为常被误解,尤其在涉及继承、装饰器顺序或缓存场景时:
- 不要在 getter 中修改实例状态(如缓存计算结果到
self._cache),除非你明确需要延迟初始化且能处理并发或重入——这容易导致意外副作用 - 避免在
__init__中直接调用 property setter:若 setter 依赖其他尚未初始化的属性,会引发 AttributeError - 调试时可用
help(obj.__class__.attr)查看 property 绑定的函数,或打印type(obj.__class__.attr)确认是否为property - 若需带参数的“属性式”接口,property 不适用,应改用普通方法;property 的设计初衷是封装无参访问逻辑










