7

我正在尝试在 iPython 中加载腌制对象。

我得到的错误是:

AttributeError:“FakeModule”对象没有属性“World”

任何人都知道如何让它工作,或者至少是在 iPython 中加载对象以便交互式浏览它们的解决方法?

谢谢

编辑添加:

我有一个名为 world.py 的脚本,它基本上可以:

import pickle
class World:
    ""
if __name__ == '__main__':
    w = World()
    pickle.dump(w, open("file", "wb"))

比在 REPL 中我做的:

import pickle  
from world import World  
w = pickle.load(open("file", "rb"))

它适用于 vanilla python REPL 但不适用于 iPython。

我正在使用来自 Enthought Python Distribution 的 Python 2.6.5 和 iPython 0.10,但我也遇到了以前版本的问题。

4

2 回答 2

12

看起来您在FakeModule腌制数据和尝试取消腌制数据之间进行了修改:具体来说,您已经从该模块中删除了一些名为的顶级对象World(可能是一个类,也可能是一个函数)。

Pickling “按名称”序列化类和函数,因此它们必须是模块顶层的名称,并且不得修改该模块(至少不能以这种方式严重影响这些名称 -绝对不能通过从模块!)在酸洗时间和解酸时间之间。

一旦您确切地确定了您所做的哪些更改阻碍了解封,如果由于其他原因您不能只恢复更改,它通常会被黑客攻击。例如,如果您刚刚World从移动FakeModuleCoolModule,请执行以下操作:

import FakeModule
import CoolModule
FakeModule.World = CoolModule.World

就在 unpickling 之前(记得用新的结构再次 pickle,这样你就不必每次 unpickle 时都重复这些 hack;-)。

编辑:OP 对 Q 的编辑使他的错误更容易理解。由于他现在正在测试 if __name__equals '__main__',因此很明显 pickle 在编写时将保存 class 的对象__main__.World。由于他使用的是 ASCII 泡菜(顺便说一下,对于性能和磁盘空间来说,这是一个非常糟糕的选择),因此检查起来很简单:

$ cat file
(i__main__
World
p0
(dp1

正在查找的模块是(清晰而明显)__main__。现在,甚至不用打扰 ipython,而是使用一个简单的 Python 交互式解释器:

$ py26
Python 2.6.5 (r265:79359, Mar 24 2010, 01:32:55) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import world
>>> import pickle
>>> pickle.load(open("file", "rb"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/pickle.py", line 1370, in load
    return Unpickler(file).load()
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/pickle.py", line 858, in load
    dispatch[key](self)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/pickle.py", line 1069, in load_inst
    klass = self.find_class(module, name)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/pickle.py", line 1126, in find_class
    klass = getattr(mod, name)
AttributeError: 'module' object has no attribute 'World'
>>> 

该错误很容易重现,其原因也很明显:执行类名查找的模块(即__main__)确实没有名为“World”的属性。模块world确实有一个,但是正如我在答案的前一部分中解释的那样,OP 没有“连接点”,在腌制文件需要它的模块中放置一个具有正确名称的引用。那是:

>>> World = world.World
>>> pickle.load(open("file", "rb"))
<world.World instance at 0xf5300>
>>> 

当然,现在这可以完美地工作(正如我之前所说的那样)。也许 OP 没有看到这个问题,因为他使用了我讨厌的导入形式from world import World(直接从模块内导入函数或类,而不是模块本身)。

在 ipython 中解决问题的方法与底层 Python 架构完全相同——只需要多几行代码,因为 ipython 提供所有额外的服务,并没有使模块__main__直接可用于直接记录什么发生在交互式命令行上,而是插入一个(称为 FakeModule,正如 OP 从错误消息中发现的那样;-)并用它做黑魔法以“酷”&c。尽管如此,当您想直接访问具有给定名称的模块时,这在 Python 中是非常简单的,当然:

In [1]: import world

In [2]: import pickle

In [3]: import sys

In [4]: sys.modules['__main__'].World = world.World

In [5]: pickle.load(open("file", "rb"))
Out[5]: <world.World instance at 0x118fc10>

In [6]: 

要记住的第一课:避免使用黑魔法,至少除非并且直到你作为巫师的学徒足够好,能够发现并解决其偶尔失控的情况(否则,那些提桶的扫帚最终可能会淹没世界当你小睡时;-)。

或者,另类阅读:要正确使用某个抽象层(例如 ipython 放在 Python 之上的“酷”抽象层),您需要深入了解底层(这里是 Python 本身及其核心机制,例如 pickling 和 sys .modules)。

第二课:由于你编写它的方式,pickle 文件本质上是损坏的,因为它只能在模块__main__有一个按 name 的类时加载Word,当然如果没有上面的一些技巧,它通常不会有。pickle 文件应该将类记录为存在于 module 中world。如果您绝对认为必须if __name__ == '__main__':在 中的子句上生成文件world.py,则为此目的使用一些冗余:

import pickle
class World:
    ""
if __name__ == '__main__':
    import world
    w = world.World()
    pickle.dump(w, open("file", "wb"))

这可以正常工作并且没有 hack(至少如果您遵循 Python 最佳实践,即在模块顶层永远不会有任何实质性代码——只有导入、类、def 和琐碎的赋值——其他一切都属于函数;如果你没有t 遵循这个最佳实践,然后编辑你的代码来这样做,它会让你在灵活性和性能方面快乐)。

于 2010-08-07T18:03:26.257 回答
2

当您使用 腌制模块时,w来自模块的事实记录在的第一行:__main__pickle.dump(w, open("file", "wb"))w__main__file

% xxd file
0000000: 2869 5f5f 6d61 696e 5f5f 0a57 6f72 6c64  (i__main__.World
0000010: 0a70 300a 2864 7031 0a62 2e              .p0.(dp1.b.

当 IPython 尝试 unpicklefile时,它​​会执行以下几行:

/usr/lib/python2.6/pickle.pyc in find_class(self, module, name)
   1124         __import__(module)
   1125         mod = sys.modules[module]
-> 1126         klass = getattr(mod, name)
   1127         return klass
   1128 

特别是,它尝试执行__import__('__main__'). 如果你在 REPL 中尝试,你会得到

In [29]: fake=__import__('__main__')

In [32]: fake
Out[32]: <module '__main__' from '/usr/lib/pymodules/python2.6/IPython/FakeModule.pyc'>

这是FakeModuleIPython 在 AttributeError 中提到的。

如果你往里看,fake.__dict__你会发现它不包括World,即使你在 .from test import World之前或之后说__import__

如果你跑

In [35]: fake.__dict__['World']=World

然后pickle.load将工作:

In [37]: w = pickle.load(open("file", "rb"))

可能有更清洁的方法;我不知道。您能想到的任何方式都应该放在World命名空间中fake

PS。2008 年,IPython 的创建者 Fernando Perez就这个问题写了一点。他可能以某种方式解决了这个问题,以避免我的肮脏黑客行为。您可能想在 IPython 用户邮件列表中询问,或者,也许更简单,只是不要在__main__命名空间内腌制。

于 2010-08-08T15:30:06.990 回答