在测试中,Pickle无法将对象存储在Django locmem缓存中?

2 投票
4 回答
3240 浏览
提问于 2025-04-18 09:55

有些事情让我有点困惑...

>>> 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 个回答

0

示例代码

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 环境下执行代码时出现。 :(

0

在Martijn的这个后续问题的帮助下,简单的回答是:

“没错”。

你不能把Mock()对象进行序列化,因为它们并没有提供它们所模拟的顶层对象,所以序列化工具(pickle)根本不知道该从哪里导入这些对象。由于缓存需要将对象序列化以便存储,因此无法在LocMemCache中存储Mock()实例。得重新考虑一下我该如何进行测试。

7

问题在于,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

2

这个问题发生是因为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)

撰写回答