一.前言
字典是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
以上便是本文的全部内容,要是觉得不错的话,可以点个赞或关注一下博主,你们的支持是博主创作的不竭动力,当然要是有问题的话也敬请批评指正!!!