@property不可复用且不支持参数化校验;描述符通过__get__/__set__/__delete__实现可复用约束,__set_name__确保字段名准确,与__slots__混用需额外处理存储机制。

为什么直接用 @property 不够用?
因为 @property 只能绑定到具体类的实例属性上,无法复用;一旦多个字段都要做类型检查或范围校验,就得写一堆重复的 @xxx.setter。更麻烦的是,它不能自动作用于子类新增的同名属性,也不支持参数化(比如想让不同字段用不同最小值)。描述符协议则把校验逻辑抽成独立对象,只要定义一次,就能在任意类里通过赋值语句触发——这才是真正可复用的数据约束机制。
怎么写一个带类型和范围校验的描述符?
关键在于实现 __get__、__set__ 和 __delete__ 方法,其中 __set__ 是校验入口。下面这个 IntField 支持指定最小值、最大值,并拒绝非整数:
class IntField:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
<pre class="brush:php;toolbar:false;">def __set_name__(self, owner, name):
self.name = name # 实例字典里的键名,如 '_age'
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, f'_{self.name}', None)
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError(f'{self.name} must be an integer')
if self.min_value is not None and value < self.min_value:
raise ValueError(f'{self.name} must be >= {self.min_value}')
if self.max_value is not None and value > self.max_value:
raise ValueError(f'{self.name} must be <= {self.max_value}')
setattr(instance, f'_{self.name}', value)
使用时直接作为类变量声明即可:
librosa 是一个用于音频和音乐分析的 Python 库。它提供了特征提取、频谱分析、节拍跟踪、起始检测和音频可视化的工具,基于 NumPy 和 SciPy 构建,用于科学音频计算。
立即学习“Python免费学习笔记(深入)”;
class Person:
age = IntField(min_value=0, max_value=150)
score = IntField(min_value=0, max_value=100)
为什么 __set_name__ 比硬编码属性名更可靠?
不靠它的话,你得手动传字段名进描述符初始化,容易错位或遗漏;而 __set_name__ 是 Python 在类创建完成、描述符被赋值给类变量时自动调用的钩子,保证 self.name 一定等于代码里写的那个变量名(如 age)。这直接影响 __get__ 和 __set__ 中对实例字典的操作是否准确——漏掉这步,所有取值都会返回 None,且报错信息里的字段名也会变成错的。
- 没实现
__set_name__→self.name是None→getattr(instance, '_None')报AttributeError - 手动传名(如
IntField('age', ...))→ 类变量名改了但构造参数没同步 → 校验提示和实际字段脱节
描述符和 __slots__ 一起用会出什么问题?
如果类启用了 __slots__,实例不再有 __dict__,而上面示例依赖 setattr(instance, f'_{self.name}', ...) 往实例上塞私有属性——这在 __slots__ 下会失败,抛出 AttributeError: 'Person' object has no attribute '_age'。解决办法是:要么在 __slots__ 里显式加上 '_age' 这类内部字段名,要么改用弱引用字典(weakref.WeakKeyDictionary)在描述符内部存储值。
后者更干净,但要注意:弱引用字典只存活跃实例,一旦实例被回收,对应校验值就没了;前者要维护两处字段名(__slots__ 和描述符名),稍不一致就崩。实际项目中,建议优先避开 __slots__ 和描述符混用,除非真有内存敏感场景。

















