nn.Module.__init__ 与 __setattr__ 笔记
1. Module.__init__ 在做什么
def __init__(self, *args, **kwargs):
torch._C._log_api_usage_once("python.nn_module") # API 统计
# call_super_init=False(默认):不允许传参,误传则 TypeError
# call_super_init=True:末尾 super().__init__(*args, **kwargs),支持多重继承协作
super().__setattr__("training", True)
super().__setattr__("_parameters", {})
super().__setattr__("_modules", {})
# ... _buffers、hooks 等要点:
- 先建好内部账本(
_parameters/_modules/_buffers/ hooks) - 用
super().__setattr__写入,绕过Module.__setattr__ - 之后才能写
self.layer = nn.Linear(...),否则报
cannot assign ... before Module.__init__() call
2. Python 赋值底层
obj.a = 1
# 语义上约等于:
type(obj).__setattr__(obj, "a", 1)| 概念 | 含义 |
|---|---|
__setattr__ | 属性赋值时调用的钩子 |
type(obj) | 对象所属的类;特殊方法从类上取,不从实例 __dict__ 找 |
object.__setattr__ | 默认实现:写入 obj.__dict__[name] = value |
为何强调 type(obj):解释器对 __setattr__ / __getattribute__ 等协议方法,在类上查找,避免被实例上同名属性干扰。
3. 为何初始化不用 self.xxx = ...
Module 重写了 __setattr__:赋值时按类型自动注册 Parameter / 子模块 / Buffer。
初始化账本若走 self._parameters = {}:
- 会再进
Module.__setattr__(多余开销) - 此时账本可能尚未就绪,逻辑也易绕
因此:
super().__setattr__("_parameters", {}) # ≈ object.__setattr__(self, ...)| 写法 | 走哪条路径 | 用途 |
|---|---|---|
self.x = y | Module.__setattr__ | 用户代码,可能自动注册 |
super().__setattr__("x", y) | 父类(通常 object) | 初始化内部字段 |
重写 __setattr__ 的类,内部真正落库时必须绕过自己,否则会递归。
4. call_super_init
| 值 | 行为 |
|---|---|
False(默认) | __init__ 不接受多余 args/kwargs,兼容旧写法 super().__init__() |
True | 允许把参数继续传给 MRO 中下一个基类的 __init__ |
super() 不是“直接父类”,而是按 MRO 找下一个类。
5. 为何子模块在 _modules,不在 __dict__
self.fc = nn.Linear(1, 1) # -> _modules["fc"]
self.flag = True # -> __dict__["flag"]三本账 + 普通属性:
| 容器 | 内容 |
|---|---|
_modules | 子模块 |
_parameters | 可学习参数 |
_buffers | 非参数张量(如 BN running_mean) |
__dict__ | 配置、flag、缓存等普通属性 |
好处:
- 遍历可靠:
parameters()/children()/to()/train()只扫注册表,不误伤普通属性 state_dict可预测:键名来自注册名(如fc.weight)- 同名切换可清理:Parameter ↔ Module 等变更时统一从其它字典移除
- 元数据无害:
self.cfg等不进state_dict/ 不搬 GPU - 与显式 API 一致:
add_module/register_parameter/register_buffer同一套账本
__setattr__ 的角色:赋值时自动分拣进正确账本(“劫持”是有意设计)。
6. 最小对照
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc = nn.Linear(1, 1)
self.flag = True
m = Net()
# "fc" in m.__dict__ -> False(在 _modules)
# "flag" in m.__dict__ -> True
# list(m.parameters()) -> 只有 fc 的 weight/bias一句话
nn.Module 用自定义 __setattr__ 把结构(模块/参数/buffer)登记进专用字典;__init__ 里用 super().__setattr__ 先建账本,避免走自己的特殊赋值逻辑。