可洗,不变

时间:2010-04-19 22:32:08

标签: python data-structures hash immutability

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

  • 在实践中可以使用什么意思?
  • hashable和immmutable之间的关系是什么?
  • 是否有可变对象是可清洗或不可变的对象?

9 个答案:

答案 0 :(得分:83)

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

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

这两个想法是相关的,因为用作散列键的对象通常必须是不可变的,因此它们的散列值不会改变。如果允许更改,那么该对象在数据结构(如哈希表)中的位置会发生变化,然后效率散列的整个目的就会失效。

要真正理解您应该尝试使用C / C ++等语言实现自己的哈希表,或者阅读O(1)类的Java实现。

答案 1 :(得分:10)

  
      
  • 是否有可变对象是可散列的或不可变的对象?
  •   

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

NULL

可灌输类型

  • 原子不可变类型都是可哈希的,例如str,bytes,numeric types
  • 冻结集始终可以清洗(其元素必须可以按照定义进行清洗)
  • 元组只有在其所有元素都可以播放时才可以播放
  • 默认情况下,用户定义的类型是可哈希的,因为它们的哈希值是它们的id()

答案 2 :(得分:8)

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

  

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

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

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

答案 3 :(得分:7)

来自Python Glossary

  

如果对象具有在其生命周期内永远不会更改的哈希值(它需要__hash__()方法),并且可以与其他对象进行比较(它需要__eq__()或{{ 1}}方法)。比较相等的Hashable对象必须具有相同的哈希值。

     

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

     

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

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

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

答案 4 :(得分:4)

由于

之间的相互作用,即使在不可变和可散列之间没有明确的关系,也存在隐含的含义。
  1. 比较相等的Hashable对象必须具有相同的哈希值
  2. 如果对象具有在其生命周期内永远不会更改的哈希值,则该对象是可清除的。
  3. 除非您重新定义__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__()的可变对象,它定义了按值比较。

    注意__lt____le____gt____ge__比较不受影响,因此您仍然可以定义可清除对象的排序,可变的或基于其价值的其他方式。

答案 5 :(得分:3)

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

>>> 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键使用的可用性。

答案 6 :(得分:3)

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

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

  

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

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

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

来阻止它

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

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

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

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

答案 7 :(得分:1)

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

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

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

答案 8 :(得分:0)

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

希望这会有所帮助......