自定义pickle行为以兼容旧版
Python的copy_reg
模块可以让你注册自定义的序列化和反序列化方法。我的理解是,我只能对在注册了自定义序列化/反序列化方法之后序列化的对象,来定制它们的反序列化行为,对吗?
举个例子:
import pickle, copy_reg
class C(object):
pass
legacy_c_ser = pickle.dumps(C())
def reduce_C(obj):
print('reduce_C called')
tpl = obj.__reduce__()
tpl = (load_C, ) + tpl[1:]
return tpl
def load_C(*tpl):
print('load_C called')
return C()
copy_reg.constructor(load_C)
copy_reg.pickle(C, reduce_C, load_C)
new_c_ser = pickle.dumps(C())
# load_C is called
pickle.loads(new_c_ser)
# load_C is not called
pickle.loads(legacy_c_ser)
看起来,copy_reg
的工作原理是简单地在序列化格式中替换构造函数:
>>> print(legacy_c_ser)
'ccopy_reg\n_reconstructor\np0\n(c__main__\nC\np1\[...]'
>>> print(new_c_ser)
'c__main__\nload_C\np0[...]'
我写自己的pickle.Unpickler
类是唯一能定制旧的序列化文件反序列化行为的方法吗?我不想这样做,因为我更喜欢使用cPickle
而不是pickle
,因为后者效率更高。
我的问题是,我有一些来自第三方库的序列化对象,而当我升级这个库时,序列化格式发生了变化。
1 个回答
1
Pickle 是一个在不同版本的 Python 之间应该是向后兼容的工具(我们暂时不讨论 Python 2.x 和 3.x 的区别)。所以当你说 Pickle 的格式发生了变化时,你是指这个第三方库注册它们的类(或者其他对象)的方法发生了变化,对吧?
如果是这样的话……为了让这个工作正常,你需要做一些小手脚。首先,你需要获取旧的类定义的源代码,当你获取原始的 Pickle 数据时,你需要把里面的类引用改成指向旧版本类的代码路径。这部分应该是明文的(即使是在 HIGHEST_PROTOCOL
中),所以抓取并编辑 Pickle 字符串的这一部分应该没问题。然后你就可以把旧的对象反序列化出来,但它们会指向旧的类定义。为了把你反序列化出来的旧对象转换成新的类实例,你需要一个“格式转换器”——基本上就是创建新的类实例,并从旧的类实例中提取相关的状态信息。