直接赋值无法触发校验因@property仅拦截点号访问(如obj.attr),不防底层变量(如_obj._attr);须将数据存私有变量,所有读写经property的getter/setter,且setter内校验并用self._name=value赋值防递归。

为什么直接赋值无法触发校验逻辑
很多人以为给类属性加 @property 就能自动拦截所有赋值,结果发现 obj._attr = "invalid" 依然能绕过校验。根本原因是 @property 只控制通过点号访问的属性(如 obj.attr),对底层实例变量(如 _attr)完全不设防。
真正有效的做法是:把真实数据存在私有变量里,所有读写都走 property 的 getter/setter,且 setter 中做校验。否则校验形同虚设。
- 必须用
self._name = value而非self.name = value在 setter 内赋值,避免无限递归 - 不要在
__init__中直接赋值给公有属性名(如self.age = age),应先存私有变量再调用 setter,或统一在 setter 中处理初始化 - 校验失败时抛出
ValueError是惯例,方便调用方捕获
如何写一个带范围校验的 age 属性
典型场景:年龄必须是 0–150 的整数。关键在于 setter 中做类型和范围双校验,并明确区分错误原因。
class Person:
def __init__(self, age):
self.age = age # 这里会触发 setter
<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 ValueError("age must be an integer")
if not (0 <p></p>注意:构造函数里写 self.age = age 是正确做法,它会进入 setter;若写成 self._age = age 就跳过了校验。
字符串长度与格式校验的常见陷阱
校验邮箱、用户名这类字符串时,容易忽略空格、None 和编码问题。例如 email.strip() 前没判空会导致 AttributeError。
- 永远先检查
value is None,再调用.strip()或.lower() - 正则校验建议用
re.fullmatch而非re.match,避免部分匹配(如"a@b.cxxx"被误认为合法) - 如果允许空字符串,需显式写
if value == ""分支,别依赖“非空即有效”
示例中邮箱校验:
import re
...
@email.setter
def email(self, value):
if value is None:
raise ValueError("email cannot be None")
value = value.strip()
if not value:
raise ValueError("email cannot be empty")
if not re.fullmatch(r"[^@]+@[^@]+\.[^@]+", value):
raise ValueError("invalid email format")
self._email = value
性能敏感场景下要不要缓存校验结果
如果属性被高频读取(比如每毫秒调用几十次),而校验逻辑较重(如调用外部 API 或解析 JSON),那每次 getter 都重复校验就浪费资源。但 @property 本身不提供缓存,得手动处理。
简单方案是加个标记位:
class Config:
def __init__(self, raw):
self._raw = raw
self._parsed = None
self._parsed_valid = False
<pre class="brush:php;toolbar:false;">@property
def data(self):
if not self._parsed_valid:
self._parsed = json.loads(self._raw) # 重操作
self._parsed_valid = True
return self._parsed
不过要注意:一旦原始数据 _raw 可能被外部修改,这种缓存就会失效。此时要么禁止外部直接改 _raw,要么改用 functools.cached_property(Python 3.8+),它只在首次访问时计算并缓存。
真正麻烦的是校验逻辑本身依赖外部状态(比如检查文件是否存在),这种没法缓存,只能每次执行——得接受这点开销,或者换设计,比如把校验提前到初始化阶段。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











