@property定义的属性默认只读,因未定义setter而赋值会报AttributeError;需显式添加@attr.setter才可写,且真只读应配合私有属性与不可变返回值。

为什么直接赋值会报错:@property 定义的属性没有 setter
当你用 @property 只定义了 getter,Python 就默认该属性“不可写”。尝试执行 obj.attr = value 时,会抛出 AttributeError: can't set attribute。这不是 bug,是设计使然——@property 本身不生成 setter,除非你显式加上 @attr.setter。
常见误操作:以为加了 @property 就自动支持读写,结果在初始化或后续逻辑中赋值失败。
- 只写
@property→ 只读(安全,推荐用于计算属性或封装内部状态) - 写了
@attr.setter→ 可读可写(需自行控制逻辑,比如校验、缓存失效) - 写了
@attr.deleter→ 还能支持del obj.attr(极少用,慎用)
如何真正实现“只读”,而不是“假装只读”
光靠 @property 不够——如果底层数据是公开实例变量(如 self._value),用户仍可绕过 getter 直接修改 obj._value。真只读需要配合命名约定 + 封装意识。
实操建议:
- 把真实数据存在私有属性(如
self._x或self.__x),并在@property中返回它的副本或只读视图(比如tuple(self._items)而非self._items) - 避免在 getter 中返回可变对象的原始引用,否则外部仍能修改内容(例如返回
list而不是tuple或copy.copy()) - 若需彻底禁止访问底层变量,可用
__slots__限制属性名,或借助__getattr__拦截未定义属性访问(但会增加复杂度,一般没必要)
什么时候该用 @property,而不是普通方法或公有属性
@property 的核心价值是“接口不变,实现可变”。它适合那些逻辑简单、无副作用、调用开销小的访问场景。
适用情况:
- 需要对字段做简单转换(如单位换算:
@property返回self._cm / 100.0) - 封装计算逻辑(如
full_name基于first_name和last_name拼接) - 兼容旧代码:把原来公有属性改成带校验或日志的访问器,调用方无需改代码
不适用情况:
- 耗时操作(如网络请求、文件读取)——应改为显式方法,如
obj.load_data() - 有副作用(如修改状态、触发事件)——违反“属性访问应是轻量且无感”的直觉
- 需要传参——属性不能接收参数,此时必须用方法
一个典型只读属性定义示例
下面是一个带类型检查、防篡改、不暴露底层列表的只读属性定义:
class Box:
def __init__(self, items):
self._items = list(items) # 存为私有可变列表
<pre class="brush:php;toolbar:false;">@property
def items(self):
return tuple(self._items) # 返回不可变副本,防止外部修改
使用效果:
-
box.items✅ 正常返回tuple -
box.items = [1, 2]❌ 报AttributeError -
box._items.append(3)⚠️ 虽然技术上可行,但属于破坏封装,应视为使用者违规
真正的难点不在语法,而在判断哪些数据该暴露、以什么形态暴露、以及是否值得为这点便利增加维护成本。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











