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 = yModule.__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、缓存等普通属性

好处:

  1. 遍历可靠:parameters() / children() / to() / train() 只扫注册表,不误伤普通属性
  2. state_dict 可预测:键名来自注册名(如 fc.weight)
  3. 同名切换可清理:Parameter ↔ Module 等变更时统一从其它字典移除
  4. 元数据无害:self.cfg 等不进 state_dict / 不搬 GPU
  5. 与显式 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__ 先建账本,避免走自己的特殊赋值逻辑。