Python:为什么要使用pickle?

42

我一直使用pickle,并感到非常满意,但后来我看到了这篇文章:不要用pickle序列化数据

继续阅读后,似乎有以下问题:

我已经改用JSON保存我的数据,但我想知道最佳实践:

考虑到所有这些问题,你什么情况下才会使用pickle? 什么特定情况需要使用它?


2
顺便提一下,有些格式比JSON更易于阅读,并且可以说更容易编辑。好的INI文件和YAML都是不错的选择。它肯定比不透明的二进制流要好,但人类可读性并不是一个二元的事情。 - user395760
我认为以JSON格式保存对象的第一个缺点是:您需要创建自己的序列化器,这需要一些时间。此外,将JSON进程进行序列化的速度最终可能比简单的pickle慢。虽然我同意安全方面的缺点。另一个问题是:为什么您想要存储一个可以编辑的对象?那样不安全吗? - Depado
4
当手头有螺丝刀时,为什么要使用锤子?当手头有锤子时,为什么要使用螺丝刀?关键在于选择正确的工具来完成工作。 - bruno desthuilliers
这基本上是与此文章相同:https://dev59.com/r2ox5IYBdhLWcg3wyXRx#19360828。如果您担心安全性,请不要依赖pickle或JSON。使用更强大的身份验证服务——例如带有加密密钥的服务。 - Mike McKerns
鉴于pickle相对于过度简化的格式(例如JSON)需要进行额外的工作以确保找到已经表示的对象的引用,因此它并不慢。而JSON甚至在极其简单的事情上也会抛出错误,例如import json; d = [1]; d.append(d); json.dumps(d) - Anthon
5个回答

33
Pickle 不安全,因为它通过调用任意函数来构造任意 Python 对象。然而,这也赋予了它几乎可以序列化任何 Python 对象的能力,无需使用任何模板或者甚至白名单/黑名单(在常见情况下)。这在某些用例中非常理想:
- 快速简便的序列化,例如暂停和恢复一个长时间运行但简单的脚本。在这里,没有任何问题是重要的,你只想将程序状态按原样转储并稍后加载。 - 发送任意 Python 数据给其他进程或计算机,如 multiprocessing。安全问题可能适用(但大多数不适用),通用性是绝对必要的,且人类不需要阅读它。
在其他情况下,以上缺点都不足以证明将东西映射到 JSON 或另一个限制性数据模型的工作是有意义的。也许您不需要人类可读性/安全性/跨语言兼容性,或者您可以不用这些。记住,You Ain't Gonna Need It。使用 JSON 是正确的™,但是正确并不总等于好。

你会注意到我完全忽略了“慢”的缺点。这是因为它部分地具有误导性:对于符合JSON模型(字符串、数字、数组、映射)的数据,Pickle确实比较慢,但如果你的数据是这样的,你应该出于其他原因使用JSON。如果你的数据不是这样的(很可能),你还需要考虑将对象转换为JSON数据所需的自定义代码以及将JSON数据转换回对象所需的自定义代码。这增加了工程努力和运行时开销,必须根据情况逐个量化。


感谢你的出色回答。知道正确的方法即使它并不总是等于好事,这很好。 - e h
multiprocessingspark中,当使用RDDs时,Spark将序列化您定义的用户函数(传递给map、flatMap)使用pickle,因为它可以序列化几乎任何Python对象。 - James Lim

6

Pickle有方便的优点,它可以在不需要额外工作的情况下序列化任意对象图,并可用于相当广泛的Python类型。尽管如此,在新代码中使用Pickle是不寻常的。JSON更加干净易用。


5

我尝试了几种方法,发现使用cPickle并将dumps方法的protocol参数设置为Cpickle.HIGHEST_PROTOCOL是最快的转储方法。

import msgpack
import json
import pickle
import timeit
import cPickle
import numpy as np

num_tests = 10

obj = np.random.normal(0.5, 1, [240, 320, 3])

command = 'pickle.dumps(obj)'
setup = 'from __main__ import pickle, obj'
result = timeit.timeit(command, setup=setup, number=num_tests)
print("pickle:  %f seconds" % result)

command = 'cPickle.dumps(obj)'
setup = 'from __main__ import cPickle, obj'
result = timeit.timeit(command, setup=setup, number=num_tests)
print("cPickle:   %f seconds" % result)


command = 'cPickle.dumps(obj, protocol=cPickle.HIGHEST_PROTOCOL)'
setup = 'from __main__ import cPickle, obj'
result = timeit.timeit(command, setup=setup, number=num_tests)
print("cPickle highest:   %f seconds" % result)

command = 'json.dumps(obj.tolist())'
setup = 'from __main__ import json, obj'
result = timeit.timeit(command, setup=setup, number=num_tests)
print("json:   %f seconds" % result)


command = 'msgpack.packb(obj.tolist())'
setup = 'from __main__ import msgpack, obj'
result = timeit.timeit(command, setup=setup, number=num_tests)
print("msgpack:   %f seconds" % result)

输出:

pickle         :   0.847938 seconds
cPickle        :   0.810384 seconds
cPickle highest:   0.004283 seconds
json           :   1.769215 seconds
msgpack        :   0.270886 seconds

因此,在需要实时性能的情况下,比如从相机流式传输视频到服务器,我更喜欢使用具有最高转储协议的cPickle。

4

我通常不使用Pickle或JSON,而是使用MessagePack,它既安全又快速,并且生成的序列化数据大小小。

另一个优点是可以与其他语言编写的软件交换数据(当然,在JSON的情况下也是如此)。


JSON在我看来最大的优点是既简洁(不像XML),又易读(不像MessagePack)。我不确定MessagePack所节省的空间是否足以抵消这两个优点。 - CadentOrange
MessagePack 并不是为了节省空间,而是可以编码 JSON 不擅长处理的二进制数据等内容。 - Joe
2
MessagePack 无法序列化 set,真遗憾。 - Display Name

2
您可以在JSON与Pickle安全性比较中找到一些答案:JSON只能pickle unicode、int、float、NoneType、bool、list和dict。如果要pickle更高级的对象(例如类实例),则无法使用它。请注意,对于这些pickle类型,没有跨语言兼容的希望。

此外,使用cPickle而不是Pickle可以部分解决速度问题。


我也认为cPickle更快,但是后来我看到了这个链接:https://dev59.com/_mQn5IYBdhLWcg3wcWvM - e h

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