首页 > 解决方案 > 协议 = 4 泡菜(python 3.7):加载具有相同键的字典时出现键错误

问题描述

我正在运行由 anaconda 在 Mac 上安装的 python3.7.9-64bit。
刚好遇到三个问题。

(虽然我写了 fix_import=False,但改变它并没有任何区别)
第一个问题,如果两个具有相同键的字典在一个文件中被腌制,加载第二个字典将引发 KeyError

import pickle

d1={"a":1}# if two dicts has the same key
d2={"a":0}# does not matter what value is stored.

tmpf=open("deleteThis","wb")
pkl=pickle.Pickler(tmpf,protocol=4,fix_imports=False)
pkl.dump(d1)
pkl.dump(d2)
tmpf.close()

tmpf2=open("deleteThis","rb")
td1=pickle.load(tmpf2)
td2=pickle.load(tmpf2)# KeyError 1 

第二个问题,虽然它可能与第一个有关,但如果第二个字典里面有一个字典D与第一个字典共享相同的键,那么D的键在加载时会改变。

d1={"a":1}
d2={"lll":{"a":2} }# this "a" will always be "lll"

tmpf=open("deleteThis","wb")
pkl=pickle.Pickler(tmpf,protocol=4,fix_imports=True)
pkl.dump(d1)
pkl.dump(d2)
tmpf.close()

tmpf2=open("deleteThis","rb")
td1=pickle.load(tmpf2)
td2=pickle.load(tmpf2)
print(td1,td2)# {'a': 1} {'lll': {'lll': 2}}

第三个问题,对我来说最具破坏性的,如果有一个字典 get pickle.dump 并且第二个转储的字典有一个共享相同密钥的字典列表,加载第二个字典raise KeyError

d1={"b":0}
d2={ "l":[{"a":2} , {"a",3}] }

tmpf=open("deleteThis","wb")
pkl=pickle.Pickler(tmpf,protocol=4,fix_imports=True)
pkl.dump(d1)
pkl.dump(d2)
tmpf.close()

tmpf2=open("deleteThis","rb")
td1=pickle.load(tmpf2)
td2=pickle.load(tmpf2)# KeyError

这些问题只发生在pickle( protocol=4 ). Unknown about python>3.8
没有任何问题或protocol=3
protocl=5

标签: python-3.xpickle

解决方案


对您的问题的简短回答是用于pickle.Unpickler()加载您的泡菜。

或者,不要使用pickle.Pickler(). 而是用 or 写每个泡菜并用orpickle.dump()读回。pickle.load()pickle.Unpickler()

其中任何一个都可以解决您的问题。


我可以确认您描述的相同问题存在于 Python 3.9.1 中,适用于协议版本 4 和 5。

顺便说一句:请注意,{"a",3}在您的最后一个示例中,是一个集合,而不是您想象的 dict。然而,同样的错误会发生。

问题是它Pickler使用备忘录来缓存它已经腌制的数据。它通过避免多次存储相同的数据来使用它来节省结果文件的大小。备忘录在所有用Pickler.

使用Unpickler备忘录重建共享缓存数据的腌制对象。但是,pickle.load()不使用备忘录,因此Pickler在转储单个泡菜时可能无法找到备忘录的值。


下面是一些代码来演示:

import pickle

d1 = {"b": 0}
d2 = {"l": [{"a": 2}, {"a": 3}]}

with open("deleteThis", "wb") as tmpf:
    pkl = pickle.Pickler(tmpf, protocol=4)
    for obj in d1, d2:
        pkl.dump(obj)

# reload objects with Unpickler - will work.
with open("deleteThis", "rb") as tmpf:
    unpkl = pickle.Unpickler(tmpf)
    print('Using Unpickler')
    while True:
        try:
            print(unpkl.load())
        except EOFError:
            break

# reload objects with pickle.load - might work, but won't here.
with open("deleteThis", "rb") as tmpf:
    print('Using pickle.load()')
    while True:
        try:
            print(pickle.load(tmpf))
        except EOFError:
            break

它的输出:

使用 Unpickler
{'b':0}
{'l': [{'a': 2}, {'a': 3}]}
使用 pickle.load()
{'b':0}
回溯(最近一次通话最后):
  文件“/tmp/z.py”,第 26 行,在
    打印(pickle.load(tmpf))
_pickle.UnpicklingError:在索引 6 处找不到备注值

推荐阅读