自定义pickle行为以兼容旧版

0 投票
1 回答
2454 浏览
提问于 2025-04-19 18:48

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 字符串的这一部分应该没问题。然后你就可以把旧的对象反序列化出来,但它们会指向旧的类定义。为了把你反序列化出来的旧对象转换成新的类实例,你需要一个“格式转换器”——基本上就是创建新的类实例,并从旧的类实例中提取相关的状态信息。

撰写回答