nn.Module ignore Module type instance variables if it is also defined as a class variable

Disclaimer: I’m not sure this counts as a bug, as I’m triggering this by doing something slightly unconventional… But this does feel like a non-intended behavior of nn.Module.

When defining the same names as both class and instance variables on an nn.Module, and storing Modules inside, instances will use the class variable instead of the instance one, as it would normally.

The above description might be very confusing, here is a code example:

import torch
from torch import nn

class A(nn.Module):
    number = 0.0
    module = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.number = 1.0
        self.module = nn.Identity()

    def forward(self, x):
        return self.module(x) * self.number

print(f"{A.number=}, {A.module=}")
a = A()
print(f"{a.number=}, {a.module=}")
t = torch.rand(1, 1)
assert a(t) == t

Running this results in an error:

A.number=0.0, A.module=None
a.number=1.0, a.module=None
Traceback (most recent call last):
  [...], in forward
    return self.module(x) * self.number
TypeError: 'NoneType' object is not callable

I would normally expect a.module to be nn.Identity(), not None… This seems pretty counter-intuitive, especially since a.number==1.0 as normal.

In my use case, I needed to do this so that the class variable could act as a default value. Models picked before I had written code with the instance variable could be loaded with that working default value.
For my case, I think I can re-factor the code to define a __setstate__() instead to get that effect.

I’ve tested this on torch==2.1.2