可哈希的,不可变的


Answers:


84

散列是将大量数据以可重复的方式转换为较小量(通常是单个整数)的过程,以便可以在表中以固定时间(O(1))查找数据,这对于高性能很重要算法和数据结构。

不变性是一个想法,即对象创建后将不会以某种重要方式更改,尤其是可能以任何方式更改该对象的哈希值的方式。

这两个想法是相关的,因为用作哈希键的对象通常必须是不可变的,因此它们的哈希值不会改变。如果允许更改,则该对象在诸如哈希表之类的数据结构中的位置将发生变化,从而破坏了哈希效率的整个目的。

要真正理解这个想法,您应该尝试使用C / C ++之HashMap类的语言来实现自己的哈希表,或者阅读该类的Java实现。


1
此外,哈希表不可能检测其键的哈希何时更改(至少以任何有效方式)。这是一个常见的陷阱,例如在Java中,HashMap如果您修改了用作键的对象,则a会损坏:即使打印地图也可以在其中看到旧键或新键。
2015年

1
哈希值和不可变值有些相关,但并不相同。例如,从自定义类继承创建的实例object是可哈希的,但不是不可变的。这些实例可以使用具有字典键的键,但是如果传递它们,仍然可以对其进行修改。
Pranjal Mittal

13
  • 是否存在可哈希的可变对象或不可哈希的不可变对象?

在Python中,元组是不可变的,但仅当其所有元素都是可哈希的时才可哈希。

>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
TypeError: unhashable type: 'list'

哈希类型

  • 原子不可变类型都是可哈希的,例如str,字节,数字类型
  • 冻结集始终是可哈希的(根据定义,其元素必须可哈希)
  • 元组仅在其所有元素都可哈希的情况下才可哈希
  • 默认情况下,用户定义类型是可哈希的,因为它们的哈希值是其id()

8

Python词汇表

如果对象的哈希值在其生命周期内始终不变(需要一种__hash__()方法),并且可以与其他对象进行比较(需要一个__eq__()或,则该对象是可哈希的)__cmp__()方法),。比较相等的可哈希对象必须具有相同的哈希值。

散列性使对象可用作字典键和set成员,因为这些数据结构在内部使用散列值。

Python的所有不可变内置对象都是可哈希的,而没有可变容器(例如列表或字典)是可哈希的。作为用户定义类实例的对象默认情况下可哈希化;它们都比较不相等,并且其哈希值是其id()。

字典和集合必须使用散列以在散列表中进行有效查找;散列值必须是不可变的,因为更改散列会破坏数据结构并导致dict或set失败。使哈希值不可变的最简单方法是使整个对象不可变,这就是为什么经常将两者一起提及的原因。

尽管内置的可变对象都不是可哈希的,但是可以使哈希对象的哈希值不可变。通常仅对象的一部分代表其身份,而对象的其余部分包含可以自由更改的属性。只要哈希值和比较函数基于身份而不是可变属性,并且身份永不更改,就可以满足要求。


@Andrey:frozensets是可哈希的,sets不是;两者都只能包含可哈希项。在马克提到的地方,他是正确的,所以我认为他的意思不是冻结的地方。
tzot 2010年

12
默认情况下,用户定义的类定义可哈希的类型(哈希只是对象的id)。这在对象的生存期内不会更改,因此可以进行哈希处理,但这并不意味着您无法定义可变类型!抱歉,但哈希性并不意味着不变。
Scott Griffiths

1
@ScottGriffiths我不知道为什么我花了6年时间才看到您的评论,但总比没有好。考虑到我一直感叹无法将可变对象放入C ++集合,我不知道我能走得这么远。我希望我的编辑能够解决问题。
Mark Ransom

7

从技术上讲,可哈希表示该类定义了__hash__()。根据文档:

__hash__()应该返回一个整数。唯一需要的属性是比较相等的对象具有相同的哈希值;建议以某种方式将散列值混合在一起(例如,使用异或),以将对象的组成部分也用作对象比较的一部分。

我认为对于Python内置类型,所有可哈希类型也是不可变的。

尽管如此,要定义一个可变的对象将是困难的,但并非并非不可能__hash__()


1
值得注意的__hash__是,默认情况下定义为返回对象的id;。您必须全力以赴__hash__ = None使其无法使用。同样,正如马克·兰瑟姆(Mark Ransom)所提到的那样,还有一个额外的条件,即只有在哈希值永远不会改变的情况下才可以哈希!
Scott Griffiths

5
我认为答案是有点误导,list定义__hash__在这个意义上hasattr([1,2,3], "__hash__")的回报True,但是调用hash([1,2,3])加薪一TypeError(Python 3中),所以它不是完全可哈希。依靠的存在__hash__还不足以确定某物是否为a)可哈希化b)不可变
Matti Lyra

4

即使不可变和可散列之间没有强制的显式关系,也存在隐式关系

  1. 比较相等的可哈希对象必须具有相同的哈希值
  2. 如果对象的哈希值在其生命周期内从未发生变化,则该对象是可哈希的。

