如何在 Python 3 中腌制和取消腌制到可移植字符串

Posted

技术标签:

【中文标题】如何在 Python 3 中腌制和取消腌制到可移植字符串【英文标题】:How to pickle and unpickle to portable string in Python 3 【发布时间】:2015-08-08 18:40:40 【问题描述】:

我需要将 Python3 对象腌制为我想从 Travis CI 构建中的环境变量中解压缩的字符串。问题是我似乎无法在 Python3 中找到一种方法来腌制可移植字符串(unicode):

import os, pickle    

from my_module import MyPickleableClass


obj = 'cls': MyPickleableClass, 'other_stuf': '(...)'

pickled = pickle.dumps(obj)

# raises TypeError: str expected, not bytes
os.environ['pickled'] = pickled

# raises UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbb (...)
os.environ['pickled'] = pickled.decode('utf-8')

pickle.loads(os.environ['pickled'])

有没有办法将复杂的对象(如datetime.datetime)序列化为 unicode 或 Python3 中的其他字符串表示形式,我可以将其传输到不同的机器并反序列化?

更新

我已经测试了@kindall 建议的解决方案,但pickle.dumps(obj, 0).decode() 引发了UnicodeDecodeError。尽管如此,base64 方法仍然有效,但它需要一个额外的解码/编码 步骤。该解决方案适用于 Python2.x 和 Python3.x。

# encode returns bytes so it needs to be decoded to string
pickled = pickle.loads(codecs.decode(pickled.encode(), 'base64')).decode()

type(pickled)  # <class 'str'>

unpickled = pickle.loads(codecs.decode(pickled.encode(), 'base64'))

【问题讨论】:

类似 JSON 的东西? 是的,如果可能的话,我更喜欢像 JSON 这样更安全的格式。 Pickles 与可执行代码一样好,并且从 envvar 中运行任意代码对我来说似乎很脏,即使由于应用程序的原因,它目前还不是安全漏洞。在您真正需要这种灵活性之前,不要诉诸泡菜;你当然不喜欢datetime 【参考方案1】:

pickle.dumps() 产生一个bytes 对象。期望这些任意字节是有效的 UTF-8 文本(您通过尝试将其解码为 UTF-8 字符串所做的假设)是相当乐观的。如果成功了,那就是巧合了!

一种解决方案是使用完全使用 ASCII 字符的旧式酸洗协议。这仍然显示为bytes,但由于它是纯 ASCII 格式,因此可以毫无压力地解码为字符串:

pickled = pickle.dumps(obj, 0).decode()

您还可以使用其他一些编码方法将二进制腌制对象编码为文本,例如 base64:

import codecs
pickled = codecs.encode(pickle.dumps(obj), "base64").decode()

然后解码将是:

unpickled = pickle.loads(codecs.decode(pickled.encode(), "base64"))

pickle 与协议 0 一起使用似乎会导致比 base64 编码二进制泡菜更短的字符串(并且 abarnert 建议的十六进制编码将比 base64 更大),但我没有严格测试它或任何东西.用你的数据测试一下看看。

【讨论】:

如果空间效率真的很重要,您肯定希望尽可能使用 pickle 协议 4 和 environb,或者甚至可能使用 pickle 协议 4 和 bzip。对于 Windows,我会测试 pickle 协议 4 加上 bzip 加上 base64 与 pickle 协议 0,但我的猜测是前者更小。但我假设有几百字节的内存/带宽/等。无论哪种方式,每个 CI 构建都不值得担心。 @abarnert 第一个解决方案不适用于协议 4,仅适用于 0,即 ascii,因此可以解码。另一个使用 base64 的解决方案可以使用任何协议,默认情况下使用 pickle.DEFAULT_PROTOCOL,在 py3 中为 3,从 py3.8 开始为 4。 谢谢,你的回答是最 Pythonic 的。【参考方案2】:

如果您想在环境中存储字节,而不是编码文本,这就是 environb 的用途。

这在 Windows 上不起作用。 (正如文档所暗示的那样,如果您使用的是 3.2+,则应该检查 os.supports_bytes_environ,而不是仅仅假设 Unix 可以,而 Windows 不会……)因此,您需要将字节走私到可以无论您的系统编码是什么,例如使用backslash-escape,甚至hex,都已编码。所以,例如:

if os.supports_bytes_environ:
    environb['pickled'] = pickled
else:
    environ['pickled'] = codecs.encode(pickled, 'hex')

【讨论】:

environb 是个不错的技巧。我不知道那个。 不幸的是,由于空字节,您仍然无法将纯二进制文件放入environb 分配给environ 项目只是为了示例。我真正需要的是通过 web 表单将序列化的字符串放入 Travis CI 环境变量中。 @PeterHudec:好在这只是为了举例,因为 bobince 是对的;这通常不会起作用。【参考方案3】:

我认为最简单的答案,特别是如果您不关心 Windows,就是按照my other answer 中的建议将字节存储在环境中。

但是,如果您想要一些干净且可调试的东西,那么使用设计为基于文本格式的东西可能会更开心。

pickle 确实有一个“纯文本”协议 0,如 kindall's answer 中所述。它肯定比协议 3 或 4 更具可读性,但它仍然不是我真正想要阅读的内容。

JSON 更好,但它无法处理开箱即用的datetime。您可以为需要编码的少数类型提供自己的编码(stdlib 的json 模块是可扩展的),或者使用类似jsonpickle 的东西。为您关心的每种类型提供自定义编码通常比picklejsonpickle 等一般的“在图灵完备协议中打包任意类型”方案更安全、更高效、更易读,但当然是还有更多的工作,特别是如果你有很多额外的类型。

JSON Schema 允许您在 JSON 中定义语言,类似于您在 XML 中所做的。它带有内置的date-timeString format,Python 的jsonschema 库知道如何使用它。

YAML 有一个标准扩展存储库,其中包含许多 JSON 没有的类型,包括 timestamp。大多数the zillion 'yaml' modules for Python 已经知道如何将datetime 对象与这种类型进行编码。如果您需要 YAML 之外的其他类型,它被设计为可声明式扩展。如果你真的需要的话,还有一些库相当于jsonpickle,可以动态定义新类型。

最后,您始终可以编写 XML 语言。

【讨论】:

我知道 JSON,但我需要序列化只是为了配置。我有一个复杂的配置字典,其中包含我需要传输到 Travis CI 环境的敏感数据的类和日期时间实例。在这种情况下,我不关心性能。 @PeterHudec:“仅用于配置”正是 JSON 或 YAML 适合的类型。它易于调试、人工可编辑,并且具有足够的可扩展性,无需无限扩展。

以上是关于如何在 Python 3 中腌制和取消腌制到可移植字符串的主要内容,如果未能解决你的问题,请参考以下文章

在 python 3.6+ 和 PyQt5 中腌制一个 QPolygon

如何在 python 中腌制一个动态创建的嵌套类?

Python - 如何使这个不可腌制的对象可腌制?

在腌制字典中检索单个对象的问题(Python 3)

Python:用一些不可腌制的物品腌制一个字典

如何在 Python 中最好地保存多个类实例?更新:如何腌制包含 osgeo.ogr 对象的类实例?