Python Pickler 连续 dump:列表明明已经追加,第二份读取结果为什么仍然是旧内容
把Pickler保存成长期对象,循环里反复调用dump,容易被理解为“每次给当前状态拍一张照片”。但同一个Pickler记得以前见过的对象。再次遇到同一对象时,流中可能写入的是引用,而不是重新遍历并保存它刚刚改变的内容。
本例只反序列化程序刚在内存中生成的可信数据,不读取外部pickle。pickle加载可以执行代码,不能拿不可信文件或网络输入替换本例payload。代码使用协议4,适用于Python 3.4及以上;已在CPython 3.12.14实跑,保存为demo.py并运行python3 demo.py。
AI模型生成概念图:两枚书签仍指向抽屉里的旧卡片,新卡片留在外面,用于比喻重复对象被记录为引用;不是pickle格式图。
让对象身份不变,只改变里面的值
第一段只建立一个writer,并在两次dump之间给同一字典里的列表追加review。boundary记录第一段输出结束的字节位置。读取时同样复用一个Unpickler,这样既能观察连续流中的引用关系,也能再把第二段单独拿出来验证。
完整可运行程序
import io
import pickle
shared = {"steps": ["draft"]}
stream = io.BytesIO()
writer = pickle.Pickler(stream, protocol=4)
writer.dump(shared)
boundary = stream.tell()
shared["steps"].append("review")
writer.dump(shared)
stream.seek(0)
reader = pickle.Unpickler(stream)
first = reader.load()
second = reader.load()
assert first is second
assert first == {"steps": ["draft"]}
print("same writer values:", first, second)
print("same loaded object:", first is second)
try:
pickle.loads(stream.getvalue()[boundary:])
except pickle.UnpicklingError:
print("second record alone: UnpicklingError")
else:
raise AssertionError("memo-dependent record loaded alone")
independent = io.BytesIO()
state = {"steps": ["draft"]}
pickle.dump(state, independent, protocol=4)
boundary = independent.tell()
state["steps"].append("review")
pickle.dump(state, independent, protocol=4)
payload = independent.getvalue()
first = pickle.loads(payload[:boundary])
second = pickle.loads(payload[boundary:])
assert first == {"steps": ["draft"]}
assert second == {"steps": ["draft", "review"]}
assert first is not second
print("fresh writer values:", first, second)
print("independent loaded objects:", first is not second)本次实际输出(展示内容,不是待执行程序)
same writer values: {'steps': ['draft']} {'steps': ['draft']}
same loaded object: True
second record alone: UnpicklingError
fresh writer values: {'steps': ['draft']} {'steps': ['draft', 'review']}
independent loaded objects: True第二次读取没有得到新的快照
前两行显示,两次读出的steps都只有draft,而且first is second为True。这说明读取端恢复的是同一个对象;第二次dump没有把追加后的完整字典再写一遍。Python内存中的shared确实已经被改动,丢失发生在对“这次dump会重新保存”的预期上。
紧接着单独读取第二段,得到UnpicklingError。本例第二段需要前段建立的memo,不能脱离那份读取上下文。即使每次dump都结束了一次对象表示,也不能据此假设每一段字节都能被全新的读取器独立解释。
独立记录采用独立写入上下文
后半段使用两次模块级pickle.dump,每次调用各有新的序列化上下文;边界也单独保留。两个片段分别用pickle.loads读取,得到draft和draft加review,两份对象身份不同,符合本例“独立状态快照”的需求。
这没有要求为每条记录建立一个磁盘文件。例子仍把两段写入同一个BytesIO,改变的是memo生命周期,而不是存储介质。真正保存记录文件时,还应设计长度或索引边界,明确如何找到每条记录,并处理半条写入;本文的boundary是内存实验里已知的位置。
清空memo也需要配套读取约定
官方接口提供clear_memo,用于清除Pickler对已见对象的记录。若要采用它,读写双方必须对memo何时重置有一致约定,不能只在写入侧清空后继续假定读取器状态自然匹配。独立记录逐条建立读写上下文,更容易验收。
反过来,若业务本来要保留跨对象的共享关系,长期复用memo可能正是需要的语义。它不是天然错误,只是不等于不断保存最新快照。验收时至少同时检查内容、对象身份和单条能否独立读取;这三项分别回答不同问题。
参考资料
资料核验日期:2026年10月2日。输出来自本文固定输入的本地实跑。


