这是关于序列化和反序列化Python对象的教程的第二部分。 在第一部分中 ,您学习了基础知识,然后深入研究Pickle和JSON的来龙去脉。
在这一部分中,您将探索YAML(确保拥有第1部分中正在运行的示例),讨论性能和安全性考虑,对其他序列化格式进行审查,最后学习如何选择正确的方案。
YAML
YAML是我最喜欢的格式。 这是一种人类友好的数据序列化格式。 与Pickle和JSON不同,它不是Python标准库的一部分,因此您需要安装它:
pip install 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对象的符号。 输出仍然非常易于阅读。 datetime对象不需要任何特殊标记,因为YAML固有地支持datetime对象。
性能
在开始考虑性能之前,您需要考虑性能是否值得关注。 如果您相对不频繁地对少量数据进行序列化/反序列化(例如,在程序开始时读取配置文件),那么性能并不是真正的问题,您可以继续。
但是,假设您已对系统进行了概要分析并发现序列化和/或反序列化会导致性能问题,那么以下是要解决的问题。
性能有两个方面:序列化/反序列化的速度有多快,序列化的表示量有多大?
为了测试各种序列化格式的性能,我将创建一个较大的数据结构并使用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个dict都为'a'键共享相同的值,因此它仅存储一次并为所有对象使用*id001
进行引用。
安全
安全通常是一个至关重要的问题。 Pickle和YAML依靠构造Python对象而容易受到代码执行攻击。 格式正确的文件可以包含将由Pickle或YAML执行的任意代码。 无需惊慌。 这是设计使然,并在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众所周知,但由于两个原因而引起人们的兴趣:
- 它是一个正式的Internet标准: RFC 7049 。
- 它是专门为物联网(IoT)设计的。
如何选择?
这是个大问题。 有这么多种选择,您如何选择? 让我们考虑应该考虑的各种因素:
- 序列化格式应该是人类可读和/或人类可编辑的吗?
- 是否会从不受信任的来源接收序列化的内容?
- 序列化/反序列化是否是性能瓶颈?
- 是否需要与非Python环境交换序列化的数据?
我将为您提供方便,并介绍几种常见方案以及我为每种方案推荐的格式:
自动保存Python程序的本地状态
在此处使用HIGHEST_PROTOCOL
泡菜(cPickle)。 它快速,高效,并且可以存储和加载大多数Python对象,而无需任何特殊代码。 它也可以用作本地持久性缓存。
配置文件
绝对是YAML。 对于人类需要阅读或编辑的任何内容,没有什么比它的简单性更好。 Ansible和其他许多项目已成功使用它。 在某些情况下,您可能更喜欢使用直接的Python模块作为配置文件。 这可能是正确的选择,但它不是序列化,它实际上是程序的一部分,而不是单独的配置文件。
Web API
JSON很明显是赢家。 如今,Web API最常被本机使用JSONJavaScript Web应用程序使用。 某些Web API可能会返回其他格式(例如,用于密集表格结果集的csv),但是我认为您可以以最小的开销将csv数据打包到JSON中(无需将每行作为具有所有列名的对象重复)。
高容量/低延迟的大规模通信
使用以下二进制协议之一:Protobuf(如果需要模式),MessagePack或CBOR。 运行您自己的测试,以验证每个选项的性能和代表性。
结论
Python对象的序列化和反序列化是分布式系统的重要方面。 您不能直接通过网络发送Python对象。 您通常需要与以其他语言实现的其他系统进行互操作,有时您只想将程序的状态存储在持久性存储中。
Python在其标准库中附带了几种序列化方案,还有更多的可作为第三方模块使用。 了解所有选项以及每个选项的利弊,可以让您选择适合自己情况的最佳方法。
翻译自: https://code.tutsplus.com/tutorials/serialization-and-deserialization-of-python-objects-part-2--cms-26184