Python系列(六):如何处理字典中缺失的键
一.前言字典是Python中的常用类型,在某些情况下,可能会删除或访问字典中不存在的键,此时会报错为KeyError,例如:capitals = {"China": "BeiJing","USA": "Washington"}print(capitals['Japan'])# KeyError: 'Japan'为此,Python提供了多套解决方案来避免上述问题,包括由字典内置的方法以及collec
一.前言
字典是Python中的常用类型,在某些情况下,可能会删除或访问字典中不存在的键,此时会报错为KeyError,例如:
capitals = {
"China": "BeiJing",
"USA": "Washington"
}
print(capitals['Japan'])
# KeyError: 'Japan'
为此,Python提供了多套解决方案来避免上述问题,包括由字典内置的方法以及collection模块中的defaultdict。
二.解决方案
2.1 字典内置方法
内置方法一:使用字典内置的get(k, d)方法,该方法有两个参数,k表示要访问的key,d表示key不存在时返回的默认值。注意,d是可选参数,其默认值为None,即当不显式指定d时,当key不存在会默认返回None。
使用get()方法来进行词频统计的例子如下:
strings = "Apple Mango Orange Mango Guava Guava Mango"
words = strings.split()
word_freqs = {}
for word in words:
count = word_freqs.get(word, 0)
word_freqs[word] = count + 1
print(word_freqs)
# {'Apple': 1, 'Mango': 3, 'Orange': 1, 'Guava': 2}
内置方法二:使用字典内置的setdefault(key, value=None)方法,参数说明:
key:要访问的键;value:表示key不存在时返回的默认值,注意当键不存在时,会先在字典内创建key: value这个键值对,然后再范围键的值。
使用setdefault()来对奇偶数分组的例子如下所示:
nums = [1, 3, 4, 2, 16, 10, 18, 33, 28, 76]
category = {}
"""
使用get()方法实现分组的代码版本
for num in nums:
if num % 2 == 0:
if not category.get("even"):
category["even"] = []
category["even"].append(num)
else:
if not category.get("odd"):
category["odd"] = []
category["odd"].append(num)
"""
for num in nums:
if num % 2 == 0:
cur_list = category.setdefault("even", [])
cur_list.append(num)
else:
cur_list = category.setdefault("odd", [])
cur_list.append(num)
print(category)
# {'odd': [1, 3, 33], 'even': [4, 2, 16, 10, 18, 28, 76]}
上述方法倘若使用get方法实现,则要更繁琐,代码也将更长。但是使用setdefault()方法也有一个不足之处,就是每次调用时不管键存不存在都会创建一个新的默认值,这样的做法开销还是比较大的,尤其当默认值的构造成本比较高的时候。
讨论:由于内置方法二会对不存在的键会在字典内创建键值对,因此setdefault()方法的使用场景和get()方法其实是不同的,这就需要具体场景具体分析了。
2.2 通过collections模块的defaultdict
除了使用字典内置的方法来处理键不存在的问题,还可以使用collections模板的defaultdict(default_factory),其中参数default_factory是返回字典默认值的函数,通过这个参数用户可以自定义返回默认值的函数。另外,可以将Python的一些基本类型传递给该参数,例如int、list等等,当传递list时,则默认返回一个空列表。
用defaultdict实现上述的奇偶数分组的示例如下:
from collections import defaultdict
nums = [1, 3, 4, 2, 16, 10, 18, 33, 28, 76]
category = defaultdict(list)
for num in nums:
if num % 2 == 0:
category["even"].append(num)
else:
category["odd"].append(num)
print(category)
# defaultdict(<class 'list'>, {'odd': [1, 3, 33], 'even': [4, 2, 16, 10, 18, 28, 76]})
2.3 继承dict类型并实现__missing__方法
在有些情况下2.1和2.2小节中的方法都不是最佳方案。举个例子,假如需要一个字典来将图片路径映射到文件句柄(file handle)。采用get()方法来实现该需求对应如下:
pictures = {}
path = 'temp1.jpg'
if pictures.get(path) is None:
handle = open(path, 'ab+')
pictures[path] = handle
print(pictures)
# {'temp1.jpg': <_io.BufferedRandom name='temp1.jpg'>}
倘若使用setdefault()方法,则其实现如下:
handle = pictures.setdefault(path, open(path, 'ab+'))
print(pictures)
# {'temp1.jpg': <_io.BufferedRandom name='temp1.jpg'>}
可以看到该方式要比最使用get()方法要代码量更少,但是该方法也存在一个缺陷,就是不管path是否存在于字典中,都会创建一个对应于该path的文件句柄,为此可能会同时为同一个文件路径创建多个文件句柄,这显然不是我们想要看到的。而defaultdict方法则更不行了,因为默认值不能够传入参数,而这里的默认值需要传入path以创建一个文件句柄。
对于这种场景,可以继承dict类,然后重写__missing__方法来添加用于处理缺失key的解决方案,对应于上述例子的示例如下:
class Picture(dict):
def __missing__(self, key):
value = open(key, 'ab+')
self[key] = value
return value
path = 'temp1.jpg'
pictures = Picture()
handle = pictures[path]
print(pictures)
# {'temp1.jpg': <_io.BufferedRandom name='temp1.jpg'>}
可以看到,采用该方案比较完美的解决了该应用场景。
三.结语
参数资料:
- Effective Python 90 Specific Ways to Write Better Python
以上便是本文的全部内容,要是觉得不错的话,可以点个赞或关注一下博主,你们的支持是博主创作的不竭动力,当然要是有问题的话也敬请批评指正!!!
更多推荐



所有评论(0)