@property 本身只提供读取逻辑,无配套 @xxx.setter 时属性只读,赋值抛 AttributeError;真正拦截需定义同名 setter 并在其中加入校验等逻辑。

为什么直接用 @property 不能拦截赋值?
很多人以为加了 @property 就能像 JavaScript 的 getter/setter 那样自动拦截所有读写,其实不是。@property 只提供读取逻辑,**没有配套的 @xxx.setter 时,属性是只读的**——尝试赋值会抛出 AttributeError: can't set attribute,这不是拦截,是拒绝。
真正实现“拦截”,得主动定义 setter,且在其中插入校验、日志、缓存更新等逻辑。
如何用 @property + @xxx.setter 拦截并验证赋值?
核心是把属性访问转为方法调用,在 setter 里控制行为。常见场景包括类型检查、范围限制、状态同步:
- 必须同时定义 getter 和 setter,名字要一致(比如都叫
age) - setter 方法名必须和 getter 完全相同,且用
@xxx.setter装饰 - 内部存储建议用私有变量(如
self._age),避免递归调用
class Person:
def __init__(self, age):
self._age = age # 存到私有变量
<pre class="brush:php;toolbar:false;">@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError("age must be int")
if value 150:
raise ValueError("age must be between 0 and 150")
self._age = value # 真正赋值放这里
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
什么时候该用 __setattr__ 而不是 @property?
@property 是按字段精细控制,__setattr__ 是全局拦截——但滥用它容易破坏封装,尤其对已用 @property 的字段再做统一处理,极易引发递归或绕过校验。
- 优先用
@property+@xxx.setter控制明确字段 - 只有需要统一处理“所有未声明的属性”或做元编程(如自动注册、日志埋点)时才考虑
__setattr__ - 若真要用
__setattr__,记得对私有变量(如_age)或特定前缀字段跳过,否则self._age = value会再次触发__setattr__
注意 @property 的性能和继承行为
每次访问 @property 字段都会执行函数体,如果里面含 IO、复杂计算或锁操作,会明显拖慢性能;另外子类重写 getter/setter 时,父类逻辑不会自动继承,必须显式调用 super().xxx。
- 高频访问的属性,考虑加缓存(如
@functools.cached_property,Python 3.8+) - setter 中避免耗时操作;真需要异步或延迟更新,应改用显式方法(如
update_name()) - 继承时,如果子类想扩展父类
name的 setter,得写@Parent.name.setter或手动代理,Python 不支持直接覆写装饰器属性
实际项目里,最常被忽略的是 setter 中没处理 None 或空字符串这类边界值,还有忘记在多线程环境下加锁——而 @property 本身不提供线程安全。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










