关于pickle:更改模块目录后的Python pickle

Python pickling after changing a module's directory

我最近改变了程序的目录布局:之前,我把所有模块放在"main"文件夹中。现在,我已将它们移动到以程序命名的目录中,并在其中放置__init__.py以生成包。

现在我在我的主目录中有一个.py文件,用于启动我的程序,这个文件更整洁。

无论如何,尝试加载以前版本的程序中的pickle文件是失败的。我得到了,"ImportError:没有模块命名工具" - 我想这是因为我的模块以前在主文件夹中,现在它在whyteboard.tools中,而不仅仅是简单的工具。但是,在工具模块中导入的代码与它位于同一目录中,因此我怀疑是否需要指定包。

所以,我的程序目录看起来像这样:

whyteboard-0.39.4

-->whyteboard.py

-->README.txt

-->CHANGELOG.txt

---->whyteboard/

<5233>

---->whyteboard/gui.py

---->whyteboard/tools.py

whyteboard.py从whyteboard / gui.py启动一个代码块,启动GUI。在目录重新组织之前,肯定没有发生这种酸洗问题。


正如pickle的文档所说,为了保存和恢复类实例(实际上也是一个函数),你必须尊重某些约束:

pickle can save and restore class
instances transparently, however the
class definition must be importable
and live in the same module as when
the object was stored

whyteboard.tools不是"与tools相同的模块"(即使它可以通过import tools由同一个包中的其他模块导入,它最终在sys.modules中作为sys.modules['whyteboard.tools']:这绝对至关重要,否则同一个软件包中的一个模块导入的同一个模块与另一个软件包中的模块导入的模块最终会出现多个且可能存在冲突的条目!)。

如果您的pickle文件是良好/高级格式(与旧的ascii格式相反,仅出于兼容性原因而默认),一旦执行此类更改,迁移它们实际上可能不像"编辑文件"那么简单(这是二元&amp; c ......!),尽管另一个答案表明。我建议你做一个"pickle-migrating script":让它像这样补丁sys.modules ......:

1
2
3
4
import sys
from whyteboard import tools

sys.modules['tools'] = tools

然后cPickle.load每个文件,del sys.modules['tools']cPickle.dump每个加载的对象返回文件:sys.modules中的临时额外条目应该让pickle成功加载,然后再次转储它们应该使用正确的模块名称对于实例的类(删除该额外的条目应该确保这一点)。


发生在我身上,通过在加载pickle之前将模块的新位置添加到sys.path来解决它:

1
2
3
4
import sys
sys.path.append('path/to/whiteboard')
f = open("pickled_file","rb")
pickle.load(f)


这可以使用find_class()的自定义"unpickler"来完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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()


pickle通过引用序列化类,因此如果您更改了类的生命,它将不会因为找不到类而无法解开。如果使用dill而不是pickle,则可以通过引用或直接序列化类(通过直接序列化类而不是它的导入路径)。只需在dump之后和load之前更改类定义,即可轻松模拟这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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


这是pickle的正常行为,unpickled对象需要将其定义模块导入。

您应该能够通过编辑pickle文件来更改模块路径(即从toolswhyteboard.tools),因为它们通常是简单的文本文件。