You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 10:36:38