Травление Python после смены каталога модуля - PullRequest
35 голосов
/ 23 января 2010

Я недавно изменил макет каталога моей программы: раньше у меня были все мои модули в «главной» папке. Теперь я переместил их в каталог, названный в честь программы, и поместил туда __init__.py, чтобы создать пакет.

Теперь у меня есть один файл .py в моей основной директории, который используется для запуска моей программы, что гораздо лучше.

Во всяком случае, попытка загрузить маринованные файлы из предыдущих версий моей программы не удалась. Я получаю «ImportError: нет модуля с именем tools» - что, я думаю, связано с тем, что мой модуль ранее находился в главной папке, а теперь он находится в Whyteboard.tools, а не просто как инструменты. Однако код, который импортируется в модуль tools, находится в том же каталоге, что и он, поэтому я сомневаюсь, что есть необходимость указывать пакет.

Итак, мой каталог программ выглядит примерно так:

whyteboard-0.39.4

-->whyteboard.py

-->README.txt

-->CHANGELOG.txt

---->whyteboard/

---->whyteboard/__init__.py

---->whyteboard/gui.py

---->whyteboard/tools.py

whyteboard.py запускает блок кода из Whyteboard / gui.py, который запускает графический интерфейс. Эта проблема с травлением определенно не возникала до реорганизации каталога.

Ответы [ 5 ]

64 голосов
/ 23 января 2010

Как говорят документация pickle , чтобы сохранить и восстановить экземпляр класса (на самом деле тоже функцию), вы должны соблюдать определенные ограничения:

рассол может сохранить и восстановить класс случаи прозрачно, однако определение класса должно быть импортируемым и жить в том же модуле, что и когда объект хранился

whyteboard.tools - это , а не «тот же модуль, что и» tools (даже если он может быть импортирован import tools другими модулями в том же пакете, он заканчивается в sys.modules как sys.modules['whyteboard.tools']: это абсолютно важно, в противном случае один и тот же модуль, импортируемый одним в том же пакете, чем один в другом пакете, будет иметь несколько и, возможно, конфликтующие записи!).

Если ваши файлы Pickle имеют хороший / расширенный формат (в отличие от старого формата ASCII, который используется по умолчанию только из соображений совместимости), перенос их после внесения таких изменений может на самом деле не быть вполне столь же тривиально, как «редактирование файла» (которое является двоичным и т. д.!), несмотря на то, что предлагает другой ответ. Я предлагаю вместо этого сделать небольшой «сценарий переноса рассола»: пусть он исправит sys.modules вот так ...:

import sys
from whyteboard import tools

sys.modules['tools'] = tools

, а затем cPickle.load каждый файл, del sys.modules['tools'] и cPickle.dump каждый загруженный объект обратно в файл: эта временная дополнительная запись в sys.modules должна позволить успешно загружать огурчики, а затем выгрузить их снова следует с помощью имя модуля для классов экземпляров (необходимо убрать эту дополнительную запись).

10 голосов
/ 23 июля 2017

Случилось со мной, решил это, добавив новое местоположение модуля в sys.path перед загрузкой pickle:

import sys
sys.path.append('path/to/whiteboard')
f = open("pickled_file", "rb")
pickle.load(f)
7 голосов
/ 15 ноября 2018

Это можно сделать с помощью пользовательского «unpickler», который использует find_class():

import io
import pickle


class RenameUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        renamed_module = module
        if module == "tools":
            renamed_module = "whyteboard.tools"

        return super(RenameUnpickler, self).find_class(renamed_module, name)


def renamed_load(file_obj):
    return RenameUnpickler(file_obj).load()


def renamed_loads(pickled_bytes):
    file_obj = io.BytesIO(pickled_bytes)
    return renamed_load(file_obj)

Тогда вам нужно будет использовать renamed_load() вместо pickle.load() и renamed_loads() вместо pickle.loads().

5 голосов
/ 11 августа 2014

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

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> 
>>> class Foo(object):
...   def bar(self):
...     return 5
... 
>>> f = Foo()
>>> 
>>> _f = dill.dumps(f)
>>> 
>>> class Foo(object):
...   def bar(self, x):
...     return x
... 
>>> g = Foo()
>>> f_ = dill.loads(_f)
>>> f_.bar()
5
>>> g.bar(4)
4
5 голосов
/ 23 января 2010

Это нормальное поведение маринованных, необработанных объектов необходимо, чтобы их определяющий модуль импортировался .

Вы должны иметь возможность изменить путь модулей (то есть с tools на whyteboard.tools), отредактировав маринованные файлы, поскольку они обычно являются простыми текстовыми файлами.

...