可洗,不变

问题描述 投票:74回答:9

从最近的SO问题(参见Create a dictionary in python which is indexed by lists)中我意识到我可能对python中可散列和不可变对象的含义有一个错误的概念。

  • 在实践中,什么是可以平均的?
  • hashtable和immutable之间有什么关系?
  • 是否有可变对象是可清洗的或不可变的对象?
python data-structures hash immutability
9个回答
84
投票

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

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

这两个想法是相关的,因为用作散列键的对象通常必须是不可变的,因此它们的散列值不会改变。如果允许更改,则该对象在诸如散列表之类的数据结构中的位置将改变,然后用于效率的散列的整个目的被打败。

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


11
投票
  • 是否有可变对象是可清洗的或不可变的对象?

在Python中,元组是不可变的,但只有当它的所有元素都是可哈希的时候才可以使用它。

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

可洗类型

  • 原子不可变类型都是可清除的,例如str,bytes,numeric类型
  • 冻结集总是可以清除的(根据定义,其元素必须是可清除的)
  • 元组只有在其所有元素都可以清洗时才可以清洗
  • 用户定义的类型默认是可哈希的,因为它们的哈希值是它们的id()

8
投票

从技术上讲,hashable意味着该类定义__hash__()。根据文件:

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

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

有一个可变对象仍然很难,但也许并非不可能,但仍定义__hash__()


7
投票

来自Python Glossary

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

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

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

Dicts和sets必须使用哈希来在哈希表中进行有效查找;哈希值必须是不可变的,因为更改哈希会弄乱数据结构并导致dict或set失败。使哈希值不可变的最简单方法是使整个对象不可变,这就是为什么这两个经常被一起提到的原因。

虽然没有内置的可变对象是可以清除的,但是可以使用不可变的哈希值来创建一个可变对象。通常只有对象的一部分表示其标识,而对象的其余部分包含可自由更改的属性。只要哈希值和比较函数基于标识但不基于可变属性,并且标识永远不会更改,您就满足了要求。


4
投票

即使由于相互之间的相互作用而在不可变和可散列之间没有明确的关系,也存在隐含

  1. 比较相等的可哈希对象必须具有相同的哈希值
  2. 如果对象具有在其生命周期内永远不会更改的哈希值,则该对象是可清除的。

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

一旦你完成了这个,你需要找到一个稳定的哈希函数,它总是为表示相同值的对象返回相同的值(例如,__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__(),该对象定义了按值比较。

注意:qazxsw poi,qazxsw poi,__lt____le__比较不受影响,因此您仍然可以定义可散列对象的顺序,可变或基于其值。


3
投票

只是因为这是谷歌的热门话题,这里有一个简单的方法可以使可变对象变为可清除:

__gt__

实际上,当创建一个类来将SQLAlchemy记录转换为可变且对我更有用的东西时,我确实找到了类似这样的用法,同时保持了它们作为dict键使用的可用性。


3
投票

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

可持续性略有不同,并指比较。

__ge__如果一个对象具有一个在其生命周期内永远不会改变的哈希值(它需要一个>>> 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) 方法),并且可以与其他对象进行比较(它需要一个hashable__hash__()方法),则该对象是可清除的。比较相等的可哈希对象必须具有相同的哈希值。

所有用户定义的类都有__eq__()方法,默认情况下只返回对象ID。因此,满足可持续性标准的对象不一定是不可变的。

您声明的任何新类的对象都可以用作字典键,除非您通过例如从__cmp__()抛出来阻止它

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

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

__hash__

可持续性和不变性是指对象实例,而不是类型。例如,类型元组的对象可以是可以清洗的。


1
投票

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

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

最后,具有不可变序列类型(元组)使得“多值键”更好。


0
投票

Hashable意味着变量的值可以用常量 - 字符串,数字等来表示(或者更确切地说,编码)。现在,某些可能发生变化(可变)的东西不能用不可能的东西来表示。因此,任何可变的变量都不能被散列,并且同样地,只有不可变变量可以是可散列的。

希望这可以帮助 ...

© www.soinside.com 2019 - 2024. All rights reserved.