字典和集合的实现原理

泛映射类型

只有可散列的数据类型才能用作这些映射里的键。

如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 __hash__()方 法。另外可散列对象还要有 __eq__()方法,这样才能跟其他键做比较。

因此,原子不可变数据类型是可散列的。用户自定义的类型的对象一般是可散列的,散列值就是id()的返回值,但如果其实现了__eq__()方法,并且方法中用到了这个对象的内部状态,那么只有当这些内部状态都不可变时,对象才是可散列的。

字典的常用构建方法:

>>> a = dict(one=1, two=2, three=3) 
>>> b = {'one': 1, 'two': 2, 'three': 3} 
>>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) 
>>> d = dict([('two', 2), ('one', 1), ('three', 3)]) 
>>> e = dict({'three': 3, 'one': 1, 'two': 2}) 
>>> a == b == c == d == e True

另外还有字典推导( dictcomp )式:

一些特殊的映射

defaultdict:处理找不到的键

在实例化一个 defaultdict 的时候,需要给构造方法提供 一个可调用对象,这个可调用对象会在__getitem__碰到找不到的键的时候被调用,让__getitem__返回某种默认值, 并且这个返回值和对应的键被对应起来。

这个用来生成默认值的可调用对象被存放在名为default_factory的实例属性中,这个属性只会在__getitem()中被调用,在其他方法(如get)中不会发挥作用。

特殊方法__missing__

当一个类继承了dict并且实现了方法__missing__时,在__getitem__碰到找不到的键的时候,Python 就会自动调用它, 而不是抛出一个 KeyError 异常。

class StrKeyDict0(dict):
    def __missing__(self, key):        
        if isinstance(key, str):
            raise KeyError(key)        
        return self[str(key)]
    def get(self, key, default=None):        
        try:            
            return self[key]   
        except KeyError:        
            return default
	def __contains__(self, key):    
        return key in self.keys() or str(key) in self.keys()

字典的变种

  • collections.OrderedDict:
    这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。
    OrderedDict 的 popitem 方法默认删除并返回的是字典里的最后一个元素,但是如果像 my_odict.popitem(last=False) 这样调用它,那么它删除并返回第一个被添加进去的元素。
  • collections.ChainMap:
    该类型可以容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当作一个整体被逐个查找,直到键被找到为止。例如python解释器的变量查询规则如下:
import builtins 
pylookup = ChainMap(locals(), globals(), vars(builtins))
  • collections.Counter:
    用于实现计数器。
  • colllections.UserDict :
    供用户继承编写子类。

集合

集合中的元素必须是可散列的,所以集合中不能包含集合但是可以包含frozenset。

构建集合使用{},但构建空集要使用set()。

集合的常见操作:

  • 数学运算 &求交集,|求并集,-求差集,^求对称差集。
  • 比较运算符:a.isdisjoint(b)判断a与b是否不相交,in判断属于,>,>=,<,<=用于判断子集,a.issubset(b),a.issuperset(b),将可迭代的b转化为集合,然后进行判断。
  • 其他方法:add(e)添加元素e;discard(e)如果有,移出元素e;remove(e),移除元素e,若无抛出KeyError。

dict和set的实现原理

dict和set背后是通过散列技术实现的,因此查找的效率非常高。

这也带来了以下的一些影响:

  1. 键必须是可散列的:
  • 支持hash函数,通过hash函数应该得到一个不变的散列值;
  • 支持通过__eq__()来检测相等性;
  • 一致性:若hash(a) == hash(b)则必须要有 a == b。
  1. 内存开销巨大
  2. 键查询很快:几乎是常数时间访问
  3. 键的顺序取决于添加顺序;添加新键可能会改变已有键的顺序:由于散列冲突的处理

h(b)则必须要有 a == b。
2. 内存开销巨大
3. 键查询很快:几乎是常数时间访问
4. 键的顺序取决于添加顺序;添加新键可能会改变已有键的顺序:由于散列冲突的处理