卓越飞翔博客卓越飞翔博客

卓越飞翔 - 您值得收藏的技术分享站
技术文章17227本站已运行3325

Python 对象序列化和反序列化:第 2 部分

Python 对象序列化和反序列化:第 2 部分

这是有关序列化和反序列化 Python 对象的教程的第二部分。在第一部分中,您学习了基础知识,然后深入研究了 Pickle 和 JSON 的细节。

在这一部分中,您将探索 YAML(确保拥有第一部分中的运行示例),讨论性能和安全注意事项,了解其他序列化格式,最后了解如何选择正确的方案。 p>

YAML

YAML 是我最喜欢的格式。它是一种人性化的数据序列化格式。与 Pickle 和 JSON 不同,它不是 Python 标准库的一部分,因此您需要安装它:

pip 安装 yaml

yaml模块只有load()dump()函数。默认情况下,它们使用像 loads()dumps() 这样的字符串,但可以采用第二个参数,它是一个开放流,然后可以转储/加载到/来自文件。

'
import yaml



print yaml.dump(simple)



boolean: true

int_list: [1, 2, 3]

none: null

number: 3.44

text: string

请注意 YAML 与 Pickle 甚至 JSON 相比的可读性。现在是 YAML 最酷的部分:它理解 Python 对象!无需自定义编码器和解码器。以下是使用 YAML 的复杂序列化/反序列化:

'
> serialized = yaml.dump(complex)

> print serialized



a: !!python/object:__main__.A

  simple:

    boolean: true

    int_list: [1, 2, 3]

    none: null

    number: 3.44

    text: string

when: 2016-03-07 00:00:00



> deserialized = yaml.load(serialized)

> deserialized == complex

True

如您所见,YAML 有自己的符号来标记 Python 对象。输出仍然非常容易阅读。日期时间对象不需要任何特殊标记,因为 YAML 本质上支持日期时间对象。

性能

在开始考虑性能之前,您需要考虑性能是否是一个问题。如果您相对不频繁地序列化/反序列化少量数据(例如在程序开始时读取配置文件),那么性能并不是真正的问题,您可以继续前进。

但是,假设您分析了系统并发现序列化和/或反序列化导致性能问题,则需要解决以下问题。

性能有两个方面:序列化/反序列化的速度有多快,以及序列化表示有多大?

为了测试各种序列化格式的性能,我将创建一个较大的数据结构,并使用 Pickle、YAML 和 JSON 对其进行序列化/反序列化。 big_data 列表包含 5,000 个复杂对象。

'
big_data = [dict(a=simple, when=datetime.now().replace(microsecond=0)) for i in range(5000)]

泡菜

我将在这里使用 IPython,因为它有方便的 %timeit 魔术函数来测量执行时间。

'
import cPickle as pickle



In [190]: %timeit serialized = pickle.dumps(big_data)

10 loops, best of 3: 51 ms per loop



In [191]: %timeit deserialized = pickle.loads(serialized)

10 loops, best of 3: 24.2 ms per loop



In [192]: deserialized == big_data

Out[192]: True



In [193]: len(serialized)

Out[193]: 747328

默认pickle需要83.1毫秒进行序列化,29.2毫秒进行反序列化,序列化大小为747,328字节。

让我们尝试使用最高协议。

'
In [195]: %timeit serialized = pickle.dumps(big_data, protocol=pickle.HIGHEST_PROTOCOL)

10 loops, best of 3: 21.2 ms per loop



In [196]: %timeit deserialized = pickle.loads(serialized)

10 loops, best of 3: 25.2 ms per loop



In [197]: len(serialized)

Out[197]: 394350

有趣的结果。序列化时间缩短至仅 21.2 毫秒,但反序列化时间略有增加,达到 25.2 毫秒。序列化大小显着缩小至 394,350 字节 (52%)。

JSON

'
In [253] %timeit serialized = json.dumps(big_data, cls=CustomEncoder)

10 loops, best of 3: 34.7 ms per loop



In [253] %timeit deserialized = json.loads(serialized, object_hook=decode_object)

10 loops, best of 3: 148 ms per loop



In [255]: len(serialized)

Out[255]: 730000

好的。编码方面的性能似乎比 Pickle 差一点,但解码方面的性能却差很多很多:慢了 6 倍。这是怎么回事?这是 object_hook 函数的一个工件,需要为每个字典运行以检查是否需要将其转换为对象。不使用对象挂钩运行速度要快得多。

'
%timeit deserialized = json.loads(serialized)

10 loops, best of 3: 36.2 ms per loop

这里的教训是,在序列化和反序列化为 JSON 时,请仔细考虑任何自定义编码,因为它们可能会对整体性能产生重大影响。

YAML

'
In [293]: %timeit serialized = yaml.dump(big_data)

1 loops, best of 3: 1.22 s per loop



In[294]: %timeit deserialized = yaml.load(serialized)

