继承dict实现ConfigDict时pickle反序列化丢失_filename属性问题
解决ConfigDict查询时触发的AttributeError问题
嘿,我一眼就看到问题出在哪了——你踩了pickle和dict子类结合的一个经典坑!
问题回顾
你继承dict实现了一个能持久化的配置字典ConfigDict,需求是通过命令行参数实现键值对的设置、查询和全量展示。代码能正常写入数据,但查询指定键时触发了如下错误:
AttributeError: 'ConfigDict' object has no attribute '_filename'
(堆栈跟踪指向__setitem__方法中的with open(self._filename,"wb") as fh:行)
你的代码如下:
import pickle,os,sys class ConfigDict(dict): def __init__(self, filename): self._filename = filename if not os.path.exists(self._filename): with open(self._filename,"wb") as fh: pickle.dump({}, fh) with open(self._filename,"rb") as fh: self.update(pickle.load(fh)) def __setitem__(self, key, value): dict.__setitem__(self,key,value) with open(self._filename,"wb") as fh: pickle.dump(self, fh) def __getitem__(self,key): return dict.__getitem__(self,key) cd = ConfigDict('first.pickle') # if 2 arguments on the command line, # set a key and value in the object's dictionary if len(sys.argv) == 3: key, value = sys.argv[1], sys.argv[2] print('writing data: {0}, {1}'.format(key, value)) cd[key] = value # if 1 argument on the command line, treat it as a key and show the value elif len(sys.argv) == 2: print('reading a value') key = sys.argv[1] print('the value for {0} is {1}'.format(sys.argv[1], cd[key])) # if no arguments on the command line, show all keys and values else: print('keys/values:') for key in cd.keys(): print(' {0} = {1}'.format(key, cd[key]))
问题根源
你在__setitem__里用pickle.dump(self, fh)把整个ConfigDict实例序列化到文件,但dict作为内置类型,pickle对它的子类序列化有特殊逻辑:
- 默认只会保存字典的键值对,不会保存子类自定义的属性(比如你的
_filename); - 反序列化时会绕过
__init__方法直接重建对象,导致反序列化出来的ConfigDict实例完全没有_filename属性。
当你第二次运行程序查询时,__init__加载出的是这个残缺的实例,虽然你用self.update(...)把键值对复制到了当前正常的cd对象,但这个残缺实例如果被意外触发(比如隐式操作),就会抛出AttributeError。更关键的是,你根本不需要序列化整个对象——保存字典的纯键值对就足够了!
解决方案
修改核心逻辑,只序列化字典的内容而非整个实例,同时规范dict子类的初始化流程:
import pickle, os, sys class ConfigDict(dict): def __init__(self, filename): # 先调用父类构造方法,确保dict初始化完整 super().__init__() self._filename = filename if not os.path.exists(self._filename): # 初始化时保存空字典 with open(self._filename, "wb") as fh: pickle.dump({}, fh) # 加载文件中的普通字典并更新当前对象 with open(self._filename, "rb") as fh: self.update(pickle.load(fh)) def __setitem__(self, key, value): # 调用父类方法设置键值对 super().__setitem__(key, value) # 只保存纯键值对字典,而非整个实例 with open(self._filename, "wb") as fh: pickle.dump(dict(self), fh) def __getitem__(self, key): # 增加键不存在的友好提示 if key not in self: raise KeyError(f"键 '{key}' 不存在") return super().__getitem__(key) cd = ConfigDict('first.pickle') if len(sys.argv) == 3: key, value = sys.argv[1], sys.argv[2] print(f'writing data: {key}, {value}') cd[key] = value elif len(sys.argv) == 2: print('reading a value') key = sys.argv[1] try: print(f'the value for {key} is {cd[key]}') except KeyError as e: print(e) else: print('keys/values:') for key, value in cd.items(): print(f' {key} = {value}')
修改说明
- 序列化内容调整:把
pickle.dump(self, fh)改为pickle.dump(dict(self), fh),确保文件中只保存纯键值对字典,避免子类属性丢失问题; - 规范父类调用:添加
super().__init__()调用dict的构造方法,让初始化流程更合规; - 友好报错处理:在
__getitem__中增加键不存在的判断,并在查询分支添加try-except,提升用户体验; - 代码简洁优化:用f-string替代
format写法,让代码更易读。
这样修改后,再测试设置、查询、全量展示的功能,就不会再触发AttributeError了!
内容的提问来源于stack exchange,提问作者fsociety
相关产品推荐
相关产品推荐

