【问题标题】:Why are tuples constructed from differently initialized sets equal?为什么由不同初始化的集合构成的元组是相等的?
【发布时间】:2014-11-24 18:46:49
【问题描述】:

我期望以下两个元组

>>> x = tuple(set([1, "a", "b", "c", "z", "f"]))
>>> y = tuple(set(["a", "b", "c", "z", "f", 1]))

比较不平等,但他们不:

>>> x == y
>>> True

这是为什么呢?

【问题讨论】:

  • 你为什么期望它是False - 两组的内容是一样的!
  • Relevant. 正如 Zero Piraeus 所说,散列随机化使得 对于字符串、字节和日期来说这是不真实的,因为 hash(a_string) 会在您每次实例化解释器时发生变化。

标签: python set comparison tuples hashtable


【解决方案1】:

乍一看,x 似乎应该总是等于y,因为由相同元素构造的两个集合总是相等的:

>>> x = set([1, "a", "b", "c", "z", "f"])
>>> y = set(["a", "b", "c", "z", "f", 1])
>>> x
{1, 'z', 'a', 'b', 'c', 'f'}
>>> y
{1, 'z', 'a', 'b', 'c', 'f'}
>>> x == y
True

然而并非总是由两个相等的集合构成的元组(或其他有序集合)相等。

实际上,您比较的结果有时是True,有时是False,至少在Python >= 3.3 中。测试以下代码:

# compare.py
x = tuple(set([1, "a", "b", "c", "z", "f"]))
y = tuple(set(["a", "b", "c", "z", "f", 1]))
print(x == y)

...一千次:

$ for x in {1..1000}
> do
>   python3.3 compare.py
> done | sort | uniq -c
147 False
853 True

这是因为,从 Python 3.3 开始,字符串、字节和日期时间的哈希值是随机的,因为 security fix 的结果。根据哈希值的不同,可能会发生“冲突”,这意味着订单项存储在底层数组中(因此迭代顺序)取决于插入顺序。

以下是文档中的相关内容:

安全改进:

  • 哈希随机化默认开启。

——https://docs.python.org/3/whatsnew/3.3.html

编辑:因为在 cmets 中提到,上面的 True/False 比率表面上令人惊讶......

集合和字典一样,是作为哈希表实现的——所以如果发生冲突,表中项目的顺序(以及迭代的顺序)将取决于首先添加的项目(在x 中有所不同)和y 在这种情况下)和用于散列的种子(自 3.3 以来的 Python 调用不同)。由于在设计上碰撞很少见,并且此问题中的示例是较小的集合,因此该问题并不像人们最初想象的那样经常出现。

有关 Python 实现字典和集合的详细说明,请参阅The Mighty Dictionary

【讨论】:

  • @stalk 正如我所说,从 Python 3.3 开始,行为发生了变化。另请参阅object.__hash__:“更改哈希值会影响字典、集合和其他映射的迭代顺序。Python 从未对此顺序做出保证(它通常在 32 位和 64 位之间变化构建)。” (我的重点)
  • 哈希很可能仍然是完全随机的,但 853/1000 并不是冲突率的度量,它是哈希在两个不同集合之间的顺序相同的度量。取决于哈希的确切计算方式 - hash("a")
  • @TonySuffolk66 不,它冲突的度量 - 重要的是底层数组中的顺序,它只会在 xy 之间有所不同,如果两个项目的(截断)散列是相同的(因此插入的第二个项目必须根据其散列转到第一个选项以外的其他位置)。
  • @Floris 数字是 5/32。见:stackoverflow.com/questions/26136894/…
  • @TonySuffolk66 这正是碰撞率的衡量标准。见:stackoverflow.com/questions/26136894/…
【解决方案2】:

这里有两件事在起作用。

  1. 集合是无序的。 set([1, "a", "b", "c", "z", "f"])) == set(["a", "b", "c", "z", "f", 1])

  2. 当您通过 tuple 构造函数将集合转换为元组时,它实质上是迭代集合并添加迭代返回的每个元素。

元组的构造函数语法是

tuple(iterable) -> tuple initialized from iterable's items

调用tuple(set([1, "a", "b", "c", "z", "f"]))与调用tuple([i for i in set([1, "a", "b", "c", "z", "f"])])是一样的

的值
[i for i in set([1, "a", "b", "c", "z", "f"])]

[i for i in set(["a", "b", "c", "z", "f", 1])]

在同一个集合上迭代是一样的。

编辑感谢@ZeroPiraeus(查看他的answer)。这不能保证。即使对于同一个集合,迭代的值也不总是相同的。

元组构造函数不知道集合的构造顺序。

【讨论】:

  • 那么是否可以保证,如果两个sets中的值相同,它们会以相同的顺序迭代?
  • @stalk ,不是:见my answer
  • 您应该按照@ZeroPiraeus 的回答更新您的答案——“_ 和 _ 的值在迭代同一集合时是相同的”并不总是正确的。
【解决方案3】:

集合没有顺序,仅由它们的成员资格定义。

例如,set([1, 2]) == set([2, 1])

如果元组在每个位置的成员相等,则元组是相等的,但是由于从集合中创建元组的集合是相等的(按递增顺序),因此元组最终也是相等的。

【讨论】:

    【解决方案4】:

    所以你有两个列表 - 它们具有相同的内容但顺序不同,你将它们转换为集合 - 这将是相等的,因为它们具有相同的内容。

    当您将这些集合转换为元组时,它们将以相同的顺序转换,因为它们是相同的集合,因此元组将是相同的。

    这在 Python2.7 中是正确的 - 但从 3.3 开始,当哈希被随机化时,您将无法保证这一点 - 因为这两个集合虽然内容相同,但不一定会以相同的顺序迭代。

    【讨论】:

    • 我没有投反对票,但你的答案是不正确的,不管哈希随机化如何:例如,在自 2.4 以来的每个 Python 中(当 set 成为内置函数时),tuple(set([0, 8])) != tuple(set([8, 0])) (和相同的适用于 Python 2.3 中的sets.Set)。
    • 所以一个集合没有做集合应该做的事情——即当内容相等时相等——很奇怪。
    • 两个对象相等的事实并不一定意味着对它们进行相同操作的结果应该是。在这种情况下,结果的出现是因为使用具有不同迭代顺序的两个相等集合来构造一个关心顺序的对象,但还有其他示例,例如8.0 == 8str(8.0) != str(8).
    猜你喜欢
    • 1970-01-01
    • 2016-12-18
    • 1970-01-01
    • 2019-02-11
    • 2016-02-22
    • 1970-01-01
    • 1970-01-01
    • 2020-05-28
    • 1970-01-01
    相关资源
    最近更新 更多