“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以上的版本也不应该出现问题。

广告
将在 10 秒后关闭
bannerAds