“RuntimeWarning: 未指定Pickled模型实例的Django版本”
这篇文章是来自2015年Python 2 Advent Calendar的第6天的文章。
如果将Django从1.7升级至1.8或更高版本,
RuntimeWarning: Pickled model instance's Django version is not specified.
obj = pickle.loads(value)
有时会显示类似的警告。
我以为使用Pickle对模型实例进行反序列化时一定会出现这个警告,但有时候却没有显示这个警告,因此我调查了一下原因,但并不清楚情况。
原因警告
简单来说,如果在Django 1.7之前的应用程序中对模型数据进行了序列化,然后在Django 1.8或更高版本的应用程序中进行反序列化,就会发生这种情况。
触发警告的代码在这里。
- https://github.com/django/django/blob/1.9/django/db/models/base.py#L518
可以使用virtualenv等工具创建一个安装了1.7、1.8/1.9版本的环境,并在同一个应用程序中切换版本,非常容易实现复制。
在 Django 1.7 的 virtualenv 中,可以通过以下方式设置缓存。
$ python manage.py shell
>>> from django.core.cache import caches
>>> from django.utils.six.moves import cPickle as pickle
>>> from myapp.models import MyModel
>>> obj = MyModel.objects.get(pk=1)
>>> caches['default'].set('my-model-instance', pickle.dumps(obj))
在Django 1.8的virtualenv环境中,可以通过以下方式从缓存中反序列化模型实例。
$ python manage.py shell
>>> from django.core.cache import caches
>>> from django.utils.six.moves import cPickle as pickle
>>> from myapp.models import MyModel
>>> pickle.loads(caches['default'].get('my-model-instance'))
<string>:1: RuntimeWarning: Pickled model instance's Django version is not specified.
处理方法
对于处理方法,可以考虑以下几种选项。
不理会
尽管无法确信,但从阅读Django源代码的注释来看,如果没有使用defer进行延迟加载的属性,则似乎没有影响。
此外,如果像Memcache一样具有挥发性的缓存,随着时间的推移,它将被新的缓存替换,问题应该会自然解决。
在升级Django版本时清除缓存。
我不推荐这个。
$ python manage.py shell
>>> from django.core.cache import caches
>>> caches['default'].clear()
如果在settings.py的CACHES设置中修改KEY_PREFIX或递增VERSION,几乎等同于完全清除缓存的效果。
只改变pickle数据缓存的键名。
这是我现在打算使用的方法。
通过将Django版本号作为缓存键,即使将来版本升级,也会自动使用不同版本的缓存。
>>> from django.core.cache import caches
>>> from django.utils.six.moves import cPickle as pickle
>>> from django.utils.version import get_version
>>> from myapp.models import MyModel
>>> obj = MyModel.objects.get(pk=1)
>>> caches['default'].set('{}:my-model-instance'.format(get_version()), pickle.dumps(obj))
只有在缓存pickle化的模型实例时才会成为问题,因此只要按照上述方式进行修正,即使升级到1.9以上的版本也不应该出现问题。