- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
3.3 常见的映射方法
映射类型的方法其实很丰富。表 3-1 为我们展示了 dict、defaultdict 和 OrderedDict 的常见方法,后面两个数据类型是 dict 的变种,位于 collections 模块内。
表3-1:dict、collections.defaultdict和collections.OrderedDict这三种映射类型的方法列表(依然省略了继承自object的常见方法);可选参数以[...]表示
dict | defaultdict | OrderedDict | ||
d.clear() | • | • | • | 移除所有元素 |
d.__contains__(k) | • | • | • | 检查 k 是否在 d 中 |
d.copy() | • | • | • | 浅复制 |
d.__copy__() | • | 用于支持 copy.copy | ||
d.default_factory | • | 在 __missing__ 函数中被调用的函数,用以给未找到的元素设置值* | ||
d.__delitem__(k) | • | • | • | del d[k],移除键为 k 的元素 |
d.fromkeys(it, [initial]) | • | • | • | 将迭代器 it 里的元素设置为映射里的键,如果有 initial 参数,就把它作为这些键对应的值(默认是 None) |
d.get(k, [default]) | • | • | • | 返回键 k 对应的值,如果字典里没有键 k,则返回 None 或者 default |
d.__getitem__(k) | • | • | • | 让字典 d 能用 d[k] 的形式返回键 k 对应的值 |
d.items() | • | • | • | 返回 d 里所有的键值对 |
d.__iter__() | • | • | • | 获取键的迭代器 |
d.keys() | • | • | • | 获取所有的键 |
d.__len__() | • | • | • | 可以用 len(d) 的形式得到字典里键值对的数量 |
d.__missing__(k) | • | 当 __getitem__ 找不到对应键的时候,这个方法会被调用 | ||
d.move_to_end(k, [last]) | • | 把键为 k 的元素移动到最靠前或者最靠后的位置(last 的默认值是 True) | ||
d.pop(k, [defaul] | • | • | • | 返回键 k 所对应的值,然后移除这个键值对。如果没有这个键,返回 None 或者 defaul |
d.popitem() | • | • | • | 随机返回一个键值对并从字典里移除它# |
d.__reversed__() | • | 返回倒序的键的迭代器 | ||
d.setdefault(k, [default]) | • | • | • | 若字典里有键k,则直接返回k所对应的值;若无,则让 d[k] = default,然后返回 default |
d.__setitem__(k, v) | • | • | • | 实现 d[k] = v 操作,把 k 对应的值设为v |
d.update(m, [**kargs]) | • | • | • | m 可以是映射或者键值对迭代器,用来更新 d 里对应的条目 |
d.values() | • | • | • | 返回字典里的所有值 |
* default_factory 并不是一个方法,而是一个可调用对象(callable),它的值在 defaultdict 初始化的时候由用户设定。
# OrderedDict.popitem() 会移除字典里最先插入的元素(先进先出);同时这个方法还有一个可选的 last 参数,若为真,则会移除最后插入的元素(后进先出)。
上面的表格中,update 方法处理参数 m 的方式,是典型的“鸭子类型”。函数首先检查 m 是否有 keys 方法,如果有,那么 update 函数就把它当作映射对象来处理。否则,函数会退一步,转而把 m 当作包含了键值对 (key, value) 元素的迭代器。Python 里大多数映射类型的构造方法都采用了类似的逻辑,因此你既可以用一个映射对象来新建一个映射对象,也可以用包含 (key, value) 元素的可迭代对象来初始化一个映射对象。
在映射对象的方法里,setdefault 可能是比较微妙的一个。我们虽然并不会每次都用它,但是一旦它发挥作用,就可以节省不少次键查询,从而让程序更高效。如果你对它还不熟悉,下面我会通过一个实例来讲解它的用法。
用setdefault处理找不到的键
当字典 d[k] 不能找到正确的键的时候,Python 会抛出异常,这个行为符合 Python 所信奉的“快速失败”哲学。也许每个 Python 程序员都知道可以用 d.get(k, default) 来代替 d[k],给找不到的键一个默认的返回值(这比处理 KeyError 要方便不少)。但是要更新某个键对应的值的时候,不管使用 __getitem__ 还是 get 都会不自然,而且效率低。就像示例 3-2 中的还没有经过优化的代码所显示的那样,dict.get 并不是处理找不到的键的最好方法。
示例 3-2 是由 Alex Martelli 举的一个例子 2 变化而来,例子生成的索引跟示例 3-3 显示的一样。
2示例代码出现在 Martelli 的演讲“Re-learning python”中(第 41 张幻灯片),他的代码被我放在了示例 3-4 中,代码很好地展示了 dict.setdefault 的用法。
示例 3-2 index0.py 这段程序从索引中获取单词出现的频率信息,并把它们写进对应的列表里(更好的解决方案在示例 3-4 中)
"""创建一个从单词到其出现情况的映射""" import sys import re WORD_RE = re.compile(r'\w+') index = {} with open(sys.argv[1], encoding='utf-8') as fp: for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() column_no = match.start()+1 location = (line_no, column_no) # 这其实是一种很不好的实现,这样写只是为了证明论点 occurrences = index.get(word, []) ➊ occurrences.append(location) ➋ index[word] = occurrences ➌ # 以字母顺序打印出结果 for word in sorted(index, key=str.upper): ➍ print(word, index[word])
❶ 提取 word 出现的情况,如果还没有它的记录,返回 []。
❷ 把单词新出现的位置添加到列表的后面。
❸ 把新的列表放回字典中,这又牵扯到一次查询操作。
❹ sorted 函数的 key= 参数没有调用 str.upper,而是把这个方法的引用传递给 sorted 函数,这样在排序的时候,单词会被规范成统一格式。3
3这是将方法用作一等函数的一个示例,第 5 章会谈到这一点。
示例 3-3 这里是示例3-2 的不完全输出,每一行的列表都代表一个单词的出现情况,列表中的元素是一对值,第一个值表示出现的行,第二个表示出现的列
$ python3 index0.py ../../data/zen.txt a [(19, 48), (20, 53)] Although [(11, 1), (16, 1), (18, 1)] ambiguity [(14, 16)] and [(15, 23)] are [(21, 12)] aren [(10, 15)] at [(16, 38)] bad [(19, 50)] be [(15, 14), (16, 27), (20, 50)] beats [(11, 23)] Beautiful [(3, 1)] better [(3, 14), (4, 13), (5, 11), (6, 12), (7, 9), (8, 11), (17, 8), (18, 25)] ...
示例 3-2 里处理单词出现情况的三行,通过 dict.setdefault 可以只用一行解决。示例 3-4 更接近 Alex Martelli 自己举的例子。
示例 3-4 index.py 用一行就解决了获取和更新单词的出现情况列表,当然跟示例 3-2 不一样的是,这里用到了 dict.setdefault
"""创建从一个单词到其出现情况的映射""" import sys import re WORD_RE = re.compile(r'\w+') index = {} with open(sys.argv[1], encoding='utf-8') as fp: for line_no, line in enumerate(fp, 1): for match in WORD_RE.finditer(line): word = match.group() column_no = match.start()+1 location = (line_no, column_no) index.setdefault(word, []).append(location) ➊ # 以字母顺序打印出结果 for word in sorted(index, key=str.upper): print(word, index[word])
➊ 获取单词的出现情况列表,如果单词不存在,把单词和一个空列表放进映射,然后返回这个空列表,这样就能在不进行第二次查找的情况下更新列表了。
也就是说,这样写:
my_dict.setdefault(key, []).append(new_value)
跟这样写:
if key not in my_dict: my_dict[key] = [] my_dict[key].append(new_value)
二者的效果是一样的,只不过后者至少要进行两次键查询——如果键不存在的话,就是三次,用 setdefault 只需要一次就可以完成整个操作。
那么,在单纯地查找取值(而不是通过查找来插入新值)的时候,该怎么处理找不到的键呢?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论