描述符的__set__是验证入口,值必须存于宿主实例__dict__中以避免共享;应使用__set_name__自动获取属性名,而非硬编码;验证逻辑须在__set__中实现并抛出异常。

描述符的 __set__ 方法是验证入口,不是 __get__
属性验证逻辑必须放在 __set__ 里,而不是 __get__ 或 __init__。因为验证的目标是“赋值时拦截非法值”,__get__ 只读不改,__init__ 无法覆盖后续赋值。描述符对象本身不保存值——它得把值存到宿主实例的 __dict__ 中,否则多个实例会共享同一份数据。
常见错误是直接在描述符类里用 self.value = value,结果所有使用该描述符的实例都共用这个 value。正确做法是用实例唯一标识(比如 instance.__dict__[self.name])存值。
- 验证逻辑写在
__set__内,抛出ValueError或自定义异常 - 用
instance.__dict__[self.name]存值,避免实例间污染 - 在
__set_name__中动态获取属性名,比硬编码字符串更安全
用 __set_name__ 自动绑定属性名,别手动传字符串
手动传属性名(如 Positive('age'))容易拼错、难维护,且和类定义脱节。Python 3.6+ 提供 __set_name__(self, owner, name),在类创建完成时自动调用,name 就是描述符在类中声明的变量名。
不实现 __set_name__ 的话,你得在每个描述符实例初始化时显式指定名称,一不小心就和实际字段名不一致,导致存取错位或 KeyError。
立即学习“Python免费学习笔记(深入)”;
librosa 是一个用于音频和音乐分析的 Python 库。它提供了特征提取、频谱分析、节拍跟踪、起始检测和音频可视化的工具,基于 NumPy 和 SciPy 构建,用于科学音频计算。
-
__set_name__是唯一可靠方式获取宿主类中的真实属性名 - 必须检查
self.name是否已存在,防止被重复调用(某些装饰器或元类可能触发多次) - 如果兼容旧版本 Python,需 fallback 到手动传参,但新项目应默认启用
__set_name__
整数范围验证示例:继承 int 不够,得重写 __set__
有人试图让描述符继承 int 并重载 __new__,这完全走偏——描述符不是值类型,它是管理属性访问的协议对象。验证必须发生在赋值那一刻,也就是 __set__。
下面是一个最小可行的正整数描述符:
class Positive:
def __set_name__(self, owner, name):
self.name = name
<pre class="brush:php;toolbar:false;">def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int) or value <= 0:
raise ValueError(f'{self.name} must be a positive integer')
instance.__dict__[self.name] = valueclass Person: age = Positive() # 自动拿到 'age'
- 调用
p = Person(); p.age = -5会立刻触发ValueError -
Person.age访问的是描述符对象本身(instance is None分支) - 不支持
__delete__?那就别实现,否则默认行为是删除实例字典里的键,反而破坏封装
和 @property 比,描述符更适合跨类复用验证逻辑
@property 写法简洁,但每个类都要重写一遍 @age.setter,逻辑分散。描述符把验证规则封装成可导入、可组合的对象,比如 NonEmptyStr、EmailStr、InRange(1, 100),直接当类属性用就行。
不过要注意:描述符不能直接用于 __slots__ 类,因为后者禁用 __dict__。若要用,得配合 __slots__ 中预留存储位,或改用基于 __set__ 写入 __slots__ 字段的方式——但这会让描述符失去通用性,得为每个 __slots__ 类单独适配。
- 验证规则越通用,越适合抽成描述符;越业务特定,
@property可能更直白 - 描述符对 IDE 类型推断支持较弱,部分工具无法识别
instance.attr的实际类型 - 调试时注意:描述符的
__set__在赋值语句执行期才触发,不是定义期
描述符协议看着简单,真正卡住人的地方往往不是语法,而是没想清楚“值存在哪”和“谁负责清理”。一旦把值误存在描述符自己身上,或者忘了处理 instance is None,问题就会在奇怪的时间点冒出来。

















