更改模块目录后的 Python 酸洗

Posted

技术标签:

【中文标题】更改模块目录后的 Python 酸洗【英文标题】:Python pickling after changing a module's directory 【发布时间】:2011-01-08 11:26:05 【问题描述】:

我最近更改了程序的目录布局:以前,我将所有模块都放在“主”文件夹中。现在,我已经将它们移动到以程序命名的目录中,并在其中放置了一个__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。在重新组织目录之前,绝对不会发生这种酸洗问题。

【问题讨论】:

也许你可以在 pickle 加载之前将你的模块添加到 pythonpath (sys.path.append(path_to_your_module))? 【参考方案1】:

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

pickle 可以保存和恢复类 透明的实例,但是 类定义必须是可导入的 并与何时生活在同一个模块中 对象已存储

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

如果您的 pickle 文件采用良好/高级格式(与仅出于兼容性原因而默认使用的旧 ascii 格式相反),则在执行此类更改后迁移它们实际上可能不是尽管另一个答案暗示了什么,但与“编辑文件”(这是二进制的&c ...!)一样微不足道。相反,我建议你制作一个“pickle-migrating script”:让它像这样修补sys.modules...:

import sys
from whyteboard import tools

sys.modules['tools'] = tools

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

【讨论】:

我已经尝试过了,但 why is it not working 对我有用吗? :// 删除 sys.modules 条目后转储不起作用。它给出了Can't pickle 的错误,因为缺少模块。有什么我想念的吗? 非常感谢! 就像一个魅力!谢谢! 如果这对其他人有帮助 - 在我的情况下,它移动到不同的包但在层次结构中,所以它从 my_old_module.h1.h2 移动到 my_new_module.h1.h2 所以我不得不同时覆盖 my_old_module 到指向my_new_module 也指向my_old_module.h1.h2 指向my_new_module.h1.h2【参考方案2】:

这可以通过使用find_class() 的自定义“unpickler”来完成:

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()

【讨论】:

也许老了,但谢谢你。当我遇到类似问题时,我真的很头疼。 感谢您发布此解决方案。适用于 Python 3.7.x。这节省了很多工作。虽然 Ranch 发布的解决方案足以读取“错位”模块的类,但该解决方案允许对此类模块进行编程控制和转换, 如果有人想知道here 是我关于np.load 的完整问题。任何提示将不胜感激。【参考方案3】:

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

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

【讨论】:

f 的值是多少? f是打开的pickle文件【参考方案4】:

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

【讨论】:

这期间有变化吗?无法重现您的示例。 (虽然使用的是 py3) @bariod:是的,实际上...现在您需要在对loads 的调用中提供ignore=True 以使用存储的类。如果不这样做,它会引用 main 中定义的任何 Foo(如果存在)(并且仅在 main 中不存在 Foo 时使用存储的 Foo)。【参考方案5】:

这是pickle的正常行为,unpickle的对象需要有自己的defining module importable。

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

【讨论】:

【参考方案6】:

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

import sys
sys.path.append('path/to/folder containing the python module')

【讨论】:

以上是关于更改模块目录后的 Python 酸洗的主要内容,如果未能解决你的问题,请参考以下文章

python文件和目录操作方法大全(含更改文件夹下所有文件名称的实例)

如何知道/更改 Python shell 中的当前目录?

使用带有cythonized类的dask。酸洗对象方法时出错

cython 嵌入后的 ImportError

使用 ORTools 实现自定义酸洗代码

使用ORTools实现自定义酸洗代码