Python中的酸洗字典

时间:2018-10-23 12:18:02

标签: python python-2.7 pickle

我可以期望相同的腌制字典的字符串表示在相同的Python版本的不同机器/运行之间是一致的吗? 在同一台机器上一次运行的范围内?

例如

# Python 2.7

import pickle
initial = pickle.dumps({'a': 1, 'b': 2})
for _ in xrange(1000**2):
    assert pickle.dumps({'a': 1, 'b': 2}) == initial

这是否取决于我的dict对象的实际结构(嵌套值等)?

UPD: 关键是-无论我的dict对象是什么样子(键/值等),我都无法使上述代码一次运行(Python 2.7)失败

5 个答案:

答案 0 :(得分:4)

出于一般原因,您不能这样做you can't rely on the dictionary order in other scenarios腌制在这里并不特殊。字典的字符串表示形式是当前字典迭代顺序的函数,无论您如何加载它。

您自己的小型测试太受限制,因为它不会对测试字典进行任何更改,也不会使用会引起冲突的键。您使用完全相同的Python源代码创建字典,因此它们将产生相同的输出顺序,因为字典的编辑历史完全相同,并且两个使用ASCII字符集的连续字母的单字符键不太可能造成碰撞。

并非实际上测试了字符串表示形式是否相等,而是仅测试它们的内容是否相同(字符串表示形式不同的两个字典仍然可以相等,因为相同的键值对受插入不同的插入顺序,可以产生不同的字典输出顺序。

接下来,在cPython 3.6之前,字典迭代顺序中最重要的因素是哈希键生成函数,该函数必须在单个Python可执行文件生存期内保持稳定(否则您将破坏所有词典),因此,单进程测试永远不会看到基于不同哈希函数结果的字典顺序更改。

当前,所有酸洗协议修订版都将字典的数据存储为键-值对流。加载时,对流进行解码,并按磁盘上的顺序将键值对分配回字典,因此从该角度来看,插入顺序至少是稳定的。 但是在不同的Python版本,机器架构和本地配置之间,哈希函数的结果绝对会有所不同:

  • PYTHONHASHSEED environment variable用于生成strbytesdatetime键的哈希。该设置自Python 2.6.8和3.2.3起可用,并且自Python 3.3起默认启用并设置为random。因此设置因Python版本而异,可以在本地设置为不同的内容。
  • 哈希函数产生一个ssize_t整数,它是一个与平台相关的有符号整数类型,因此不同的体系结构可能会产生不同的哈希,仅因为它们使用更大或更小的ssize_t类型定义。

随着机器与机器之间以及Python运行与Python运行之间输出不同的哈希函数,您将 看到字典的不同字符串表示形式。

最后,从cPython 3.6开始,dict类型的实现更改为更紧凑的格式,该格式也发生以保留插入顺序。从Python 3.7开始,语言规范已更改为强制执行此行为,因此其他Python实现必须实现相同的语义。因此,即使在所有其他因素相同的情况下,在Python 3.7之前的不同Python实现或版本之间进行酸洗和酸洗也可能导致字典输出顺序不同。

答案 1 :(得分:2)

不,您不能。这取决于很多事情,包括键值,解释器状态和python版本。

如果需要一致的表示形式,请考虑使用规范形式的JSON。

编辑

我不太确定为什么人们会对此一无所知而对此表示反对,但我会澄清。

pickle并不是要产生可靠的表示形式,它是纯机器(不是人类)可读的序列化器。

Python版本的向前/向后兼容性是一回事,但这仅适用于在相同的对象 解释器中反序列化相同对象的功能—即,当您转储一个版本并加载另一个版本时,可以保证它具有相同公共接口的相同行为。序列化的文本表示形式或内部存储器结构都声称不一样(而IIRC则从未如此)。

最简单的检查方法是在结构处理和/或种子处理方面存在显着差异的版本中转储相同数据,同时使密钥不在缓存范围内(无短整数或字符串):

Python 3.5.6 (default, Oct 26 2018, 11:00:52) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> d = {'first_string_key': 1, 'second_key_string': 2}
>>> pickle.dump
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x11\x00\x00\x00second_key_stringq\x01K\x02X\x10\x00\x00\x00first_string_keyq\x02K\x01u.'

Python 3.6.7 (default, Oct 26 2018, 11:02:59) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> d = {'first_string_key': 1, 'second_key_string': 2}
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x10\x00\x00\x00first_string_keyq\x01K\x01X\x11\x00\x00\x00second_key_stringq\x02K\x02u.'

答案 2 :(得分:1)

Python2字典是无序的;顺序取决于键的哈希值,如Martijn Pieters所著的answer中所解释的那样。我认为您不能在此处使用字典,但是可以使用OrderedDict(需要Python 2.7或更高版本)来维护键的顺序。例如,

from collections import OrderedDict

data = [('b', 0), ('a', 0)]
d = dict(data)
od = OrderedDict(data)

print(d)
print(od)

#{'a': 0, 'b': 0}
#OrderedDict([('b', 0), ('a', 0)])

您可以像对dict进行腌制一样对OrderedDict进行腌制,但是将保留订单,并且在腌制相同对象时所得的字符串将相同。

from collections import OrderedDict
import pickle

data = [('a', 1), ('b', 2)]
od = OrderedDict(data)
s = pickle.dumps(od)
print(s)

请注意,您不应在OrderedDict的构造函数中传递字典,因为键已被放置。如果您有字典,则应首先将其转换为具有所需顺序的元组。 OrderedDict是dict的子类,并具有所有dict方法,因此您可以创建一个空对象并分配新的键。

您的测试不会失败,因为您使用的是相同的Python版本和相同的条件-字典的顺序在循环迭代之间不会随机改变。但是,我们可以证明当我们更改字典中键的顺序时,您的代码如何无法产生差异字符串。

import pickle

initial = pickle.dumps({'a': 1, 'b': 2})
assert pickle.dumps({'b': 2, 'a': 1}) != initial

当我们首先将键'b'放置时,结果字符串应该有所不同(在Python> = 3.6中会有所不同),但是在Python2中,它是相同的,因为键'a'放在键'b'之前。

要回答您的主要问题,Python2字典是无序的,但是当使用相同的代码和Python版本时,字典可能具有相同的顺序。但是,该顺序可能与您在字典中放置项目的顺序不同。如果顺序很重要,则最好使用OrderedDict或更新您的Python版本。

答案 3 :(得分:1)

与Python中令人沮丧的大量事物一样,答案是“有点”。直接来自文档

  

pickle序列化格式在所有Python版本中都可以向后兼容。

这可能与您要的内容有很大的不同。如果现在是有效的腌制词典,它将始终是有效的腌制词典,并且将始终反序列化为正确的词典。留下了一些您可能期望且不必保留的属性:

  • 即使对于同一平台上的同一Python实例中的同一对象,也不必进行确定性处理。同一本词典可能具有无限多个可能的腌制表示形式(不是我们希望该格式效率不高到足以支持任意程度的额外填充)。正如其他答案所指出的,字典没有定义的排序顺序,这至少可以给出n!具有n个元素的字典的字符串表示形式。
  • 最后一点,即使在单个Python实例中,也不能保证泡菜的一致性。实际上,这些更改当前不会发生,但是不能保证在将来的Python版本中仍会保留这种行为。
  • Python的未来版本不需要以与当前版本兼容的方式序列化字典。我们唯一的承诺是他们将能够正确地反序列化我们的词典。目前,所有Pickle格式都支持相同的字典,但是并不需要永远保持这种情况(不是我怀疑它会改变)。

答案 4 :(得分:0)

如果您不修改dict,则在程序的给定运行期间其字符串表示形式将不会更改,并且其.navbar { overflow: hidden; background-color: #333; position: sticky; position: -webkit-sticky; top: 0; } .navbar button { background-color: inherit; float: left; color: white; border: none; outline: none; cursor: pointer; padding: 14px 16px; transition: 0.3s; font-size: 17px; } .navbar button:hover { background-color: #ddd; color: black; } .navbar button.active { background-color: #666; color: white; } 方法将以相同顺序返回键。但是,顺序 可以在运行之间更改(在Python 3.6之前)。

此外,两个具有相同键值对的不同dict对象也不保证使用相同的顺序(在Python 3.6之前)。


顺便说一句,用自己的变量来遮盖模块名称并不是一个好主意,就像使用该lambda一样。这样会使代码更难阅读,并且如果您忘记了对模块进行了阴影处理并尝试在程序中稍后使用它的其他名称,则会导致混淆的错误消息。