可拆分,不可变

从最近的SO问题(请参阅在Python中创build一个由列表索引的字典 ),我意识到我可能对python中的可哈希和不可变对象的含义有错误的概念。

  • 可行性在实践中意味着什么?
  • 可哈希和不可变的关系是什么?
  • 有可变的对象是可哈希或不可变的不可哈代对象?

哈希是以一种可重复的方式将大量数据(通常是一个整数)转换成更小的数据的过程,以便可以在一个表中以恒定的时间( O(1) )查找,这是重要的用于高性能algorithm和数据结构。

不变性是指一个对象在被创build后不会以某种重要的方式改变,特别是以任何可能改变该对象的哈希值的方式。

这两个想法是相关的,因为用作散列键的对象通常必须是不可变的,所以它们的散列值不会改变。 如果允许改变,那么在哈希表等数据结构中该对象的位置就会改变,然后哈希效率的整个目的就被打败了。

要真正掌握这个概念,你应该尝试用C / C ++这样的语言实现自己的散列表,或者阅读HashMap类的Java实现。

从技术上讲,可哈希表示类定义__hash __()。 根据文件:

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

我认为对于python builtintypes,所有可sorting的types也是不可变的。 有一个可变的对象,虽然定义了__hash __(),但是也许不是不可能的。

即使没有明确的关系,由于相互之间的相互作用而在不可变的和可排除的之间强制存在一个隐含的含义

  1. 比较相等的哈希对象必须具有相同的哈希值
  2. 如果一个对象具有在其生命周期中从不改变的散列值,则该对象是可散列的。

这里没有问题,除非你重新定义__eq__所以对象类定义了等价的价值。

一旦你这样做,你需要find一个稳定的散列函数,它总是返回相同的值,代表相同的值的对象(例如__eq__ )返回True,并永远不会在对象的生命周期中更改。

很难看到一个可能的应用程序,考虑可能的A类满足这些要求。 尽pipe__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. 

事实上,这意味着在创build散列(b)==散列(c),尽pipe事实上从来没有比较相等。 无论如何,我很难看到有用地定义__hash__ ()为一个可变对象,它定义了按值比较。

注意__lt____le____gt____ge__比较不受影响,因此您仍然可以定义可__ge__对象的sorting,可变或以其值为基础。

来自Python术语表 :

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

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

所有Python的不可变内置对象都是可散列的,而没有可变容器(如列表或字典)。 对象是用户定义的类的实例默认可哈希; 他们都比较不等,他们的哈希值是他们的id()。

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

虽然没有内置的可变对象是可散列的,但是可以使用不可变的散列值来创build可变对象。 通常只有一部分对象表示其身份,而对象的其余部分则包含可自由更改的属性。 只要哈希值和比较函数是基于身份而不是可变属性,并且身份永远不会改变,您就已经达到了要求。

只是因为这是谷歌最受欢迎的命中,下面是一个简单的方法来做一个可变的对象hashable:

 >>> 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) 

实际上,当创build一个类来将SQLAlchemylogging转换为可变的,对我更有用的类的时候,我实际上find了这样的用法,同时保持了它们作为字典键的可用性。

不可变意味着对象在其生命周期内不会发生任何重大变化。 在编程语言中这是一个模糊但常见的想法。

可哈希性稍有不同,是指比较。

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

所有用户定义的类都有__hash__方法,默​​认情况下只返回对象ID。 所以符合可测性标准的对象不一定是不变的。

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

我们可以说所有的不可变对象都是可哈希的,因为如果哈希在对象的生命期内发生了变化,那么就意味着对象发生了变异。

但不完全。 考虑一个有一个列表(可变)的元组。 有人说元组是不可改变的,但是同时它有点不可抛(throw)。

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

可哈希性和不变性是指对象实例,而不是types。 例如,元组types的对象可以是可散列的或不可散列的。

  • 有可变的对象是可哈希或不可变的不可哈代对象?

在Python中,元组是不可变的,但是只有当它的所有元素都是可散列的时候它才是可散列的。

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

可散列types

  • primefaces不可变types都是可散列的,比如str,bytes,numerictypes
  • 一个冻结的集合总是可散列的(根据定义它的元素必须是可散列的)
  • 一个元组只有在其所有元素都是可散列的时候才是可散列的
  • 用户定义的types默认是可散射的,因为它们的散列值是它们的id()

在Python中,它们大部分是可以互换的。 因为散列应该表示内容,所以它就像对象一样可变,并且使对象更改散列值将使其不能作为字典键。

在其他语言中,散列值与对象的“身份”更相关,而不是(必然)与该值相关。 因此,对于一个可变对象,指针可以用来开始散列。 当然,假设一个对象不会在内存中移动(就像一些GC一样)。 例如,这是Lua中使用的方法。 这使可变对象可用作表键; 但为新手创造了一些(令人不愉快的)意外。

最后,拥有一个不可变的序列types(元组)使得它更适合'多值键'。

Hashable意味着一个variables的值可以用一个常量 – string,数字等来表示(或者说是编码)。现在,一些可以改变的东西(可变的)不能用不是的东西来表示。 因此,任何可变的variables都是不可散列的,同样的,只有不可变的variables才是可散列的。

希望这可以帮助 …