巨蟒泡菜的缺陷

2020-06-23 23:27:16

Python的Pickle模块是序列化和反序列化对象的一种非常方便的方式。它不需要模式,可以处理任意的Python对象。但它也有问题。这篇文章简要解释了这些问题。

有些人会告诉你永远不要用泡菜,因为它不好。我不会走那么远的。我要说的是,只有当你对泡菜的九个缺点没有意见的时候,才可以使用泡菜:

泡菜可以是手工制作的,当你解开泡菜时,会有恶意的影响。因此,您永远不应该取消选择不信任的数据。

不安全不是因为Pickle包含代码,而是因为它们通过调用在Pickle中命名的构造函数来创建对象。任何可调用对象都可以用来代替您的类名来构造对象。恶意的Pickles将使用其他Python可调用函数作为“构造函数”。例如,危险的Pickle可能会执行“os.system(‘rm-rf/’)”,而不是执行“mods.MyObject(17)”。UnPickler无法区分“mods.MyObject”和“os.system”。这两个名字都是它可以解析的,产生了它可以称之为的东西。解拣器按照酸菜的指示执行它们中的任何一个。

更多细节,包括一个例子,请参阅Supaken在Python标准库中的POST DISKINGS。

因为泡菜存储对象的结构,所以当它们未泡菜时,它们的结构与您泡菜时的结构相同。这听起来像是一件好事,也正是泡菜的设计初衷。但是,如果您的代码在制作泡菜的时间和使用它的时间之间发生更改,则您的对象可能与您的代码不一致。对象仍将具有由旧代码创建的结构,但它们将与新代码一起运行。

例如,如果您在制作泡菜后添加了一个属性,则泡菜中的对象将不会具有该属性。您的代码将需要该属性,从而导致问题。

Pickles的最大便利在于它们将序列化您的对象具有的任何结构。创建序列化结构不需要额外的工作。但这也带来了自身的问题。您真的希望将DateTime序列化为DateTime吗?或者作为iso8601字符串?你别无选择:他们将是约会时间。

您不仅不必指定序列化形式,而且也不能指定它。

Pickle是隐式的:它们序列化对象中的所有内容,甚至是您不想序列化的数据。例如,您可能有一个属性是您不想序列化的计算缓存。Pickle没有跳过该属性的便捷方法。

更糟糕的是,如果您的对象包含不能进行PICLE的属性,比如打开的文件对象,PICLE不会跳过它,它会坚持尝试PICLE,然后抛出异常。

泡菜存储对象的整个结构。当Pickle模块重新创建您的对象时,它不会调用您的__init__方法,因为该对象已经创建。

这可能令人惊讶,因为在其他任何地方,对象都会在不调用__init__的情况下产生。这里的逻辑是,在制作泡菜的进程中首次创建对象时,已经调用了__init__。

但是您的__init__方法可能会执行一些重要的工作,比如打开文件对象。您的未酸洗对象将处于与您的__init__方法不一致的状态。或者,您的__init__可能会记录有关正在创建的对象的信息。未酸洗的对象不会出现在日志中。

Pickle特定于Python,并且只能由其他Python程序使用。严格说来并非如此,您可以找到可以使用泡菜的其他语言的包,但它们很少见。它们自然会局限于跨语言的通用列表/字典对象结构,在这一点上,您也可以只使用JSON。

Pickle是一个二进制数据流(实际上是抽象执行引擎的指令)。如果将泡菜作为纯文件打开,则无法读取其内容。知道酸菜中有什么的唯一方法是使用酸菜模块加载它。这可能会使调试变得困难,因为您可能无法在PICLE文件中搜索您感兴趣的数据:

函数和类是Python中的一级对象:您可以将它们存储在列表、字典、属性等中。Pickle将很乐意序列化包含函数和类等可调用对象。但它不会将代码存储在Pickle中,只存储函数或类的名称。

Pickle不是一种移动或存储代码的方式,尽管它们看起来可能是。当您取消挑选数据时,函数的名称用于查找正在运行的进程中的现有代码。

与其他序列化技术相比,PICLE可能会很慢,正如Ben Frederickson在Don‘t Pickle Your Data中所展示的那样。

其中一些问题可以通过向您的类添加特殊方法来解决,如__getstate__或__Reduce__。但是,一旦您开始沿着这条路走下去,您不妨使用另一种从一开始就没有这些缺陷的序列化方法。

有很多其他方法可以序列化对象,从普通的JSON到更时髦的替代方法,如棉花糖、cattrs、协议缓冲区等等。

我对其中任何一个都没有强烈的推荐。正确的答案将取决于您问题的细节。甚至可能是泡菜。

名称是必填项。电子邮件或Web为必填项。电子邮件不会显示,我不会给您发送垃圾邮件。您的网站不会被搜索引擎编入索引。