继承torch.nn.Module后类成员变量赋值后变为None的问题
问题原因与解决方法
问题核心原因
这是torch.nn.Module的属性赋值逻辑与普通Python类不同,再结合你定义的类属性共同导致的矛盾:
- 继承
torch.nn.Module后,当你执行self.txt_encoder = txt_encoder时,由于txt_encoder是nn.Module的子类实例(CLIPTextModelWithProjection继承自nn.Module),Module的__setattr__方法会把这个实例注册到对象内部的_modules字典中,不会将其写入实例的__dict__。 - 你的
Foo类定义了类级别的属性txt_encoder = None,当访问self.txt_encoder时,Python的属性查找顺序是先找实例的__dict__,找不到就去类的属性里找,所以返回了类定义的None。 torch.nn.Module重写了__getattr__方法,当常规属性查找失败时,它会去_modules字典里查找对应的子模块,所以直接调用test.__getattr__('txt_encoder')能拿到正确的实例。tokenizer能正常访问,是因为它不是nn.Module子类,赋值时会直接写入实例的__dict__,不会触发Module的特殊逻辑。
解决方法
方法一:移除类级别的txt_encoder属性(推荐)
直接删除Foo类中定义的txt_encoder: Optional[CLIPTextModelWithProjection] = None这一行。这样访问self.txt_encoder时,常规查找失败后会自动触发Module的__getattr__,从_modules中获取正确实例。
修改后的Foo类:
class Foo: def _setup_txt_encoder(self, clip_txt_model_name: str): print('loading text tokenizer and encoder') tokenizer = AutoTokenizer.from_pretrained(clip_txt_model_name, clean_up_tokenization_spaces=True) txt_encoder = CLIPTextModelWithProjection.from_pretrained(clip_txt_model_name).requires_grad_(False) self.tokenizer, self.txt_encoder = tokenizer, txt_encoder print(type(self.tokenizer).__name__, type(self.txt_encoder).__name__, type(txt_encoder).__name__) return tokenizer, txt_encoder tokenizer: Optional[PreTrainedTokenizer|PreTrainedTokenizerFast] = None
方法二:强制写入实例__dict__(仅特殊场景使用)
如果必须保留类属性,可以用object.__setattr__绕开Module的__setattr__逻辑,直接把属性写入实例的__dict__:
# 在_setup_txt_encoder方法中替换赋值语句 object.__setattr__(self, 'txt_encoder', txt_encoder)
⚠️ 注意:这样会导致txt_encoder不被当作Module的子模块管理,比如调用to(device)时不会自动移动该模型,也不会被包含在parameters()或children()中,仅适合不需要Module管理该实例的场景。
方法三:显式注册子模块(规范写法)
如果希望txt_encoder作为Module的子模块被管理,可以用register_module方法:
# 在_setup_txt_encoder方法中替换赋值语句 self.register_module('txt_encoder', txt_encoder)
这种方式和直接赋值效果一致,但更明确,同时需要移除类级别的txt_encoder属性才能正常访问。
内容的提问来源于stack exchange,提问作者Pui Ho Lam
相关产品推荐
相关产品推荐

