【问题标题】:Set iteration order varies from run to run设置迭代顺序因运行而异
【发布时间】:2010-10-03 00:41:58
【问题描述】:

为什么 Python 集(具有相同内容)的迭代顺序因运行而异,我有哪些选项可以使其在运行之间保持一致?

我了解 Python 集的迭代顺序是任意的。如果我将 'a'、'b' 和 'c' 放入一个集合中然后迭代它们,它们可能会以任何顺序返回。

我观察到的是,在程序运行中顺序保持不变。也就是说,如果我的程序连续两次迭代同一个集合,我两次得到相同的顺序。但是,如果我连续两次运行该程序,则顺序会从运行变为运行。

不幸的是,这破坏了我的一个自动化测试,它只是比较了我的程序两次运行的输出。我不关心实际的顺序,但我希望它在运行之间保持一致。

我想出的最佳解决方案是:

  1. 将集合复制到列表中。
  2. 对列表应用任意排序。
  3. 迭代列表而不是集合。

有没有更简单的解决方案?

注意:我在 StackOverlow 上发现了类似的问题,但没有一个可以解决每次运行都获得相同结果的特定问题。

【问题讨论】:

  • 如果您要测试的是“程序两次输出相同的内容”,那么排序列表选项是您的最佳选择。如果您要测试的是“程序两次创建相同的集合”,则需要进行集合比较(通过对两次运行的输出进行酸洗,然后对两者的输出进行解酸并比较它们,或者道德上等价的东西)。
  • @Russell:我有单元测试来验证设置的内容。但我也有这个测试,它比较两次运行的输出作为健全性检查。输出部分取决于集合中项目的顺序,但仅以迂回的方式。

标签: python set iteration


【解决方案1】:

设置迭代顺序从运行到运行发生变化的原因似乎是因为 Python 默认使用哈希种子随机化。 (参见命令选项-R。)因此集合迭代不仅是任意的(因为散列),而且是非确定性的(因为随机种子)。

您可以通过为解释器设置环境变量PYTHONHASHSEED 来覆盖具有固定值的随机种子。每次运行都使用相同的种子意味着集合迭代仍然是任意的,但现在它是确定性的,这是所需的属性。

哈希种子随机化是一种安全措施,可让攻击者难以提供会导致病态行为的输入(例如,通过创建大量哈希冲突)。对于单元测试,这不是问题,因此在运行测试时覆盖哈希种子是合理的。

【讨论】:

  • 直到 2012 年才向 Python 添加随机哈希。
  • @pydsigner:这很有趣,因为这确实解决了我面临的问题。去年秋天我回到了这个项目,设置 PYTHONHASHSEED 使我的测试输出在每次运行中保持一致。
  • 确实很有趣....2.6.83.2.3 是引入此功能的版本。
  • @pydsigner 你也有介绍随机化的链接(不是介绍 PYTHONHASHSEED 变量)吗?
  • @superbrain 基于ocert.org/advisories/ocert-2011-003.html,听起来版本与添加的 PYTHONHASHSEED 一致;向后移植到 2.6.8 和 3.1.5,并且在 2.7.3 和 3.2.3 之外的任何地方都可用。
【解决方案2】:

在你的两个集合上使用 symmetric_difference (^) 运算符来查看是否有任何差异:

In [1]: s1 = set([5,7,8,2,1,9,0])
In [2]: s2 = set([9,0,5,1,8,2,7])
In [3]: s1
Out[3]: set([0, 1, 2, 5, 7, 8, 9])
In [4]: s2
Out[4]: set([0, 1, 2, 5, 7, 8, 9])
In [5]: s1 ^ s2
Out[5]: set()

【讨论】:

  • 这对于直接比较集合很好。然而,在我的测试中,我正在寻找一种简单的方法来比较一次运行与另一次运行的输出,并且该输出受迭代顺序的影响。
【解决方案3】:

你想要的都是不可能的。任意意味着任意。

我的解决方案与您的解决方案相同,如果您希望能够将其与另一个解决方案进行比较,则必须对其进行排序。

【讨论】:

  • 我猜我认为任意意味着它取决于内容,而不是月相。
  • 嗯,有任意性,然后有不确定性。可能有一种方法可以确定集合中的顺序,但我敢打赌这比它的价值更麻烦。在 python 中检查有序集或类似...
  • 即使每次运行都是一致的,也不能保证在机器之间、python 版本到 python 版本、cpython 与 jython 等之间保持一致。
  • 而且“相同的内容”也不能保证,即使在同一台机器上的同一 Python 版本中也是如此。根据哈希值插入项目。当多个项目具有相同的哈希值时,它们会根据插入的顺序插入不同的位置。删除项目会导致更多不同的排序。还有一些项目的哈希值取决于它们的内存位置,这使得它在运行之间有所不同。除了使用sorted() 方便地编写 3 个步骤之外,您无能为力。
  • 不确定,但我猜在某些时候事情会被地址(即 id())散列,并且系统中的一些异步事物正在以不同的方式扰乱内存管理器跑跑。我根本不希望 cpython 在散列中涉及 PRNG。
【解决方案4】:

集合的迭代顺序不仅取决于其内容,还取决于项目插入集合的顺序,以及沿途是否有删除。因此,您可以创建两个不同的集合,使用不同的插入和删除,最后得到相同的集合,但迭代顺序不同。

正如其他人所说:如果您关心集合的顺序,则必须从中创建一个排序列表。

【讨论】:

  • 使用相同的输入连续两次运行我的程序,涉及相同的插入、删除和设置操作序列,但迭代顺序仍然发生变化。好像还涉及到更多的东西,比如一天中的时间、进程 ID 或其他因运行而异的东西。
  • Thomas Wouters 在他上面的评论中指出,一些类在散列函数中使用 id(),这意味着对象的散列取决于它的内存地址,谁知道可能会有所不同。如果您使用自己的类,则可以编写自己的 hash 函数来消除一些不确定性,但最好还是简单地对结果进行排序。
【解决方案5】:

您的问题变成了两个问题:A)如何在您的具体情况下比较“两次运行的输出”; B)集合中迭代顺序的定义是什么。也许您应该区分它们并在适当的情况下将 B) 作为新问题发布。我会回答A。

恕我直言,在您的情况下使用排序列表不是一个非常干净的解决方案。您应该决定是否一劳永逸地关心迭代顺序并使用适当的结构。

任一 1) 您想比较这两组以查看它们是否具有相同的内容,而不管顺序如何。那么集合上的简单 == 运算符似乎是合适的。见python2 setspython3 sets

或者 2) 你想检查元素是否以相同的顺序插入。但这似乎是合理的,只有当插入顺序对你的库的用户来说很重要时,在这种情况下,使用 set 类型可能一开始就不合适。换句话说,您不清楚“比较两次运行的输出”的确切含义以及您为什么要这样做。

在所有情况下,我都怀疑排序列表在这里是否合适。

【讨论】:

    【解决方案6】:

    您可以将预期结果也设置为一个集合。并使用 == 检查这两组是否相等。

    【讨论】:

      【解决方案7】:

      与集合相反,列表总是有保证的顺序,因此您可以折腾集合并使用列表。

      【讨论】:

      • 是的,但我正在使用多个集合操作(​​联合、交叉点等)创建集合。这些不如列表有效。
      猜你喜欢
      • 1970-01-01
      • 2012-12-03
      • 1970-01-01
      • 1970-01-01
      • 2017-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-07
      相关资源
      最近更新 更多