fluent python-第 3 章 字典和集合

字典这个数据结构活跃在所有 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 之外, 标准库还提供现成且好用的特殊映射类型, 比如defaultdictOrderedDictChainMapCounter。 这些映射类型都属于 collections 模块, 这个模块还提供了便于扩展的 UserDict类。

大多数映射类型都提供了两个很强大的方法: setdefault 和
update。 setdefault 方法可以用来更新字典里存放的可变值( 比如列
表) , 从而避免了重复的键搜索。 update 方法则让批量更新成为可
能, 它可以用来插入新值或者更新已有键值对, 它的参数可以是包含
(key, value) 这种键值对的可迭代对象, 或者关键字参数。 映射类型
的构造方法也会利用 update 方法来让用户可以使用别的映射对象、 可
迭代对象或者关键字参数来创建新对象.

在映射类型的 API 中, 有个很好用的方法是 __missing__, 当对象找不到某个键的时候, 可以通过这个方法自定义会发生什么。

collections.abc 模块提供了 Mapping 和 MutableMapping 这两个抽
象基类, 利用它们, 我们可以进行类型查询或者引用。 不太为人所知的MappingProxyType 可以用来创建不可变映射对象, 它被封装在types模块中。 另外还有 SetMutableSet 这两个抽象基类。

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 格式可以完美地用于数据交换。

PHPRuby 的散列语法借鉴了 Perl, 它们都用 => 作为键和值的连接。 JavaScript 则从 Python 那儿偷师, 使用了:。 而 JSON 又从JavaScript 发展而来, 它的语法正好是 Python 句法的子集。 因此,除了在 true、 false 和 null 这几个值的拼写上有出入之外,JSON 和Python 是完全兼容的。 于是, 现在大家用来交换数据的格式全是 Python 的 dict 和 list。

简单而正确。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容