直接在__init__中校验不够用,因实例化后属性仍可随意修改;需用@property+@xxx.setter在赋值时拦截校验,注意私有属性命名、异常类型、避免耗时操作及None显式判断。

为什么直接在 __init__ 里校验不够用
因为实例化后属性仍可能被随意修改,__init__ 只管“出生”,不管“成长”。比如 user.age = -5 或 user.email = "abc",若没后续约束,数据一致性立刻崩坏。这时候靠 @property + @xxx.setter 是最轻量、最 Pythonic 的拦截方案。
用 @property 和 @xxx.setter 做字段级校验的实操要点
核心是把校验逻辑收束到 setter 中,同时保留属性访问的自然语法。注意几个关键细节:
- 私有属性名建议加下划线前缀(如
_email),避免和 property 名冲突 - setter 中抛出
ValueError是惯例,调用方容易捕获和理解 - 不要在 setter 里做耗时操作(如网络请求、大文件读取),它会在每次赋值时触发
- 如果允许 None,需显式判断
if value is None:,否则if not value:会误判空字符串、0 等
示例:
class User:
def __init__(self, email):
self.email = email # 触发 setter
<pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">@property
def email(self):
return self._email
@email.setter
def email(self, value):
if not isinstance(value, str):
raise ValueError("email must be a string")
if "@" not in value or "." not in value.split("@")[-1]:
raise ValueError("invalid email format")
self._email = value.strip()</code></pre>当多个字段相互依赖时,setter 怎么协作校验
比如 end_date 必须晚于 <code>start_date,但两个都是可独立设置的属性。不能只在各自 setter 里查——因为设置顺序不确定。常见做法:
立即学习“Python免费学习笔记(深入)”;
- 把依赖校验推迟到真正需要时(如调用
is_valid()方法) - 或在每个 setter 中缓存值,并标记“待验证”,在访问最终状态前统一校验
- 更稳妥的是:只暴露一个入口(如
update_dates(start, end)),内部集中校验,避免 setter 之间互相调用导致递归或状态不一致
强行在 end_date setter 里读 self.start_date 是危险的——此时 start_date 可能还没设,或仍是旧值。
别忽略 __slots__ 和继承场景下的 setter 失效风险
如果类定义了 __slots__ = ["_email"],但忘了包含 _email,Python 会静默跳过赋值,setter 根本不会执行,而你得不到任何提示。另外,在子类中重写父类的 property setter 时,必须显式调用 super().xxx.fset(self, value),否则父类校验逻辑就丢了。
最容易被忽略的一点:property 只对新式类(继承 object 或默认在 Python 3 中)生效;如果手误写了老式类(class User: 且无显式父类),@property 会完全失效,变成普通方法。


















