Python Pickle中的命名空间

7 投票
1 回答
2667 浏览
提问于 2025-04-17 05:10

我在使用pickle和unittest的时候遇到了一个错误。

我写了三个程序文件:

  1. 一个用于定义要被pickle的类,
  2. 一个使用第一个类的类,
  3. 一个用于测试第二个类的unittest。

这三个程序的代码分别如下。

#1. ClassToPickle.py

import pickle
class ClassToPickle(object):
    def __init__(self, x):
        self.x = x
if __name__=="__main__":
    p = ClassToPickle(10)
    pickle.dump(p, open('10.pickle', 'w'))

#2. SomeClass.py

from ClassToPickle import ClassToPickle
import pickle

class SomeClass(object):
    def __init__(self):
        self.pickle = pickle.load(open("10.pickle", 'r'))
        self.x = self.pickle.x
        print self.x

if __name__ == "__main__":
    SomeClass()

#3. SomeClassTest.py

import unittest
from SomeClass import SomeClass
from ClassToPickle import ClassToPickle # REQUIRED_LINE

class SomeClassTest(unittest.TestCase):
    def testA(self):
        sc = SomeClass()
        self.assertEqual(sc.x, 10)

def main():
    unittest.main()

if __name__ == "__main__":
    main()

我先运行了第一个程序来生成pickle文件。
然后,当我单独运行第二个程序(也就是输入“python SomeClass.py”)时,它能正常工作。
而且,当我单独运行第三个程序(也就是输入“python SomeClassTest.py”)时,它也能正常工作。

但是,当我在eclipse+pydev中以“单元测试”的方式运行第三个程序时,出现了下面的错误信息。

======================================================================
ERROR: testA (SomeClassTest.SomeClassTest)
----------------------------------------------------------------------
Traceback (most recent call last):
$ File "/home/tmp/pickle_problem/SomeClassTest.py", line 9, in testA
sc = SomeClass()
$ File "/home/tmp/pickle_problem/SomeClass.py", line 8, in init
self.pickle = pickle.load(open("10.pickle", 'r'))
$ File "/usr/lib/python2.7/pickle.py", line 1378, in load
return Unpickler(file).load()
$ File "/usr/lib/python2.7/pickle.py", line 858, in load
dispatchkey
File "/usr/lib/python2.7/pickle.py", line 1090, in load_global
klass = self.find_class(module, name)
$ File "/usr/lib/python2.7/pickle.py", line 1126, in find_class
klass = getattr(mod, name)
$ AttributeError: 'module' object has no attribute 'ClassToPickle'

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (errors=1)

而且,当我注释掉导入ClassToPickle类的那一行(在第三个程序的第3行,注释为“REQUIRED_LINE”)时,它也不工作,并且返回了下面的错误信息。

E
======================================================================
ERROR: testA (main.SomeClassTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "SomeClassTest.py", line 9, in testA
sc = SomeClass()
File "/home/tmp/pickle_problem/SomeClass.py", line 8, in init
self.pickle = pickle.load(open("10.pickle", 'r'))
File "/usr/lib/python2.7/pickle.py", line 1378, in load
return Unpickler(file).load()
File "/usr/lib/python2.7/pickle.py", line 858, in load
dispatchkey
File "/usr/lib/python2.7/pickle.py", line 1090, in load_global
klass = self.find_class(module, name)
File "/usr/lib/python2.7/pickle.py", line 1126, in find_class
klass = getattr(mod, name)
AttributeError: 'module' object has no attribute 'ClassToPickle'

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

我猜这个问题和Python中的命名空间有关,但我不知道具体发生了什么,也不知道该怎么解决。

我该如何正确地在eclipse+pydev中以“单元测试”的方式运行第三个程序,
以及如何在命令行中运行第三个程序而不包含导入ClassToPickle的那一行?
请帮帮我。

1 个回答

9

这是因为 __main__.ClassToPickle != ClassToPickle.ClassToPickle,可以这样理解:

当你在 ClassToPickle.py 脚本中对 ClassToPickle 的实例进行了“腌制”(也就是序列化),pickle 模块会把这个类的所有引用都腌制起来。这意味着它会把定义这个类的模块名称也腌制进去。由于你执行了 ClassToPickle.py 这个脚本,所以这个模块会被标记为 __main__,这就导致 pickle 模块会腌制 __main__.ClassToPickle

当你尝试加载这个腌制的实例时,会失败,因为它找不到实例的类是 __main__.ClassToPickle,而不是你通过 from ClassToPickle import ClassToPickle 导入的那个类,因为后者是 ClassToPickle.ClassToPickle

解决这个问题的方法是创建另一个脚本来处理腌制,而不是在 ClassToPickle.py 中进行,例如:

import pickle

from ClassToPickle import ClassToPickle

if __name__=="__main__":
    p = ClassToPickle(10)
    pickle.dump(p, open('10.pickle', 'w'))

撰写回答