Python自定义dict子类pickle序列化加载报AttributeError的解决方法
问题描述
为实现特定需求写了一个继承自dict的自定义字典类,最小复现代码如下:
import pickle class ItemToIndexMappings(dict): def __init__(self, **kwargs): super(ItemToIndexMappings, self).__init__(**kwargs) self.default_value = 0 def __setitem__(self, key, value): if key not in super().keys(): super().__setitem__(key, value) if value >= self.default_value: self.default_value = value + 1 if __name__ == '__main__': obj = ItemToIndexMappings() obj["a"] = 1 with open("someFilename", 'wb') as f: pickle.dump(obj, f) with open("someFilename", "rb") as f: obj2 = pickle.load(f)
反序列化加载对象时抛出错误:
AttributeError: 'ItemToIndexMappings' object has no attribute 'default_value'
测试发现如果移除__setitem__中对super().__setitem__(key, value)的调用,报错就不会出现,需要找到正确的序列化/反序列化实现方式。
问题根因
pickle对dict子类的反序列化有特殊处理逻辑:它不会默认调用类的__init__方法初始化实例属性,而是先创建空的类实例,再直接往实例中写入序列化时存储的键值对。
写入键值对的过程会触发重写的__setitem__方法,此时实例的default_value属性还没有被初始化,访问该属性时就会抛出属性不存在的错误。你观察到的「移除父类__setitem__调用就不报错」的现象,本质是因为移除后键值写入流程不会提前访问default_value,避开了属性未初始化的阶段。
修复方案
两种方案都可以解决问题,可根据实际场景选择:
- 方案1:实现
__reduce__方法,自定义pickle的序列化/反序列化流程,确保实例属性在键值对恢复前完成初始化,是更规范的扩展方式import pickle class ItemToIndexMappings(dict): def __init__(self, **kwargs): super().__init__(**kwargs) self.default_value = 0 def __setitem__(self, key, value): if key not in super().keys(): super().__setitem__(key, value) if value >= self.default_value: self.default_value = value + 1 def __reduce__(self): # 按pickle协议指定反序列化流程:类构造器、构造参数、实例属性字典、键值对迭代器 return (self.__class__, (), self.__dict__, iter(self.items()), None) if __name__ == '__main__': obj = ItemToIndexMappings() obj["a"] = 1 with open("someFilename", 'wb') as f: pickle.dump(obj, f) with open("someFilename", "rb") as f: obj2 = pickle.load(f) # 验证结果 assert obj2["a"] == 1 assert obj2.default_value == 2 - 方案2:在
__setitem__中增加属性存在性判断,兼容反序列化时属性未初始化的场景,改动量最小import pickle class ItemToIndexMappings(dict): def __init__(self, **kwargs): super().__init__(**kwargs) self.default_value = 0 def __setitem__(self, key, value): if key not in super().keys(): super().__setitem__(key, value) # 仅当default_value属性已初始化时才更新值 if hasattr(self, 'default_value') and value >= self.default_value: self.default_value = value + 1 if __name__ == '__main__': obj = ItemToIndexMappings() obj["a"] = 1 with open("someFilename", 'wb') as f: pickle.dump(obj, f) with open("someFilename", "rb") as f: obj2 = pickle.load(f) # 验证结果 assert obj2["a"] == 1 assert obj2.default_value == 2
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

