протокол = 4 pickle (python 3.7): ошибка ключа при загрузке dicts с тем же ключом внутри

#python-3.x #pickle

#python-3.x #pickle

Вопрос:

Я использую python3.7.9-64bit, установленный anaconda на Mac.
Просто случается столкнуться с тремя проблемами.

(Хотя я пишу fix_import=False, изменение этого значения не имеет никакого значения)
1-я проблема, если два dicts с одним и тем же ключом были маринованы в одном файле, загрузка 2-го dict вызовет ошибку ключа.

 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 
 

2-я проблема, хотя она, вероятно, относится к первой, если во 2-м dict внутри него есть dict D, который использует один и тот же ключ с 1-м dict, тогда ключ 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}}
 

3-я проблема, и самая разрушительная для меня, если есть dict get pickle.дамп и 2-й сброшенный dict содержат список dicts, использующих один и тот же ключ, загрузка 2-го dict вызывает 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 ) .
Ничего не происходит с protocol=3
Неизвестно о python> 3.8 или protocl=5

Комментарии:

1. Пожалуйста, не модифицируйте свой вопрос решениями; для этого и нужны ответы. Если ответы неадекватны, вы можете опубликовать свои собственные, но смешивать ответы в вашем вопросе настоятельно не рекомендуется (среди прочего, это делает невозможным голосование по качеству этого «ответа»). Я вернул его в состояние, в котором он был до того, как вы начали редактировать в решениях.

Ответ №1:

Короткий ответ на ваш вопрос — использовать pickle.Unpickler() для загрузки ваших солений.

В качестве альтернативы, не используйте pickle.Pickler() . Вместо этого записывайте каждый pickle с pickle.dump() помощью и считывайте обратно с pickle.load() помощью или pickle.Unpickler() .

Любой из них должен решить вашу проблему.


Я могу подтвердить, что та же проблема, которую вы описываете, существует в Python 3.9.1 для обеих версий протокола 4 и 5.

КСТАТИ: обратите внимание, что {"a",3} в вашем последнем примере это set, а не dict, как вы думали. Тем не менее, произойдет та же ошибка.

Проблема в том, что Pickler для кэширования данных, которые он обработал, используется memo. Он использует это, чтобы сэкономить на размере результирующего файла, избегая хранения одних и тех же данных более одного раза. Памятка является общей для всех pickles, написанных с Pickler помощью .

Unpickler Использует memo для восстановления обработанных объектов, которые совместно используют кэшированные данные. Однако 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.загрузка (tmpf))
_pickle.Ошибка UnpicklingError: значение заметки не найдено в индексе 6

Комментарии:

1. Большое вам спасибо. Можете ли вы также проверить это pickle. Unpickler(tmpf).load() также не будет работать в этом случае по той же причине?

2. @dsASDW: Нет. pickle.Unpickler() будет работать во всех случаях.