python 字典的变种

下面总结了标准库里 collections 模块中,除了 defaultdict 之外的不同映射类型。

collections.OrderedDict

这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的OrderedDictpopitem 方法默认删除并返回的是字典里的最后一个元素,但是如果像 my_odict.popitem(last=False) 这样调用它,那么它删除并返回第一个被添加进去的元素。

collections.ChainMap

该类型可以容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当作一个整体被逐个查找,直到键被找到为止。这个功能在给有嵌套作用域的语言做解释器的时候很有用,可以用一个映射对象来代表一个作用域的上下文。在 collections
文档介绍 ChainMap 对 象 的 那 一 部 分(https://docs.python.org/3/library/collections.html# collections.ChainMap)里有一些具体的使用示例,其中包含了下面这个 Python 变量查询规则的代码片段:

import builtins
pylookup = ChainMap(locals(), globals(), vars(builtins))

collections.Counter

这个映射类型会给准备一个整数计数器每次更新一个的时候都会增加这个计数器。所以这个类型可以用来给可散列表对象计数或者是当成多重集来用——多重集合就是集合里的元素可以出现不止一次Counter 实现了 +- 运算符用来合并记录,还有像 most_common([n]) 这类很有用的方法。most_common([n]) 会按照次序返回映射里最常见n 个键它们的计数,详情参阅文档(https://docs.python.org/3/library/collections.html#collections.Counter)。下面的小例子利用 Counter 来计算单词中各个字母出现的次数:

import collections
ct = collections.Counter('abracadabra')
dt = collections.Counter('fqwefawraaaa')
# Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(ct)
ct.update('aaaaazzz')
print(ct)
# Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(ct.most_common(2))
# [('a', 10), ('z', 3)]
print(ct + dt)
# Counter({'a': 15, 'r': 3, 'z': 3, 'b': 2, 'f': 2, 'w': 2, 'c': 1, 'd': 1, 'q': 1, 'e': 1})

colllections.UserDict

这个类其实就是把标准 dict 用纯 Python 又实现了一遍。跟 OrderedDictChainMapCounter 这些开箱即用的类型不同,UserDict 是让用户继承写子类的。

就创造自定义映射类型来说,以 UserDict 为基类,总比以普通的 dict 为基类要来得方便。这体现在,我们能够改进 博客:python 字典 d[k]中key不存在的解决方案 中定义的 StrKeyDict0 类(https://blog.csdn.net/MZP_man/article/details/135665825?spm=1001.2014.3001.5502)➊,使得所有的键都存储为字符串类型。

而更倾向于从 UserDict 而不是从 dict 继承的主要原因是,后者有时会在某些方法的实现上走一些捷径,导致我们不得不在它的子类中重写这些方法,但是 UserDict 就不会带来这些问题。

另外一个值得注意的地方是,UserDict 并不是 dict子类,但是 UserDict 有一个叫作data 的属性,是 dict 的实例,这个属性实际上是 UserDict 最终存储数据的地方。这样做的好处是,比起示例 ➊,UserDict 的子类就能在实现 __setitem__ 的时候避免不必要的递归,也可以让 __contains__ 里的代码更简洁。

多亏了 UserDict,在下面示例里的 StrKeyDict 的代码比示例➊里的 StrKeyDict0 要短一些,功能却更完善:它不但把所有的键都以字符串的形式存储,还能处理一些创建或者更新实例时包含非字符串类型的键这类意外情况。

class StrKeyDict(collections.UserDict):  # StrKeyDict 是对 UserDict 的扩展。

    def __missing__(self, key):  # __missing__ 跟之前➊示例里的一模一样。
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    def __contains__(self, key):
        # __contains__ 则更简洁些。这里可以放心假设所有已经存储的键都是字符串。因此,只
        # 要在 self.data 上查询就好了,并不需要像 StrKeyDict0 那样去麻烦 self.keys()。
        return str(key) in self.data

    def __setitem__(self, key, item):
        # __setitem__ 会把所有的键都转换成字符串。由于把具体的实现委托给了 self.data 属
        # 性,这个方法写起来也不难。
        self.data[str(key)] = item

因为 UserDict 继承的是 MutableMapping,所以 StrKeyDict剩下的那些映射类型的方法都是从 UserDictMutableMappingMapping 这些超类继承而来的。特别是最后的 Mapping类,它虽然是一个抽象基类(ABC),但它却提供了好几个实用的方法。以下两个方法值得关注

  • MutableMapping.update
    这个方法不但可以为我们所直接利用,它还用在 __init__ 里,让构造方法可以利用传入的各种参数(其他映射类型、元素是 (key, value) 对的可迭代对象键值参数)来新建实例。因为这个方法在背后是用 self[key] = value 来添加新值的,所以它其实是在使用我们的 __setitem__ 方法。

  • Mapping.get
    StrKeyDict0中, 我 们 不 得 不 改 写 get 方 法, 好 让 它 的 表 现 跟__getitem__ 一致。而在StrKeyDict示例中就没这个必要了,因为它继承了 Mapping.get 方法,而 Python 的源码(https://hg.python.org/cpython/file/3.4/Lib/_collections_abc.py#l422)显示,这个方法的实现方式跟 StrKeyDict0.get 是一模一样的。

TransformDict

https://github.com/fluentpython/example-code/blob/master/03-dict-set/transformdict.py比起StrKeyDictTransformDict 的通用性更强,也更复杂,因为它把键存成字符串同时,还要按照它原来的样子存一份。

你可能感兴趣的:(python)