【问题标题】:Pickling dict in Python在 Python 中腌制字典
【发布时间】:2018-10-23 12:18:02
【问题描述】:

我可以期望相同的腌制字典的字符串表示在不同的机器/运行中对于相同的 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 对象的实际结构(嵌套值等)?

更新: 问题是 - 无论我的 dict 对象看起来如何(什么键/值等),我实际上都不能让上面的代码在一次运行(Python 2.7)的范围内失败

【问题讨论】:

  • 绝对不是。你有充分的理由使用字符串表示吗?您使用xrange,这意味着 Python 2,其中字典中键的顺序是任意的(这使得字符串表示无用)。
  • 上面的代码可以工作(一次运行 ofc),所以真的“没用”吗?我也想了解这样的行为,所以我有一个很好的理由提出这样的问题:)
  • 正如已经指出的那样,您的 dict 的腌制表示可能会有所不同。但是,即使在这种情况下,未选择的字典也会与原始字典进行比较,这不是真正重要的吗?
  • 不,在我的问题中,腌制对象的字符串表示很重要,就是这样
  • 如果您需要维护顺序维护数据类型,为什么不使用collections.OrderedDict

标签: python python-2.7 pickle


【解决方案1】:

你不能在一般情况下,出于同样的原因you can't rely on the dictionary order in other scenarios; 酸洗在这里并不特别。字典的字符串表示是当前字典迭代顺序的函数,无论您如何加载它。

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

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

接下来,在 cPython 3.6 之前的字典迭代顺序中最重要的因素是哈希键生成函数,它必须在单个 Python 可执行文件生命周期内保持稳定(否则你会破坏所有字典),所以单进程test 永远不会看到字典顺序根据不同的哈希函数结果发生变化。

目前,所有酸洗协议修订版都将字典的数据存储为键值对流;在加载流时解码,键值对按磁盘顺序分配回字典,因此从这个角度来看,插入顺序至少是稳定的。 但是在不同的 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 实现或版本之间进行酸洗和取消酸洗也会导致不同的字典输出顺序,即使所有其他因素都相同。

【讨论】:

    【解决方案2】:

    不,你不能。这取决于很多东西,包括键值、解释器状态和 python 版本。

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

    编辑

    我不太清楚为什么人们在没有任何 cmets 的情况下对此投反对票,但我会澄清一下。

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

    Python 版本向后/向前兼容性是一回事,但它仅适用于反序列化相同对象的能力 inside 解释器 - 即,当您转储一个版本并加载另一个版本时,它保证有相同公共接口的相同行为。序列化文本表示或内部存储器结构都声称是相同的(而 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.'
    

    【讨论】:

    • 你的意思是排序键(在 JSON 的情况下)?
    • 是的,如果是 stdlib 序列化程序,它的 sort_keys=True in docs.python.org/3/library/json.html#json.dump
    • 这是一个很好的解决方法,是的。但问题是 - JSON 无法处理我需要的所有数据类型,这就是为什么我一直在寻找另一种方法来获取对象的字符串表示形式(列表、字典、类实例等)
    • @d-d 可以扩展 json 序列化器(和反序列化器)以支持更多类型。当然,它永远不会完全取代 pickle,但根据您的实际需求,它可能仍然是一个可行的解决方案。
    • @d-d 您的问题仍然在这里得到解答,答案是否定的!因为这取决于很多事情。酸洗只是对象的格式良好的内部表示转储,您不能假设散列集中的项目顺序在每台机器上都以相同的顺序转储。它取决于机器,问题与其说是泡菜,不如说是某些类型(例如 dict 和 set)的 python 内部实现。例如,当每个 str 调用都在不同的机器
    【解决方案3】:

    Python2 字典是无序的;顺序取决于键的哈希值,正如 Martijn Pieters 的 answer 中所解释的那样。我认为您不能在这里使用 dict,但您可以使用 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)])
    

    您可以像腌制字典一样腌制 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 版本。

    【讨论】:

    • 嗨@t.m.adam,希望你做得很好。如果你给this post 看看,我会很高兴,以防你能提供任何解决方案。谢谢。
    【解决方案4】:

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

    pickle 序列化格式保证在 Python 版本之间向后兼容。

    这可能与您所要求的存在细微差别。如果它现在是一个有效的腌制字典,它将始终是一个有效的腌制字典,并且它总是会反序列化为正确的字典。这留下了一些您可能期望并且不必保留的未说出口的属性:

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

    【讨论】:

    • 仅仅因为 pickling 是向后兼容的,并不意味着在加载 pickle 文件时会产生相同的字典顺序。
    • @MartijnPieters 我认为我们意见一致。我应该重组/重新格式化我的答案以使其更清楚吗?
    【解决方案5】:

    如果你不修改 dict,它的字符串表示在程序的给定运行期间不会改变,它的 .keys 方法将以相同的顺序返回键。但是,顺序可以在运行之间改变(在 Python 3.6 之前)。

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


    顺便说一句,用你自己的变量来隐藏模块名称并不是一个好主意,就像你对那个 lambda 所做的那样。它使代码更难阅读,并且如果您忘记隐藏模块并在程序稍后尝试从中访问其他名称,则会导致令人困惑的错误消息。

    【讨论】:

    • 这是否意味着腌制表示也将保持不变(考虑到我没有复杂/嵌套的值,比如说 - 整数)?
    • 我相信,在某些时候(3.5?)键顺序甚至强制每次运行都不同
    • @d-d pickle 不关心字符串 repr,它使用 .items。但是您的代码不应依赖 pickle 维护密钥顺序(在 Python 3.6 之前)。
    • @Slam 确切的行为取决于 PYTHONHASHSEED 环境变量的设置。 IIRC,从 3.2 到 3.5,默认使用随机哈希种子,以防止在服务器上使用 dicts 时进行 DOS 攻击。
    • 在这种情况下,当我说“字符串表示”时,我指的是字符串,它在调用dumps 方法之后返回
    猜你喜欢
    • 2016-01-05
    • 1970-01-01
    • 2018-08-18
    • 1970-01-01
    • 2018-06-15
    • 2011-05-04
    • 2018-03-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多