78

我最近更改了程序的目录布局:以前,我将所有模块都放在“主”文件夹中。现在,我将它们移动到以程序命名的目录中,并在__init__.py那里放置以制作包。

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

无论如何,尝试从我的程序的早期版本加载腌制文件失败了。我得到了“ImportError:没有名为工具的模块”——我猜这是因为我的模块以前在主文件夹中,现在它在 whyteboard.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 启动一段代码,启动 GUI。在重新组织目录之前,绝对不会发生这种酸洗问题。

4

6 回答 6

117

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

pickle 可以透明地保存和恢复类实例,但是类定义必须是可导入的,并且与存储对象时位于同一模块中

whyteboard.tools不是“相同的模块” tools(即使它可以由import tools同一个包中的其他模块导入,但它最终会sys.modulessys.modules['whyteboard.tools']:这绝对是至关重要的,否则相同的模块由同一个包中的一个与一个在同一个包中导入另一个包最终会包含多个并且可能有冲突的条目!)。

如果您的泡菜文件采用良好/高级格式(与仅出于兼容性原因默认使用的旧 ascii 格式相反),则在执行此类更改后迁移它们实际上可能不像“编辑文件”那么简单(这是二进制&c ...!),尽管另一个答案表明。相反,我建议您制作一个小“泡菜迁移脚本”:让它sys.modules像这样修补......:

import sys
from whyteboard import tools

sys.modules['tools'] = tools

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

于 2010-01-23T03:11:03.640 回答
32

这可以通过使用以下自定义“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()

于 2018-11-15T20:22:24.863 回答
22

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

import sys
sys.path.append('path/to/whiteboard')
f = open("pickled_file", "rb")
pickle.load(f)
于 2017-07-23T11:50:09.257 回答
13

pickle通过引用序列化类,所以如果你改变了类的存在,它不会解开,因为找不到类。如果您使用dill而不是pickle,那么您可以通过引用或直接序列化类(通过直接序列化类而不是它的导入路径)。dump只需在 a 之后和 a 之前更改类定义,就可以很容易地模拟这一点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
于 2014-08-11T13:52:24.857 回答
4

这是 pickle 的正常行为,未腌制的对象需要其定义模块 importable

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

于 2010-01-23T02:58:53.927 回答
0

当您尝试加载包含类引用的 pickle 文件时,您必须在保存 pickle 时遵循相同的结构。如果你想在其他地方使用泡菜,你必须告诉这个类或其他对象在哪里;所以在下面执行此操作可以节省一天:

import sys
sys.path.append('path/to/folder containing the python module')
于 2021-12-28T08:48:19.423 回答