有时候为了方便起见,就算某个键在映射里不存在,我们也希望在通过这个键读取值的时候能得到一个默认值。有两个途径能帮我们达到这个目的,一个是通过 defaultdict
这个类型而不是普通的 dict
,另一个是给自己定义一个 dict
的子类,然后在子类中实现 __missing__
方法。下面将介绍这两种方法。
3.4.1 defaultdict
:处理找不到的键的一个选择
在创建一个 defaultdict
的时候,需要给构造方法提供一个可调用对象,这个可调用对象会在 __getitem__
碰到找不到的键的时候被调用,让 __getitem__
调用可调用对象生成一个默认值,并将默认值和键配对放入字典中。
# 新建一个字典,并传入可调用对象 list 和 生成字典的键值对
dd = defaultdict(list, {'a': 1, 'b': 2})
# 键 'c' 在 dd 中还不存在,dd['c'] 会按照以下的步骤来行事:
# 1. 调用 list() 来建立一个新列表;
# 2. 把这个新列表作为值,'c' 作为它的键,放到 dd 中;
# 3. 返回这个列表的引用
rint(dd['c']) # []
print(dd) # defaultdict(<class 'list'>, {'a': 1, 'b': 2, 'c': []})
3.4.2 特殊方法 __missing__
所有的映射类型在处理找不到的键的时候,都会牵扯到 __missing__
方法。虽然基类 dict
并没有定义这个方法,但是 dict
是知道有这么个东西存在的。也就是说,如果有一个类继承了 dict
,然后这个继承类提供了 __missing__
方法,那么在 __getitem__
碰到找不到的键的时候,Python 就会自动调用它,而不是抛出一个 KeyError
异常。
class StrKeyDict(dict):
"""自定义字典类,一般自定义字典类应该是继承 collections.UserDict,此处仅做展示 __miss__ 的用法"""
def get(self, key, default=None):
try:
# 1. get方法把查询工作以 dict[key] 的形式委托给 __getitem__,假如查询失败,__getitem__会先调用 __miss__
return self[key]
# 4. 当 __miss__ 也查询失败抛出 KeyError 异常时捕获异常,然后返回默认值
except KeyError:
return default
# 2. 当查询失败时,__getitem__会先调用 __miss__
def __missing__(self, key):
# 3. 检查 key 是不是 str 类型,不过不是,就转为字符串再查询一次,如果本身就是字符串,会抛出 KeyError 异常
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
# 配套修改
def __contains__(self, key):
return key in self.keys() or str(key) in self.keys()
为了保持一致性,__contains__
方法在这里也是必需的。这是因为 k in d
这个操作会调用它,但是我们从 dict
继承到的 __contains__
方法不会在找不到键的时候调用 __missing__
方法。__contains__
里还有个细节,就是我们这里没有用更具 Python 风格的方式——k in my_dict
——来检查键是否存在,因为那也会导致 __contains__
被递归调用。为了避免这一情况,这里采取了更显式的方法,直接在这个 self.keys()
里查询。