上两节讲hash表,python里的字典就是通过hash表去实现的。

字典最常用的就是key, value存储,经常用作缓存,他的key值唯一。

内置库里collections.OrderDict保持了key的添加顺序,用之前实现的hash表也能自己实现一个OrderDict。

根据hash表继承他的类来实现字典的抽象数据类型,并支持items(),keys(),values(),赋值操作等。


代码如下:

"""
    继承上一节的hash表的类
"""

class DictADT(HashTable):                       #继承HashTable类
    def __setitem__(self, key, value):		#赋值操作
        self.add(key, value)			#调用add方法,添加键值对

    def __getitem__(self, key):			#取值操作
        if key is not self:			#如果Key并没有在当前的HashTable里面,返回error异常
            raise KeyError()			
        else:
            return self.get(key)		#否则,返回这个key对应的value


    def _iter_slot(self):					#定义一个辅助方法
        for slot in self._table:				
            if slot not in (HashTable.EMPTY, HashTable.UNUSED):	#对于非空的slot,如果slot不是空槽,也不是未被使用的槽
                yield slot					#遍历槽

    def items(self):					#遍历操作
        for slot in self._iter_slot:
            yield (slot.key, slot,value)		#按照python的定义,遍历返回key,value的形式


    def keys(self):				#取key	
        for slot in self._iter_slot():
            yield slot.key

    def values(self):				#取value
        for slot in self._iter_slot():
            yield slot.value
    

"""
   单元测试
"""
    
def test_dict_adt():
    d = Dict()
    d['a'] = 1                  #给'a'赋值,调用setitem方法
    assert d['a'] == 1          #断言值是1
    d.remove('a')               #删除'a'
    import random
    l = list(range(30))         #获取长度为30的列表
    random.shuffle(l)  		#把顺序打乱
    for i in l:
        d.add(i, i)             #向字典中添加元素

    for i in range(30):
        assert d.get(i) == i    #断言取得值等于i
    """测试迭代"""
    assert sorted(list(d.keys())) == sorted(l)	#排序后的key的顺序和排序后列表的顺序一致

如此,字典就基本上实现出来了,主要的主体方法就是这些,都是比较常用的。


Hashable(可哈希的)

作为字典的key,必须是可哈希(hashable)的,也就是说不能使list等这种可变的对象。

举例:

>>> d = dict()
>>> d
{}
>>> d[[1]] = 1        #赋值会报错
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unhashable type: 'list'

元组是不可变的对象

>>> d[(1,2)] = 1
>>> d[(1,2)]
1

在python里面有可哈希的概念,之所以说可变的对象不能作为key,因为在他的生命周期里面,他哈希的值会改变,这是就没有办法每次都哈希的到同一个位置,所以这种对象就不可哈希,也不会作为key;而不可变对象,它的生命周期里我们用hash去获取他的哈希值,他始终是保持一致,这种就可以作为python字典的key。

————————————————————

(1)可变对象与不可变对象的区别:

不可变对象:

    该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。


可变对象:

    该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。


so, Python中,数值类型(int和float)、字符串str、元组tuple都是不可变类型。而列表list、字典dict、集合set是可变类型。


(2)了解__hash__和__eq__的魔术方法,以及何时被调用?

        ① 方法:__hash__

        意义:内建函数hash()调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash。 

        __hash__的使用场景有二:

           a. 被内置函数hash()调用;

           b. hash类型的集合对自身成员的hash操作:set(),  frozenset([iterable]),   dict(**kwarg)

       以下是文档说明:

object.__hash__(self)
 
Called by built-in function hash() and for operations on members of
hashed collections including set, frozenset, and dict.

        如果自定义类没定义__eq__()方法,那也不应该定义__hash__()方法。 

        如果定义了__eq__()方法没有定义__hash__()方法,那么它无法作为哈希集合的元素使用(这个hashable collections值得是set、frozenset和dict)。这其实是因为重写__eq__()方法后会默认把__hash__赋为None(文档后面有说),像list一样。 

         如果定义可变对象的类实现了__eq__()方法,就不要再实现__hash__()方法,否则这个对象的hash值发生变化会导致被放在错误的哈希桶中。这个可以用字典试一下,你的键值不在是一一对应的,只要能让这两个方法返回一致的对象都能改动那个本不属于自己的值



参考资料:

https://blog.csdn.net/lnotime/article/details/81194962(python中的__hash__和__eq__方法之间的一些使用问题)

https://blog.csdn.net/sinat_38068807/article/details/86519944(python 何时单用__hash__或__eq__何时一起用)