__set_name__是python 3.6+引入的描述符协议方法,当描述符被赋值给类属性时自动调用,接收所有者类(owner)和属性名(name)作为参数,用于在类创建阶段自动绑定属性名称,避免手动传参。

__set_name__ 是什么,什么时候会被调用?
__set_name__ 是 Python 3.6+ 引入的描述符协议方法,当描述符被赋值给类属性时自动触发,Python 解释器会传入定义该描述符的类对象和属性名字符串。它不是在实例访问时调用,而是在类创建阶段(即类体执行完毕、类对象生成后)由解释器主动调用。
常见错误现象:定义了 __set_name<strong></strong> 却没生效 —— 很可能是因为描述符没真正作为类属性被赋值(比如写在函数里、动态 setattr、或用了 slots__ 但没预留空间),或者 Python 版本低于 3.6。
使用场景包括:
- 自动绑定字段名,避免手动传参(如 ORM 字段、验证器)
- 构建带名称上下文的描述符(如日志记录、调试输出)
- 配合
<strong>get</strong>/<strong>set</strong>实现“懒加载 + 名称感知”逻辑
怎么写一个带 __set_name__ 的描述符?
关键点是:必须继承自 object(或任何新式类),且不能覆盖 __set_name__ 时不调用父类(除非你明确不想触发默认行为)。它没有返回值,也不该抛异常(否则类定义会失败)。
class LoggedDescriptor:
def __set_name__(self, owner, name):
self.name = name # 保存属性名,供 __get__/__set__ 使用
self.private_name = f'_{name}'
<pre class="brush:python;toolbar:false;">def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.private_name, None)
def __set__(self, obj, value):
print(f"Setting {self.name} = {value}")
setattr(obj, self.private_name, value)
注意:
-
owner是定义该描述符的类(不是实例),可用于检查类型或注册元信息 -
name是字符串,即你在类中写的属性名,如age = LoggedDescriptor()中的"age" - 不要尝试在
__set_name__里访问obj或实例状态 —— 此时实例还不存在
为什么不能靠 __set__ 或 __init__ 获取属性名?
<strong>init</strong> 在描述符实例化时运行,此时还不知道它会被赋给哪个类、哪个名字;<strong>set</strong> 在实例属性被赋值时才触发,那时连类定义都已完成,更无从得知定义时的名称。
所以常见坑包括:
- 在
<strong>init</strong>里硬编码name参数:导致每次都要手动传,违背“自动获取”的初衷 - 试图用
inspect.stack()反向解析赋值语句:不可靠、慢、且在某些环境(如 PyPy、冻结二进制)下失效 - 忘记
__set_name__只对类属性有效:如果把描述符放在实例上(如obj.x = MyDesc()),它根本不会被调用
和 __set__ / __get__ 配合时要注意什么?
__set_name__ 设置的字段(如 self.name)必须是实例变量,不能存在类变量里 —— 否则多个描述符实例会互相覆盖。
性能影响极小,仅在类定义期执行一次;但要注意兼容性:Python
示例中 self.private<em>name = f'</em>{name}' 是常见模式,用于隔离存储,但需确保:
- 对应的
<strong>get</strong>和<strong>set</strong>确实用的是同一个私有名 - 如果类用了
<strong>slots</strong>,得提前声明该私有名(如<strong>slots</strong> = ('_age',)),否则setattr会失败
描述符的“自动命名”看似简单,但一旦涉及多继承、装饰器包装、或动态类构建,__set_name__ 的调用时机和参数就容易出偏差——最稳妥的方式,永远是用最小可复现例子先验证类定义是否真触发了它。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











