字典设置默认值的几种方法
前言
在使用 Python 字典的时候,通过 d[k] 来获取键 k 的值,如果 k 不存在,Python 会抛出KeyError
异常,这符合 Python 信奉的“快速失败”哲学。我们可以通过try/except
语句块来捕获异常,但有时候我们更愿意设置一个默认值,当要查询的键不存在的时候,直接返回默认值。
有几种方式可以满足这个需求,本文将介绍这些方法,并比较它们的异同。
本文中的命令行示例,都是基于以下环境:
- 操作系统: CentOS 7
- Python:3.7.9
get 方法
获取默认值最常用的方法就是字典自带的 get()
方法:
|
|
get()
的第二个参数default
表示当 key 不存在时,返回的默认值。它默认为 None。
示例1
假设有一个记载最近三天各个城市最高气温的文件 weather.dat,文件每行是某个城市在某天的最高气温,没有排序,文件内容如下:
|
|
现在我们做数据处理,读入这些数据后,以城市作为关键字,把它最近三天的最高气温存在列表中作为值,为了之后做相关统计。
可以用get()
方法的default
参数来初始化空列表,程序实现如下:
|
|
执行结果:
|
|
上述代码中,我们先用正则表达式模块re
解析每行数据,得到城市和最高气温,然后把它们添加到字典 weather_statistic 中。添加过程中,先用get()
方法从字典中查找关键字 city,如果是第一次添加,即 city 不存在,则返回一个空列表,如果 city 存在,则返回它对应的列表,然后把 temperature 加入到返回的列表中,最后把 city 和更新的列表加入到字典中。之所以最后需要手动将列表加入到字典中,是因为如果之前get()
方法返回的是空列表,这个空列表还没有在字典中。
这个例子需要注意的是,对字典进行了两次查询,第一次是调用get()
方法的时候,第二次是最后把 city 和对应列表加入到字典中时。关于字典的工作方式,可以参考本人另一篇文章:深入浅出地理解 Python 中可散列类型的原理和实现(二)
setdefault 方法
另一个设置默认值的方法是字典的setdefault()
方法,严格来说,这个是设置默认值方法,而不是获取默认值方法。但因为它为字典某个键设置默认值之后,会返回该值,所以用该方法既可以设置默认值,也能返回设定的默认值。
setdefault()
方法的工作方式是:
|
|
如果 key 在字典中,那么直接返回它的值,就像 d[key] 一样;如果 key 不存在,把 key 和 default 值添加到字典中,并返回 default 值。
示例2
把 示例1 代码的get()
方法用setdefault()
方法替代:
|
|
执行结果:
|
|
这里,只用一行代码就实现了原来三行代码的功能,也就是说,下面两种写法的功能是一样的:
|
|
和
|
|
两者的作用相同,但用setdefault()
字典只需要进行一次查询,如果 key 不存在,直接插入 key 和空列表,而无需在最后再插入。而第二种方法,如同 示例1 解释,需要查询两次。
使用 defaultdict 类
Python 的 collections 模块提供了一个 defaultdict 类,它的作用和 dict 类似, 但如果查询的 key 不存在,它可以返回设定的默认值。
在创建一个 defaultdict 对象的时候,需要给构造方法提供一个可调用对象,这个可调用对象会在__getitem__
方法找不到 key 时被调用,得到一个默认对象,让__getitem__
返回它。
比如,我们新建一个字典 d = defaultdict(list),如果 key 在 d 中不存在的话,表达式 d[key] 会按照以下步骤操作:
- 调用 list() 新建一个列表对象;
- 把这个列表对象作为值,key 作为键,放入 d 中;
- 返回这个列表对象的引用。
而这个用来生成默认值的可调用对象存放在 d 的名为default_factory
属性里。
示例3
把先前统计城市最高气温的例子,用defaultdict
来实现:
|
|
执行结果:
|
|
可以看到,用defaultdict
类来为字典设定默认值的方式,和 示例2 非常相似,也只需要字典进行一次查询即可。区别有两点:
- 用
defaultdict
类需要在创建字典时就指定默认值,而setdefault()
方法则在每次调用时指定默认值,从这个角度来看,setdefault()
更灵活些。 defaultdict
类设定的默认值是一个可调用对象,任何可调用对象都可以起作用,比如类、函数、实现__call__()
方法的类的对象、lambda 表达式等,当 key 不存在时,就用这些可调用对象创建默认对象。而setdefault()
方法直接指定默认值。
__missing__ 方法
我们知道,当执行 d[key] 查询字典中的 key 时,Python 会调用__getitem__()
方法,如果 key 不存在,__getitem__()
方法会抛出KeyError
异常。其实,在抛出异常之前,__getitem__()
方法会先检查该类有没有定义__missing__()
方法,如果有,就调用它,而不抛出异常。默认情况下,dict 类没有定义该方法,但是它知道有这么一个东西存在。也就是说,如果有一个类继承了 dict,然后该类实现了__missing__()
方法,那么在__getitem__()
方法找不到 key 的时候,就会自动调用它,而不是抛出 KeyError 异常。
__missing__()
方法只会被__getitem__()
方法调用,而字典的get()
方法和运算符 in
会调用的__contains__()
方法,都是用 C 语言实现高效查询,并不会调用__getitem__()
方法。所以__missing__()
方法的实现不会影响字典的get()
方法和运算符 in
的行为。
上一节介绍的defaultdict
类,其实就是实现了__missing__()
方法,创建defaultdict
对象的时候,设定的可调用对象被赋给defaultdict
对象的default_factory
属性,当 d[key] 查询的 key 不存在时,调用__missing__()
方法,该方法内会再调用default_factory()
创建默认的对象作为 key 对应的默认值,并返回。这意味着default_factory
也只对__getitem__()
方法有作用,而不会影响字典的get()
方法和运算符 in
的行为。
示例4
对于统计城市最高气温的例子,我们通过定义一个继承自 dict 的子类,并实现它的__missing__()
方法,来达到和前面几个例子一样的效果:
|
|
执行结果:
|
|
这个例子中,我们自定义了一个类 my_dict 继承自 dict,并实现了__missing__()
方法。当要查询的 key 不在 my_dict 对象中,就会触发调用__missing__()
方法。在该方法中,我们先用构造函数内设置的 default_factory 构造一个默认对象,然后执行 “self[key] = self.default_factory()” 把 key 和 该对象插入到 my_dict 对象中,该语句会调用父类 dict 的__setitem__()
方法,最后返回该对象。
可以看到,该例子中使用 my_dict 对象的方法,和 示例3 中使用defaultdict
对象的方法,几乎一样。其实 my_dict 类的实现,就是defaultdict
类实现的变体。有兴趣的可以参考 Python 中defaultdict
的实现。
需要注意,想要自定义一个和字典有类似行为的类,继承自 dict 并不是最好的办法。推荐的方法是继承自UserDict
类。具体细节,可参考本人另一篇文章 巧用 Python 的 UserDict 和 UserList 。
参考
https://docs.python.org/3/library/collections.html
https://blog.finxter.com/python-__missing__-magic-method/
「 您的赞赏是激励我创作和分享的最大动力! 」
- 原文链接:https://zhuyinjun.me/2019/how_to_set_default_value_for_dict/
- 版权声明:本创作采用 CC BY-NC 4.0 国际许可协议,非商业性使用可以转载,但请注明出处(作者、链接),商业性使用请联系作者获得授权。