Python __set_name__ 的调用时机:字段后来挂上类,为什么仍然不知道自己的名字
给表单字段或模型字段做自动注册时,常用__set_name__接收所属类和字段名。把字段写在类体里可以正常保存数据,改成类创建后赋值却报没有名字,问题通常出在通知时机:普通属性赋值不会重新运行类创建过程。
下面在Linux、CPython 3.12.14实跑,只改动内存对象。保存为demo.py,运行python3 demo.py。Field用字段名生成实例字典中的存储键;未收到通知便明确报错,避免多个字段悄悄写进同一个默认位置。events记录实际通知对象,便于比较声明、挂载和继承。
AI模型生成概念插图:装配拱内的组件已经经过初始化,后加组件等待单独通知;图形解释创建时机,不是软件截图。
完整程序与本地结果
完整可运行程序
events = []
class Field:
def __init__(self):
self.storage = None
def __set_name__(self, owner, name):
self.storage = "_" + name
events.append((owner.__name__, name))
def __get__(self, obj, owner=None):
if obj is None:
return self
if self.storage is None:
raise RuntimeError("field has no assigned name")
return obj.__dict__.get(self.storage, "empty")
def __set__(self, obj, value):
if self.storage is None:
raise RuntimeError("field has no assigned name")
obj.__dict__[self.storage] = value
class Article:
title = Field()
article = Article()
article.title = "draft"
print("class body:", events, article.title)
late = Field()
Article.summary = late
assert events == [("Article", "title")]
try:
article.summary = "short"
except RuntimeError as exc:
print("late assignment:", str(exc))
else:
raise AssertionError("late field was unexpectedly initialized")
late.__set_name__(Article, "summary")
article.summary = "short"
assert vars(article) == {"_title": "draft", "_summary": "short"}
print("explicit notification:", events, article.summary)
class News(Article):
pass
news = News()
news.title = "fresh"
assert len(events) == 2
print("inherited field:", news.title, "notifications:", len(events))本次实际输出(以下为结果,不是程序)
class body: [('Article', 'title')] draft
late assignment: field has no assigned name
explicit notification: [('Article', 'title'), ('Article', 'summary')] short
inherited field: fresh notifications: 2类体里的字段自动收到名称
第一行只有Article与title这一条通知,title读回draft。默认类创建流程会检查类命名空间,对其中带__set_name__的对象调用该方法,把所属类和名称交给它。名字来自命名空间中的键,不是Field构造函数猜出来的。
执行Article.summary = late后,events仍然只有一项。对象确实放到了类上,描述符的赋值入口也能被调用;缺少的是内部依赖的storage初始化。因此出现我们明确设计的RuntimeError,并不是Python拒绝动态添加描述符。
显式补通知,再检查真实存储位置
late.__set_name__(Article, "summary")补上初始化,随后summary读回short,实例字典同时含有_title和_summary。显式调用时传入的名称必须与挂载名称一致。若挂成summary却通知为title,描述符可能把数据写到错误键,甚至覆盖另一字段。
实际的动态安装函数可把挂载与通知放在同一入口,并检查名称冲突和初始化失败。本例不把这两步称为事务;若通知包含其他业务逻辑,失败后是否撤销挂载、是否允许重复安装,仍要由接口约定。仅确认属性存在,不足以证明字段已经可用。
继承不等于重新声明
News继承title后能保存fresh,通知总数仍为二。继承使用已经存在的字段对象,不等于把它重新放进新类的命名空间。因此不能期待每创建一个子类,字段就自动收到一次新的所属类通知。需要按访问类区分行为时,应另外设计状态。
本例每个字段使用独立Field实例。若同一个描述符挂在多个名字下,内部却只保存一个storage,后来收到的通知可能覆盖先前名称。排查动态模型时,应一起核对对象身份、挂载名称、回调记录和实际存储键。回调是否发生、发生在哪个类上,要用这些证据回答。
参考资料
资料核验日期:2026年10月2日。以上输出对应文中固定输入和明确运行版本,本地执行退出码为0。