除非您重新定义,否则这里没有问题 __eq__,因此objects类定义了值的等价关系。

完成之后,您需要找到一个稳定的哈希函数,该函数始终为表示相同值的对象返回相同的值(例如where __eq__),返回True,并且在对象的生存期内始终不变。

很难找到可能的应用程序,请考虑满足这些要求的可能的A类。尽管存在明显的退化情况,其中__hash__返回常数。

现在:-

>>> a = A(1)
>>> b = A(1)
>>> c = A(2)
>>> a == b
True
>>> a == c
False
>>> hash(a) == hash(b)
True
>>> a.set_value(c)
>>> a == c
True
>>> assert(hash(a) == hash(c)) # Because a == c => hash(a) == hash(c)
>>> assert(hash(a) == hash(b)) # Because hash(a) and hash(b) have compared equal 
                                 before and the result must stay static over the objects lifetime.

实际上,这意味着在创建时hash(b)== hash(c),尽管从来没有将其比较为相等。无论如何,我都很难看到__hash__为定义按值比较的可变对象有用地定义()。

__lt____le____gt____ge__comparsions不受影响,所以你仍然可以定义哈希的对象,可变或以其他方式基于其数值的排序。


3

仅仅因为这是Google的热门话题,这是使可变对象可哈希化的一种简单方法:

>>> class HashableList(list):
...  instancenumber = 0  # class variable
...  def __init__(self, initial = []):
...   super(HashableList, self).__init__(initial)
...   self.hashvalue = HashableList.instancenumber
...   HashableList.instancenumber += 1
...  def __hash__(self):
...   return self.hashvalue
... 
>>> l = [1,2,3]
>>> m = HashableList(l)
>>> n = HashableList([1,2,3])
>>> m == n
True
>>> a={m:1, n:2}
>>> a[l] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> m.hashvalue, n.hashvalue
(0, 1)

实际上,当创建一个类将SQLAlchemy记录转换为对我来说更易变且更有用的东西,同时保持其哈希值以用作dict键时,我实际上发现了这种用法。


3

不变是指对象在其生存期内不会发生任何重大变化。在编程语言中,这是一个模糊但普遍的想法。

哈希性略有不同,指的是比较。

哈希的一个目的是可哈希如果它有一个哈希值其寿命(它需要一个在这期间从不改变__hash__()方法),并且可相对于其他对象(它需要一个__eq__()__cmp__()方法)。比较相等的可哈希对象必须具有相同的哈希值。

所有用户定义的类都有 __hash__方法,默​​认情况下,该方法仅返回对象ID。因此,满足散列性标准的对象不一定是不变的。

您声明的任何新类的对象都可以用作字典键,除非您通过例如从 __hash__

我们可以说所有不可变的对象都是可哈希的,因为如果哈希在对象的生命周期内发生变化,则意味着该对象已突变。

但不完全是。考虑一个具有列表(可变)的元组。有人说元组是不可变的,但同时它有些不可散列(抛出)。

d = dict()
d[ (0,0) ] = 1    #perfectly fine
d[ (0,[0]) ] = 1  #throws

哈希性和不变性是指对象实例,而不是类型。例如,一个元组类型的对象可以是不可哈希的。


1
“比较相等的可哈希对象必须具有相同的哈希值。” 为什么?我可以创建比较相等但没有相同哈希值的对象。
endlith 2014年

1
可以创建这样的对象,但这将违反Python文档中定义的概念。想法是,实际上,我们可以使用此要求来得出这样的含义(在逻辑上等效):如果哈希不相等,则对象不相等。很有用。许多实现,容器和算法都依靠这种含义来加快处理速度。
user2622016 2014年

常见的情况comparison != identity是将“无效”值(例如float("nan") == float("nan"))或切片中的"apple" is "apple""apple" is "crabapple"[4:]
插入

1

在Python中,它们几乎可以互换;因为哈希应该表示内容,所以它和对象一样易变,并且更改对象的哈希值会使它无法用作dict键。

在其他语言中,哈希值与对象“身份”更多相关,而与(不必)与该值相关。因此,对于可变对象,可以使用指针开始哈希。当然,假设某个对象不会在内存中移动(就像某些GC一样)。例如,这就是Lua中使用的方法。这使得可变对象可用作表键;但是会给新手带来一些(不愉快的)惊喜。

最后,拥有不可变的序列类型(元组)使其更适合“多值键”。


3
@javier:“在Python中,它们几乎是可互换的”我的疑问是指“大部分”中未包含的一小部分
joaquin 2010年

0

Hashable表示变量的值可以用常量(字符串,数字等)表示(或编码)。现在,可能发生变化(可变)的事物不能用非常量表示。因此,任何可变的变量都不能是可散列的,并且出于同样的原因,只有不可变的变量才可以是散列的。

希望这可以帮助 ...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.