1 loops, best of 3: 2.03 s per loop



In [295]: len(serialized)

Out[295]: 200091

好的。 YAML 真的非常非常慢。但是,请注意一些有趣的事情:序列化大小仅为 200,091 字节。比 Pickle 和 JSON 都好得多。让我们快速了解一下内部:

'
In [300]: print serialized[:211]

- a: &id001

    boolean: true

    int_list: [1, 2, 3]

    none: null

    number: 3.44

    text: string

  when: 2016-03-13 00:11:44

- a: *id001

  when: 2016-03-13 00:11:44

- a: *id001

  when: 2016-03-13 00:11:44

YAML 在这里非常聪明。它确定所有 5,000 个字典共享相同的“a”键值,因此它仅存储一次并使用 *id001 为所有对象引用它。

安全

安全性通常是一个关键问题。 Pickle和YAML由于构造Python对象,很容易受到代码执行攻击。格式巧妙的文件可以包含将由 Pickle 或 YAML 执行的任意代码。无需惊慌。这是设计使然,并记录在 Pickle 的文档中:

警告:pickle 模块并非旨在防止错误或恶意构建的数据。切勿取消从不受信任或未经身份验证的来源收到的数据。

以及 YAML 文档中的内容:

警告:使用从不受信任的来源收到的任何数据调用 yaml.load 是不安全的! yaml.load 与 pickle.load 一样强大,因此可以调用任何 Python 函数。

您只需要了解,不应使用 Pickle 或 YAML 加载从不受信任的来源收到的序列化数据。 JSON 没问题,但是如果您有自定义编码器/解码器,那么您也可能会暴露。

yaml 模块提供了 yaml.safe_load() 函数,该函数仅加载简单的对象,但随后您会失去很多 YAML 的功能,并且可能选择只使用 JSON。

其他格式

还有许多其他可用的序列化格式。以下是其中的一些。

协议缓冲区

Protobuf(即协议缓冲区)是 Google 的数据交换格式。它是用 C++ 实现的,但具有 Python 绑定。它具有复杂的架构并有效地打包数据。非常强大,但不太容易使用。

消息包

MessagePack 是另一种流行的序列化格式。它也是二进制且高效的,但与 Protobuf 不同的是它不需要模式。它有一个类似于 JSON 的类型系统,但更丰富一些。键可以是任何类型,不仅支持字符串和非 UTF8 字符串。

CBOR

CBOR 代表简洁二进制对象表示。同样,它支持 JSON 数据模型。 CBOR 不像 Protobuf 或 MessagePack 那样出名,但它很有趣,原因有两个:

  1. 它是官方互联网标准:RFC 7049。
  2. 它专为物联网 (IoT) 设计。

如何选择?

这是一个大问题。这么多选择,你如何选择?让我们考虑一下应该考虑的各种因素:

  1. 序列化格式应该是人类可读和/或人类可编辑的吗?
  2. 是否会从不受信任的来源接收序列化内容?
  3. 序列化/反序列化是性能瓶颈吗?
  4. 序列化数据是否需要与非Python环境交换?

我会让您变得非常简单,并介绍几种常见场景以及我为每种场景推荐的格式:

自动保存Python程序的本地状态

此处使用 pickle (cPickle) 和 HIGHEST_PROTOCOL。它快速、高效,无需任何特殊代码即可存储和加载大多数 Python 对象。它也可以用作本地持久缓存。

配置文件

绝对是 YAML。对于人类需要阅读或编辑的任何内容来说,没有什么比它的简单性更好的了。它已被 Ansible 和许多其他项目成功使用。在某些情况下,您可能更喜欢使用直接的 Python 模块作为配置文件。这可能是正确的选择,但它不是序列化,它实际上是程序的一部分,而不是单独的配置文件。

Web API

JSON 显然是这里的赢家。如今,Web API 最常由原生使用 JSON 的 JavaScript Web 应用程序使用。某些 Web API 可能会返回其他格式(例如,用于密集表格结果集的 csv),但我认为您可以以最小的开销将 csv 数据打包为 JSON(无需将每一行作为具有所有列名称的对象重复)。

高容量/低延迟大规模通信

使用二进制协议之一:Protobuf(如果需要架构)、MessagePack 或 CBOR。运行您自己的测试来验证每个选项的性能和代表能力。

结论

Python对象的序列化和反序列化是分布式系统的一个重要方面。您不能直接通过网络发送 Python 对象。您经常需要与用其他语言实现的其他系统进行互操作,有时您只想将程序的状态存储在持久存储中。

Python 在其标准库中附带了多种序列化方案,还有更多序列化方案可作为第三方模块使用。了解所有选项以及每个选项的优缺点将使您能够选择最适合您情况的方法。

卓越飞翔博客
上一篇: 计算不含连续1的二进制字符串的数量的PHP程序
下一篇: 返回列表
留言与评论(共有 0 条评论)
   
验证码:
隐藏边栏