在测试中,Pickle无法将对象存储在Django locmem缓存中?
有些事情让我有点困惑...
>>> from django.core.cache import get_cache
>>>
>>> cache = get_cache('django.core.cache.backends.locmem.LocMemCache')
>>>
>>> # Set the 'content' cache key to a string
>>> cache.set('content', 'a string')
>>> cache.get('content')
'a string'
>>>
>>> class TestObj(object):
... pass
>>>
>>> a = TestObj()
>>> cache.set('content', a)
>>>
>>> # cache hasn't updated...
>>> cache.get('content')
'a string'
>>>
>>> cache.set('content', 1)
>>> # this is fine however..
>>> cache.get('content')
1
>>>
好吧,缓存出于某种原因不接受对象。
# in locmem.py, set() method
try:
pickled = pickle.dumps(new_value, pickle.HIGHEST_PROTOCOL)
self._cache[key] = pickled
except pickle.PickleError:
pass
这就是原因,很明显是遇到了PickleError。
>>> import pickle
>>> pickled = pickle.dumps(a, pickle.HIGHEST_PROTOCOL)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/usr/lib/python2.7/pickle.py", line 1374, in dumps
Pickler(file, protocol).dump(obj)
File "/usr/lib/python2.7/pickle.py", line 224, in dump
self.save(obj)
File "/usr/lib/python2.7/pickle.py", line 331, in save
self.save_reduce(obj=obj, *rv)
File "/usr/lib/python2.7/pickle.py", line 396, in save_reduce
save(cls)
File "/usr/lib/python2.7/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.7/pickle.py", line 748, in save_global
(obj, module, name))
PicklingError: Can't pickle <class 'TestObj'>: it's not found as __builtin__.TestObj
没错,但为什么会这样呢?在python控制台里一切正常,但在django的命令行里就不行?
# Works fine in python shell...
>>> import pickle
>>> class TestObj(object):
... pass
...
>>> testobj = TestObj()
>>> pickled = pickle.dumps(testobj, pickle.HIGHEST_PROTOCOL)
>>> pickled
'\x80\x02c__main__\nTestObj\nq\x00)\x81q\x01}q\x02b.'
>>>
这个问题出现是因为我试图把一个Mock()对象存储到缓存里进行测试。不太确定我这样做是不是错了...
4 个回答
示例代码
import pickle # or from django.utils.six.moves import cPickle as pickle
lass TestObj(object):
pass
testobj = TestObj()
pickled = pickle.dumps(testobj, pickle.HIGHEST_PROTOCOL)
pickled
'\x80\x02c__main__\nTestObj\nq\x00)\x81q\x01}q\x02b.'
我有点搞不懂一个情况,当我用“python manage.py shell”打开一个控制台会话,然后执行代码时,出现了_pickle.PicklingError: 无法序列化:查找属性 builtins.TestObj 失败的错误。
但是当我打开一个单独的 Python 控制台并执行相同的代码时,一切都正常!这是为什么呢?
我还注意到,如果我在单独的控制台中导入from django.utils.six.moves import cPickle as pickle,也能正常工作。这个问题只在 Django 环境下执行代码时出现。 :(
在Martijn的这个后续问题的帮助下,简单的回答是:
“没错”。
你不能把Mock()对象进行序列化,因为它们并没有提供它们所模拟的顶层对象,所以序列化工具(pickle)根本不知道该从哪里导入这些对象。由于缓存需要将对象序列化以便存储,因此无法在LocMemCache中存储Mock()实例。得重新考虑一下我该如何进行测试。
问题在于,pickle是通过引用来序列化类的。那么,能不能用一个更好的序列化工具,它是通过序列化类的定义而不是引用来进行的呢?这样你就可以序列化一个模拟对象,这个对象会序列化类的源代码,然后你就可以把它传递给django的缓存。我是dill
的作者,它是一个更好的序列化工具……同时也是klepto
的作者,后者是一个缓存包……这正是我用来在SQL表、磁盘或内存缓存中存储任何对象的方法。
基本上(虽然我没有尝试过,但根据我自己缓存包的经验猜测它能工作),应该是这样的:
>>> from django.core.cache import get_cache
>>> import dill
>>>
>>> cache = get_cache('django.core.cache.backends.locmem.LocMemCache')
>>>
>>> # Set the 'content' cache key to a string
>>> cache.set('content', dill.dumps('a string'))
>>> dill.loads(cache.get('content'))
'a string'
>>>
>>> class TestObj(object):
... pass
>>>
>>> a = TestObj()
>>> cache.set('content', dill.dumps(a))
>>>
>>> dill.loads(cache.get('content'))
<__main__.TestObj object at 0x10235e510>
>>>
>>> # this is pickling classes w/o using a reference
>>> dill.dumps(a)
'\x80\x02cdill.dill\n_create_type\nq\x00(cdill.dill\n_load_type\nq\x01U\x08TypeTypeq\x02\x85q\x03Rq\x04U\x07TestObjq\x05h\x01U\nObjectTypeq\x06\x85q\x07Rq\x08\x85q\t}q\n(U\r__slotnames__q\x0b]q\x0cU\n__module__q\rU\x08__main__q\x0eU\x07__doc__q\x0fNutq\x10Rq\x11)\x81q\x12}q\x13b.'
>>> # and here's using a reference, which is exactly how pickle does it
>>> dill.dumps(a, byref=True)
'\x80\x02c__main__\nTestObj\nq\x00)\x81q\x01}q\x02b.'
如果你想自己试试,可以在这里获取dill
(和klepto
):https://github.com/uqfoundation
这个问题发生是因为Django的LocMemCache默认使用cPickle,而不是pickle。你可以在LocMemCache类中看到这一点:
try:
from django.utils.six.moves import cPickle as pickle
except ImportError:
import pickle
如果你在命令行中尝试这样做:
from django.utils.six.moves import cPickle as pickle
testobj = TestObj()
pickled = pickle.dumps(testobj, pickle.HIGHEST_PROTOCOL)
你会遇到同样的错误。
作为一个可能的解决办法,我建议你在测试中手动使用pickle来打包对象,然后再使用cache.set():
a = TestObj()
pickled = pickle.dumps(a, pickle.HIGHEST_PROTOCOL)
cache.set('content', pickled)