字典这个数据结构活跃在所有 Python 程序的背后, 即便你的源码里
并没有直接用到它。
3.2 字典推导
>>> DIAL_CODES = [ ➊ #就是把 list 变成 dict
... (86, 'China'),
... (91, 'India'),
... (1, 'United States'),
... (62, 'Indonesia'),
... (55, 'Brazil'),
... (92, 'Pakistan'),
... (880, 'Bangladesh'),
... (234, 'Nigeria'),
... (7, 'Russia'),
... (81, 'Japan'),
... ]
>>> country_code = {country: code for code, country in DIAL_CODES} ➋ # 这里把配好对的数据左右换了下, 国家名是键, 区域码是值
>>> country_code
{'China': 86, 'India': 91, 'Bangladesh': 880, 'United States': 1,
'Pakistan': 92, 'Japan': 81, 'Russia': 7, 'Brazil': 55, 'Nigeria':
234, 'Indonesia': 62}
>>> {code: country.upper() for country, code in country_code.items() ➌
... if code < 66}
{1: 'UNITED STATES', 55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA'}
3.3 常见的映射方法
表 3-1 为我们展示了dict、 defaultdict 和 OrderedDict 的常见方法, 后面两个数据类型是 dict 的变种, 位于 collections 模块内。
3.2 特殊方法 __missing__
像 k in my_dict.keys()
这种操作在 Python 3 中是很快
的, 而且即便映射类型对象很庞大也没关系。 这是因为
dict.keys() 的返回值是一个“视图”。 视图就像一个集合, 而且跟
字典类似的是, 在视图里查找一个元素的速度很快。
3.5 字典的变种
collections.OrderedDict
这个类型在添加键的时候会保持顺序, 因此键的迭代次序总是一致
的。 OrderedDict 的 popitem 方法默认删除并返回的是字典里的最后
一个元素, 但是如果像 my_odict.popitem(last=False)
这样调用
它, 那么它删除并返回第一个被添加进去的元素
collections.ChainMap
该类型可以容纳数个不同的映射对象, 然后在进行键查找操作的时
候, 这些对象会被当作一个整体被逐个查找, 直到键被找到为止。 这个
功能在给有嵌套作用域的语言做解释器的时候很有用, 可以用一个映射
对象来代表一个作用域的上下文。
collections.Counter
这个映射类型会给键准备一个整数计数器。 每次更新一个键的时候都会增加这个计数器。 所以这个类型可以用来给可散列表对象计数, 或者是当成多重集来用——多重集合就是集合里的元素可以出现不止一次。 Counter 实现了 + 和 - 运算符用来合并记录, 还有像most_common([n])
这类很有用的方法。 most_common([n])
会按照次序返回映射里最常见的 n 个键和它们的计数.
下面的小例子利用 Counter 来计算单词中各个字母出现的次数:
>>> ct = collections.Counter('abracadabra')
>>> ct
Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>>> ct.update('aaaaazzz')
>>> ct
Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>>> ct.most_common(2)
[('a', 10), ('z', 3)]
3.8 集合论
“集”这个概念在 Python 中算是比较年轻的, 同时它的使用率也比较
低。
3.9 dict和set的背后
3.9.1 一个关于效率的实验
通过对比,我们发现,字典和集合的查询速度非常快,几乎可以忽略,而列表恰恰相反,列表因为有序排列,每次查询都要全部遍历,效率特低(关于列表的效率问题可以用冒泡查询法解决,这里暂略)
3.9.2 字典中的散列表
散列表其实是一个稀疏数组( 总是有空白元素的数组称为稀疏数组) 。在一般的数据结构教材中, 散列表里的单元通常叫作表元(bucket) 。在 dict 的散列表当中, 每个键值对都占用一个表元, 每个表元都有两个部分, 一个是对键的引用, 另一个是对值的引用。 因为所有表元的大小一致, 所以可以通过偏移量来读取某个表元。
因为 Python 会设法保证大概还有三分之一的表元是空的, 所以在快要达到这个阈值的时候, 原有的散列表会被复制到一个更大的空间面。所以对比list,廖大说过,字典查询速度极快,但是占内存,list查询速度很慢,但是不占内存。
无论何时往字典里添加新的键, Python 解释器都可能做出为字典扩容的决定。 扩容导致的结果就是要新建一个更大的散列表, 并把字典里已有的元素添加到新表里。 这个过程中可能会发生新的散列冲突, 导致新散列表中键的次序变化。 要注意的是, 上面提到的这些变化是否会发生以及如何发生, 都依赖于字典背后的具体实现, 因此你不能很自信地说自己知道背后发生了什么。 如果你在迭代一个字典的所有键的过程中同时对字典进行修改, 那么这个循环很有可能会跳过一些键——甚至是跳过那些字典中已经有的键。
3.10 本章小结
字典算得上是 Python 的基石。 除了基本的 dict 之外, 标准库还提供现成且好用的特殊映射类型, 比如defaultdict
、 OrderedDict
、 ChainMap
和 Counter
。 这些映射类型都属于 collections
模块, 这个模块还提供了便于扩展的 UserDict
类。
大多数映射类型都提供了两个很强大的方法: setdefault 和
update。 setdefault 方法可以用来更新字典里存放的可变值( 比如列
表) , 从而避免了重复的键搜索。 update 方法则让批量更新成为可
能, 它可以用来插入新值或者更新已有键值对, 它的参数可以是包含
(key, value) 这种键值对的可迭代对象, 或者关键字参数。 映射类型
的构造方法也会利用 update 方法来让用户可以使用别的映射对象、 可
迭代对象或者关键字参数来创建新对象.
在映射类型的 API 中, 有个很好用的方法是 __missing__
, 当对象找不到某个键的时候, 可以通过这个方法自定义会发生什么。
collections.abc
模块提供了 Mapping 和 MutableMapping 这两个抽
象基类, 利用它们, 我们可以进行类型查询或者引用。 不太为人所知的MappingProxyType
可以用来创建不可变映射对象, 它被封装在types
模块中。 另外还有 Set
和 MutableSet
这两个抽象基类。
dict 和 set 背后的散列表效率很高, 对它的了解越深入, 就越能理解
为什么被保存的元素会呈现出不同的顺序, 以及已有的元素顺序会发生变化的原因。 同时, 速度是以牺牲空间为代价而换来的。
杂谈
我的朋友 Geraldo Cohen 曾经说过, Python 的特点是“简单而正确”。
dict 类型正是这一特点的完美体现——对它的优化只为一个目标: 更好地实现对随机键的读取。 而优化的结果非常好, 由于速度快而且够健壮, 它大量地应用于 Python 的解释器当中。 如果对排序有要求, 那么还可以选择OrderedDict。 然而对于映射类型来说,保持元素的顺序并不是一个常用需求, 因此会把它排除在核心功能之外, 而以标准库的形式提供其他衍生的类型。
本书前两章的目的是展示 Python 中的集合类型为特定的使用场景做
了怎样的优化。 我特意强调了在 list 和 dict 的常规用法之外还
有那些特殊的使用情景。
在遇到 Python 之前, 我主要使用 Perl、 PHP 和 JavaScript 做网站开
发。 我很喜欢这些语言中跟映射类型相关的字面量句法特性。 某些时候我不得不使用 Java 和 C, 然后我就会疯狂地想念这些特性。 好用的映射类型的字面量句法可以帮助开发者轻松实现配置和表格相关的开发, 也能让我们很方便地为原型开发或者测试准备好数据容器。 Java 由于没有这个特性, 不得不用复杂且冗长的 XML来替代。
JSON 被当作“瘦身版 XML”( http://www.json.org/fatfree.html) 。 在很多情景下, JSON 都成功取代了 XML。 由于拥有紧凑的列表和字典表达, JSON 格式可以完美地用于数据交换。
PHP
和 Ruby
的散列语法借鉴了 Perl
, 它们都用 =>
作为键和值的连接。 JavaScript 则从 Python 那儿偷师, 使用了:
。 而 JSON 又从JavaScript 发展而来, 它的语法正好是 Python 句法的子集。 因此,除了在 true、 false 和 null 这几个值的拼写上有出入之外,JSON 和Python 是完全兼容的。 于是, 现在大家用来交换数据的格式全是 Python 的 dict 和 list。
简单而正确。