下面总结了标准库里 collections
模块中,除了 defaultdict
之外的不同映射类型。
collections.OrderedDict
这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的
。OrderedDict
的 popitem
方法默认删除并返回的是字典里的最后
一个元素,但是如果像 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
又实现了一遍。跟 OrderedDict
、ChainMap
和 Counter
这些开箱即用的类型不同,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
里剩下
的那些映射类型的方法都是从 UserDict
、MutableMapping
和 Mapping
这些超类继承而来的。特别是最后的 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
比起StrKeyDict
,TransformDict
的通用性更强,也更复杂,因为它把键存成字符串
的同时
,还要按照它原来的
样子存一份。