映射类型的方法其实很丰富。表 3-1 为我们展示了 dictdefaultdictOrderedDict 的常见方法,后面两个数据类型是 dict 的变种,位于 collections 模块内。

表3-1:dictcollections.defaultdictcollections.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,则把它对应的值设置为 default,然后返回这个值;若无,则让 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 可能是比较微妙的一个。我们虽然并不会每次都用它,但是一旦它发挥作用,就可以节省不少次键查询,从而让程序更高效。如果你对它还不熟悉,下面我会通过一个实例来讲解它的用法。

当字典 d[k] 不能找到正确的键的时候,Python 会抛出异常,这个行为符合 Python 所信奉的“快速失败”哲学。也许每个 Python 程序员都知道可以用 d.get(k, default) 来代替 d[k],给找不到的键一个默认的返回值(这比处理 KeyError 要方便不少)。但是要更新某个键对应的值的时候,不管使用 __getitem__ 还是 get 都会不自然,而且效率低。就像示例 3-2 中的还没有经过优化的代码所显示的那样,dict.get 并不是处理找不到的键的最好方法。

示例 3-2 是由 Alex Martelli 举的一个例子 1 变化而来,例子生成的索引跟示例 3-3 显示的一样。

1示例代码出现在 Martelli 的演讲“Re-learning python”中(第 41 张幻灯片,http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf),他的代码被我放在了示例 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.uppper,而是把这个方法的引用传递给 sorted 函数,这样在排序的时候,单词会被规范成统一格式。2

2这是将方法用作一等函数的一个示例,第 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 只需要一次就可以完成整个操作。

那么,在单纯地查找取值(而不是通过查找来插入新值)的时候,该怎么处理找不到的键呢?