使用Python 3对Python 2对象进行反序列化

145

我想知道是否有一种方法可以在Python 3.4中加载用Python 2.4 pickle的对象。

我一直在运行2to3工具,使公司大量遗留代码更新到最新版本。

但是,在运行文件时,我遇到了以下错误:

  File "H:\fixers - 3.4\addressfixer - 3.4\trunk\lib\address\address_generic.py"
, line 382, in read_ref_files
    d = pickle.load(open(mshelffile, 'rb'))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 1: ordinal
not in range(128)

看到有争议的腌制对象,它是一个包含类型为str的键和值的dict字典中的dict

所以我的问题是:是否有一种方法可以使用Python 3.4加载最初在Python 2.4中腌制的对象?


1
Python 2.4有json模块吗?也许你可以编写一个2.4脚本,将对象反序列化并保存为JSON对象,然后编写一个3.4脚本,读取JSON对象并将其保存为3.4兼容的pickle对象。这将是一个一次性操作,您可以在所有pickle文件上运行它。 - Kevin
我也在考虑类似的想法,考虑到这些是字典,我认为我可以将sys.stdout更改为文件并将它们打印出来,但我想先看看是否可以加载它们。 - NDevox
与日期时间有关的相关问题:https://dev59.com/m4Hba4cB1Zd3GeqPTZ36 - John Y
2个回答

209

你需要告诉 pickle.load() 如何将Python的bytestring数据转换为Python 3字符串,或者你可以告诉 pickle 将它们保留为bytes。

默认情况下,尝试将所有字符串数据解码为ASCII,如果解码失败,则会看到 pickle.load() 文档

可选关键字参数是 fix_importsencodingerrors,用于控制兼容性支持,以处理由 Python 2 生成的 pickle 流。如果 fix_imports 为 True,则 pickle 将尝试将旧的 Python 2 名称映射到 Python 3 中使用的新名称。encodingerrors 告诉 pickle 如何解码 Python 2 pickled 的 8 位字符串实例;默认值分别为“ASCII”和“strict”。可以将 encoding 设置为“bytes”以将这些 8 位字符串实例读取为bytes对象。

将编码设置为 latin1 可以直接导入数据:

with open(mshelffile, 'rb') as f:
    d = pickle.load(f, encoding='latin1') 

但您需要验证没有使用错误的编解码器对字符串进行解码;Latin-1适用于任何输入,因为它将字节值0-255直接映射到前256个Unicode代码点。

另一种选择是使用encoding='bytes'加载数据,然后在解码所有bytes键和值之后进行操作。

请注意,在Python版本3.6.8、3.7.2和3.8.0之前,除非使用encoding='bytes',否则无法正确反序列化Python 2的datetime对象数据


1
如何使其与Python 2向后兼容?显然,Python 2中不存在编码参数。 - EpicAdv
3
@EpicAdv:你不需要将此代码兼容Python 2;这个问题是关于如何在Python 3中加载Python 2的pickle文件。对于Python 2,完全不需要使用encoding关键字。 - Martijn Pieters
11
你可以创建一个名为 "pickle_options" 的字典,在Python 2中为一个空字典,在Python 3中则包含'encoding': 'latin1'的键值对。然后,将 **pickle_options 作为参数传递给 pickle 函数。这样就可以在两个 Python 版本中都运行。 - pipefish
1
@pipefish - 聪明,但是某个地方你必须检测你正在使用哪个版本,所以你也可以更直接地根据版本不同使用不同的调用方式(一个包含额外参数,一个不包含)。但至少你理解了EpicAdv评论的要点,而Martijn的评论并没有涉及到这一点。 - John Y
2
我知道datetime的留言不是这个回答的主要重点,但是为了未来的读者,我想指出即使Python 3的“固定”版本仍然需要encoding='latin-1'来取消封存Python 2的日期时间。如果您的封存Python 2数据恰好包括编码为Latin-1以外的字节串和日期时间,那么仍然最好使用encoding='bytes' - John Y

19

如果您的对象中包含了numpy数组,使用encoding='latin1'会导致一些问题。

使用encoding='bytes'将更好。

请参见此答案,获取有关使用encoding='bytes'的完整解释。


哪些问题?我应该注意什么?使用bytes将字符串转换为bytes(),因此如果可能的话,我更喜欢使用latin1,但我不清楚问题是什么。 - Gulzar
2
@sreeragh-a-r:你能举个例子说明你遇到的问题吗?我有一个二维的numpy.ndarray(numpy 1.14),在Python 2.7中使用cPickle.dumps()进行了pickle,然后在Python 3中使用pickle.loads(..., encoding='latin1')进行了unpickle,一切都正常。 - djvg
@djvg 当我需要将图像作为图像字符串进行pickle和unpickle时,我遇到了问题。代码可以在这里找到。 https://gist.github.com/sreeragh-ar/70205db3a43badbfa69f758faa898be3 - Sreeragh A R
@Gulzar 请查看上面的gist以获取问题。在反序列化后,图像变得损坏了。 - Sreeragh A R
如果您没有使用np.arrays,请节省一些麻烦并保持encoding ='latin1',这样您就不必将所有的bytes解码为str - jboxxx
如果您的字符串只包含ASCII字符,则使用@jboxxx,否则您将需要使用字节。 - Javed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接