更改模块目录后Python序列化问题

100 投票
8 回答
69970 浏览
提问于 2025-04-15 18:22

我最近改变了我程序的文件夹结构:之前,我把所有的模块都放在一个叫“main”的文件夹里。现在,我把它们移动到了一个以程序命名的文件夹里,并在里面放了一个 __init__.py 文件,这样就把它变成了一个包。

现在,我的主目录里有一个单独的 .py 文件,用来启动我的程序,这样看起来整洁多了。

不过,我在尝试加载之前版本的程序生成的文件时遇到了问题。出现了“ImportError: No module named tools”的错误。我猜这是因为我的模块之前在主文件夹里,而现在它在 whyteboard.tools 里,而不是简单的 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 中启动了一段代码,来打开图形用户界面。这个 pickle 的问题在我重新组织文件夹之前是没有发生的。

8 个回答

30

我也遇到过这个问题,解决的方法是在加载pickle之前,把模块的新位置添加到sys.path里:

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

这可以通过一个自定义的“解包器”来实现,使用的是 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()

149

根据pickle的文档,要保存和恢复一个类的实例(其实函数也是一样),你需要遵循一些规则:

pickle可以透明地保存和恢复类的实例,但类的定义必须是可以导入的,并且要和对象被存储时在同一个模块里。

whyteboard.tools 不是tools“同一个模块”(虽然在同一个包里的其他模块可以通过import tools来导入它,但它在sys.modules中显示为sys.modules['whyteboard.tools']:这一点非常重要,否则在同一个包中导入的模块和在其他包中导入的模块会出现多个条目,可能会产生冲突!)。

如果你的pickle文件是比较好的/高级的格式(而不是为了兼容性而默认的旧ascii格式),在你进行这些更改后迁移它们可能并不简单,可能并不是像“编辑文件”那么容易(因为文件是二进制的等等...!),尽管其他答案可能这么说。我建议你做一个小的“pickle迁移脚本”:让它像这样修改sys.modules...:

import sys
from whyteboard import tools

sys.modules['tools'] = tools

然后用cPickle.load加载每个文件,接着用del sys.modules['tools']删除这个模块,再用cPickle.dump把每个加载的对象重新保存到文件里:在sys.modules中临时增加的这个条目应该能让pickle成功加载,然后再保存时应该使用正确的模块名来保存实例的类(删除那个额外的条目可以确保这一点)。

撰写回答