Python系列(六):如何处理字典中缺失的键

一.前言

字典是Python中的常用类型,在某些情况下,可能会删除或访问字典中不存在的键,此时会报错为KeyError,例如:

capitals = {
  "China": "BeiJing",
  "USA": "Washington"
}

print(capitals['Japan'])
# KeyError: 'Japan'

为此,Python提供了多套解决方案来避免上述问题,包括由字典内置的方法以及collection模块中的defaultdict

二.解决方案

2.1 字典内置方法

内置方法一:使用字典内置的get(k, d)方法,该方法有两个参数,k表示要访问的keyd表示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的一些基本类型传递给该参数,例如intlist等等,当传递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

以上便是本文的全部内容,要是觉得不错的话,可以点个赞或关注一下博主,你们的支持是博主创作的不竭动力,当然要是有问题的话也敬请批评指正!!!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

斯曦巍峨

码文不易,有条件的可以支持一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值