【问题标题】:when compare by id is used in Python? Dictionary key comparison?什么时候在 Python 中使用按 id 比较?字典键比较?
【发布时间】:2012-09-21 22:00:14
【问题描述】:

这是什么意思?

唯一不能作为字典键接受的值类型是包含列表或字典或其他可变类型的值,它们按值而不是对象标识进行比较,原因是字典的有效实现需要保留键的哈希值常数。

我认为即使对于元组,比较也会按值进行。

【问题讨论】:

    标签: python


    【解决方案1】:

    将可变对象作为键的问题在于,当我们使用字典时,我们很少想要检查身份。例如,当我们使用这样的字典时:

    a = "bob"
    test = {a: 30}
    print(test["bob"])
    

    我们希望它能够工作——第二个字符串"bob" 可能与a 不同,但它是相同的值,这是我们关心的。这适用于任何两个相等的字符串将具有相同的哈希,这意味着dict(实现为哈希图)可以非常有效地找到这些字符串。

    当我们有一个列表作为键时,问题就出现了,想象一下这种情况:

    a = ["bob"]
    test = {a: 30}
    print(test[["bob"]])
    

    我们不能再这样做了 - 因为列表的哈希值不是基于它的值,而是基于列表的实例(又名(id(a) != id(["bob"))),所以比较将不起作用。

    Python 可以选择更改列表的哈希值(破坏哈希图的效率)或简单地比较身份(这在大多数情况下是无用的)。 Python 不允许使用这些特定的可变键以避免微妙但常见的错误,即人们期望值等同于值而不是身份。

    【讨论】:

    • -1 不,这不是问题所在。您可以拥有一个具有一致哈希的可变对象,只是不能将可变状态放入哈希中。遵守该限制的最常见和最常用的散列函数是恒等散列函数,即id(self) 的函数。当然,相等性也必须定义为身份,只有 then 可变容器成为问题,因为我们通常希望通过值而不是身份来比较它们。
    • @delnan 这就是我想要表达的观点——我似乎没有说清楚什么是重要的,我会尽量弄清楚。
    • 删除了我的反对票,但我仍然感叹您的回答给人的印象是可变对象一般不能是字典键。
    • @delnan 我在最后添加了一些内容以尝试使其更清晰。
    • 从您的答案中得出的以下推论是否正确? " 对于可变类型,python 实现无法创建散列/唯一值。如果将实现修改为使用 id() 创建散列/唯一值并允许使用可变类型作为字典键,则将在三项检查中的第一项(哈希,然后是值)。在其他情况下,python 实现不使用按 id 进行比较"
    【解决方案2】:

    文档将两种不同的东西混合在一起:可变性和价值可比性。让我们把它们分开。

    • 按身份比较的不可变对象很好。身份可以 永远不会改变,对于任何对象。

    • 按值比较的不可变对象很好。价值永远不可能 更改为不可变对象。这包括元组。

    • 通过身份比较的可变对象很好。身份可以 永远不会改变,对于任何对象。

    • 不接受按值比较的可变对象。价值 可以更改为可变对象,这将使字典 无效。

    同时,您的措辞与映射类型(4.10 in Python 3.35.8 in Python 2.7,两者都说:

    字典的键几乎是任意值。不可散列的值,即包含列表、字典或其他可变类型的值(按值而不是对象标识进行比较)不能用作键。

    无论如何,这里的重点是规则是“不可散列的”; “可变类型(通过值而不是对象身份进行比较)”只是为了进一步解释事情。严格来说,按对象身份进行比较和按对象身份进行散列并不总是相同的(唯一需要的是,如果 id 相等,则散列相等)。

    您发布的版本中有关“有效实现字典”的部分只会增加混乱(这可能是它不在参考文档中的原因)。即使明天有人想出了一种有效的方法来处理将列表存储为 dict 键,该语言也不允许这样做。

    【讨论】:

    • 高效的实现很重要,因为这就是限制的原因。如果有人找到了一种无需一致哈希即可执行高效查找的方法,则可能会更改实现。
    • @Lattyware:好吧,即使发生这样的发现,4.x 字典也可能允许任意键,但我怀疑它们会更改 2.x,甚至可能不会更改 3。 X。它将要求在所有 Python 中执行特定(并且可能很复杂)的实现,即到 Java/.NET/ObjC/等的透明映射。 hashmaps 会被破坏,JSON 编码的规则会改变,等等。无论如何,它显然会妨碍解释应该是一个非常简单的规则,否则 OP 不会被混淆。不是在任何地方都不值得一提,只是不在那里。
    【解决方案3】:

    哈希是计算一个对象的唯一代码的方法,这个代码对于同一个对象总是相同的。 hash('test') 例如是 2314058222102390712,所以是 a = 'test'; hash(a) = 2314058222102390712

    在内部,字典值是通过哈希而不是您指定的变量来搜索的。一个列表是可变的,一个列表的散列,如果它在哪里被定义,它会随着列表的改变而改变。因此python的设计没有散列列表。因此,列表不能用作字典键。

    元组是不可变的,因此元组具有哈希值,例如hash((1,2)) = 3713081631934410656。可以通过比较哈希而不是值来比较元组a 是否等于元组(1,2)。这会更有效率,因为我们只需要比较一个值而不是两个值。

    【讨论】:

    • 实际上,比较散列的效率较低 - 您必须进行三项检查(散列,然后是值) - 相同的散列告诉你值是一样的——只是它们可能是一样的。当然,在宏伟的计划中,它通过使初始检查非常简单来提高效率(通过数据结构时,你知道你会得到比正数更多的错误命中)。
    • 这回答(并且非常好)除了 OP 询问的部分之外的所有内容:不能将列表用作键的原因是因为它们是可变的它们的相等性和哈希是基于它们的值。如果它们是可变的,但它们的相等性和哈希是基于 id 的,那将是可以接受的。问题在于这种区别与可变/不可变区别一样多,答案是只有当它既可变又基于值时才会出现问题。
    猜你喜欢
    • 1970-01-01
    • 2011-02-21
    • 2010-09-30
    • 2019-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-08
    • 2010-12-07
    相关资源
    最近更新 更多