【问题标题】:Difference between hash() and id()hash() 和 id() 的区别
【发布时间】:2015-12-21 18:57:15
【问题描述】:

我有两个用户定义的对象,比如 ab
这两个对象都具有相同的hash 值。
但是,id(a)id(b) 是不相等的。

此外,

>>> a is b
False
>>> a == b
True

从这个观察中,我可以推断出以下内容吗?

  • 不相等的对象可能具有相同的hash 值。
  • 相等的对象需要具有相同的id 值。
  • 每当调用obj1 is obj2 时,都会比较两个对象的id 值,而不是它们的hash 值。

【问题讨论】:

  • 通过比较id(a)id(b),您的第二个推断很容易失效。
  • @chepner 谢谢。我现在明白了
  • 我写了如何为内置类型 here 计算哈希值。您可以注意到对象类型的hash 计算取决于其id
  • 这与@Blckknght 在他的回答中所说的直接矛盾。他说哈希计算取决于对象中包含的值。
  • @DarshanChaudhary:你误解了 Delimetry 的评论。当他说“对象”(不是一般的对象)时,他指的是object 类型。 object 类型的实例除了其标识之外没有任何价值。默认情况下,也不做自定义类的实例。要为自定义类提供对“值”的不同解释,您需要为其提供 __eq__ 方法(以及基于相同类型值的 __hash__ 方法,如果您希望它是可散列的)。

标签: python hash equality


【解决方案1】:

在尝试理解 idhash 以及 ==is 运算符时需要掌握三个概念:identityvalue 和 哈希值。并非所有对象都具有这三个。

  1. 所有对象都有一个identity,尽管在​​某些情况下这可能会有些麻烦。 id 函数返回一个对应于对象标识的数字(在 cpython 中,它返回对象的内存地址,但其他解释器可能返回其他内容)。如果两个对象(同时存在)具有相同的标识,则它们实际上是对同一对象的两个引用。 is 运算符按身份比较项目,a is b 等价于 id(a) == id(b)

    当您处理缓存在其实现中某处的对象时,标识可能会有些混乱。例如,cpython 中用于小整数和字符串的对象不会在每次使用时都重新制作。相反,现有对象会在需要时随时返回。但是,您不应该在代码中依赖它,因为它是 cpython 的实现细节(其他解释器可能会以不同的方式或根本不这样做)。

  2. 所有对象也都有一个,虽然这有点复杂。一些对象除了它们的身份之外没有有意义的值(因此在某些情况下,值一个身份可能是同义词)。值可以定义为== 运算符比较的值,因此任何时候a == b,您都可以说ab 具有相同的值。容器对象(如列表)具有由其内容定义的值,而其他一些类型的对象将具有基于其属性的值。不同类型的对象有时可以具有相同的值,例如数字:0 == 0.0 == 0j == decimal.Decimal("0") == fractions.Fraction(0) == False(是的,bools 在 Python 中是数字,出于历史原因)。

    如果一个类没有定义__eq__ 方法(以实现== 运算符),它将从object 继承默认版本,并且其实例将仅通过它们的身份进行比较。当否则相同的实例可能具有重要的语义差异时,这是合适的。例如,连接到同一主机的同一端口的两个不同套接字需要区别对待,如果一个正在获取 HTML 网页,另一个正在获取从该页面链接的图像,因此它们没有相同的值。

  3. 除了值之外,一些对象还有一个散列值,这意味着它们可以用作字典键(并存储在sets 中)。函数hash(a) 返回对象a 的哈希值,一个基于对象值的数字。对象的哈希值在对象的生命周期内必须保持不变,因此只有当对象的值是不可变的(或者因为它基于对象的身份,或者因为它基于对象本身是不可变的)。

    多个不同的对象可能具有相同的哈希值,尽管设计良好的哈希函数会尽可能避免这种情况。在字典中存储具有相同散列的对象比存储具有不同散列的对象效率低得多(每次散列冲突都需要更多的工作)。默认情况下,对象是可散列的(因为它们的默认值是它们的身份,这是不可变的)。如果您在自定义类中编写 __eq__ 方法,Python 将禁用此默认哈希实现,因为您的 __eq__ 函数将为它的实例定义新的 value 含义。如果您希望您的类仍然是可散列的,您还需要编写一个 __hash__ 方法。如果你继承自一个可散列的类,但又不想自己是可散列的,你可以在类体中设置__hash__ = None

【讨论】:

  • 非常详尽的解释,我喜欢在为自己的类自定义 eq 时实现自己的 hash 方法的提示。不知道!
  • 很好的答案!一个问题:哈希值用作什么字典的字典键?什么的字典,内存地址位置字典?
  • @bretonics:字典是 Python 中的内置数据类型。所有键的哈希值用于查找存储在字典中的值。例如,如果您创建对象o=SomeClass() 并将其用作字典d = {}; d[o] = something 中的键,则o 的哈希值在d 内部用于确定存储something 的位置。使用对象和字典时,您根本不直接与哈希值交互。最多只需要在SomeClass 中写一个__hash__ 方法(需要时会被字典自动调用)。
  • 我们可以使用 id 作为哈希函数吗?
  • @LeoUfimtsev:是的,AFAIK 它是默认的__hash__ 定义,但是一旦你定义了自定义__eq__,它就会被忽略并且没有指定
【解决方案2】:

不相等的对象可能具有相同的哈希值。

是的,这是真的。一个简单的例子是 CPython 中的hash(-1) == hash(-2)

相等的对象需要有相同的 id 值。

不,这通常是错误的。 @chepner 指出的一个简单反例是 5 == 5.0id(5) != id(5.0)

每当调用 obj1 is obj2 时,都会比较两个对象的 id 值,而不是它们的哈希值。

是的,这是真的。 is 比较对象的 id 是否相等(在 CPython 中它是对象的内存地址)。一般来说,这与对象的哈希值无关(对象甚至不需要是可哈希的)。

【讨论】:

  • 第二个断言的反例:5 == 5.0 vs id(5) == id(5.0)
  • @ajcr 即使[] is [] 返回False,即使id([]) == id([]) 返回True。你也能解释一下吗?
  • @chepner:这是一个更好的反例。介意我盗用它作为答案吗(当然要注明出处)?
  • @KshitijSaraogi 用a = []b = [] 再试一次;其实id([]) == id([])是因为两个空的list对象不需要同时存在,所以id可以回收。
  • @ajcr 随意;我打算将它包含在我自己的答案中,但你涵盖了我要说的所有内容。
【解决方案3】:

哈希函数用于:

在字典查找过程中快速比较字典键

ID 函数用于:

返回对象的“身份”。这是一个整数,保证在其生命周期内对于该对象是唯一且恒定的。具有不重叠生命周期的两个对象可能具有相同的 id() 值。

【讨论】:

  • 那么为什么不使用 id 进行哈希呢?
猜你喜欢
  • 1970-01-01
  • 2011-05-16
  • 2015-10-31
  • 1970-01-01
  • 2011-02-02
  • 2012-12-21
  • 1970-01-01
  • 2012-10-25
相关资源
最近更新 更多