从容应对 KeyError:Python 中的 defaultdict 模块

文章目录

参考

项目描述
Python 标准库DougHellmann / 刘炽 等
搜索引擎Bing
Python 官方文档collections — 容器数据类型

描述

项目描述
Python 解释器3.10.6

defaultdict_18">defaultdict

defaultdictPython 标准库 collections 模块中的一种字典类型,它提供了一种更加便捷的方式来处理字典中的缺失键值对。当我们使用普通的字典对象访问一个不存在的键时,Python 将抛出一个 KeyError 异常。而使用 defaultdict 模块,你将能够通过提供一个可调用对象以在访问一个不存在的键时向用户提供一个默认值并将相关的键值对添加到 defaultdict 字典中。对此,请参考如下示例:

python">from collections import defaultdict


# 尝试访问一个不存在的键时,defaultdict
# 将调用 int() 来返回一个默认值。
dd = defaultdict(int)
print(dd)

# 尝试访问一个不存在的键
print(dd['a'])

# 对一个不存在的键进行自增操作
dd['b'] += 1
print(dd)

执行效果

defaultdict(<class 'int'>, {})
0
defaultdict(<class 'int'>, {'a': 0, 'b': 1})

参数

python">defaultdict(default_factory=None, /, [...])

其中:

项目描述
default_factory在尝试访问一个不存在的键时,将执行 default_factory 对应的可调用对象以返回一个默认值。
[…]除去第一个位置参数外,其他参数都将传递给 dict 构造函数用于为 defaultdict 字典添加键值对。

注:

Python 3 中,/ 符号用于分隔位置参数和关键字参数,它出现在参数列表中,表示该符号之前的参数均为位置参数。对此,请参考如下示例:

python">from collections import defaultdict


dd = defaultdict(default_factory={'a': 1, 'b': 3})
print(dd)
print(dd['a'])

执行效果

default_factory 仅能通过位置参数的形式进行传递,由于我们使用了关键字参数。因此 default_factory 将使用其默认值 None 。而 default_factory={‘a’: 1, ‘b’: 3} 也就成了第二个参数实际接收到的数据。

defaultdict(None, {'default_factory': {'a': 1, 'b': 3}})

产生错误信息

Traceback (most recent call last):
  File "C:\main.py", line 6, in <module>
    print(dd['a'])
KeyError: 'a'

default_factory

default_factory 可以是 Python 内置的可调用对象外,也可以是我们自定义的可调用对象。相比 Python 内置的可调用对象,使用自定义可调用对象可以对返回值进行更细腻、更具有针对性的控制。对此,请参考如下示例:

python">from collections import defaultdict


def default_generator():
    return 'Hello World'

dd = defaultdict(default_generator)
print(dd['a'])

执行效果

Hello World

原理

魔术方法 __missing__

__missing__() 是 Python 中的一个特殊方法,用于处理通过键访问字典中的值时键不存在时的情况。
当我们使用字典的索引来访问一个不存在的键时,Python 将会调用特殊方法 __missing__() 来尝试返回一个合适的值。若未实现 __missing__() 方法,Python 将会抛出 KeyError 异常。对此,请参考如下示例:

python"># 创建一个字典对象,该对象继承自 Python 内置的 dict 对象
class MyDict(dict):
    def __missing__(self, key):
        return 0

# 实例化 MyDict() 对象
myDict = MyDict()
# 尝试访问 myDict 对象中不存在的键 a
print(myDict['a'])

执行效果

0

模仿游戏

defaultdict 正是因为实现了魔术方法 _missing_ ,所以才能够在用户尝试访问一个不存在的键时进行适当的处理而不至于引发 KeyError 异常。
我们可以通过实现 _missing_ 来模仿 defaultdict 。对此,请参考如下示例:

python"># MyDefaultdict 类继承 dict 以模仿原生字典
class MyDefaultdict(dict):
    def __init__(self, default_factory=None):
        # 执行 default_factory 可执行对象以获得默认值
        self.result = default_factory()

    def __missing__(self, key):
        # 键 = 默认值
        self[key] = self.result
        return self.result


# 在尝试访问一个不存在的键时,调用 str() 以获得默认值
myDefaultdict = MyDefaultdict(str)
print(myDefaultdict)

print(myDefaultdict['a'])
myDefaultdict['b'] += 'Hello World'
print(myDefaultdict)

执行效果

{}

{'a': '', 'b': 'Hello World'}

单次调用

再深入了解 default_factory 参数前,请先观察如下示例:

python">from collections import defaultdict


# 创建一个生成器(通过生成器推导式)
arr = (i for i in range(1, 100))
print(arr)

# 使用生成器对象的 __next__() 方法来生成默认值
dd = defaultdict(arr.__next__)
for c in 'Hello World':
    print(dd['c'])

执行效果

<generator object <genexpr> at 0x000001D4A4DBE3B0>
1
1
1
1
1
1
1
1
1
1
1

分析

在观察上述示例后,想必你也产生了疑惑。为什么访问不存在的键时,获取到的值均为第一次调用 default_factory 参数所对应的可调用对象的值。这是因为在创建 defaultdict 对象时,defaultdict 就已经调用了 default_factory 对象获取默认值。在该 defaultdict 对象的生命中,都将使用该值作为默认值,而不会在每次访问不存在的键时都去调用 default_factory 对象。

所以在上一个示例中,对 defaultdict 进行模仿时,我们使用的是:

python"># MyDefaultdict 类继承 dict 以模仿原生字典
class MyDefaultdict(dict):
    def __init__(self, default_factory=None):
        # 执行 default_factory 可执行对象以获得默认值
        self.result = default_factory()

    def __missing__(self, key):
        # 键 = 默认值
        self[key] = self.result
        return self.result

而不是(第二种实现):

python">class MyDefaultdict(dict):
    def __init__(self, default_factory=None):
        # 将 default_factory 的地址传递给 self.default_factory
        self.default_factory = default_factory

    def __missing__(self, key):
        # 在每一次访问不存在的键时都将调用 default_factory()
        # 来获取返回值
        result = self.default_factory()
        self[key] = result
        return result

如果使用 第二种实现,你将看到另一种景观。对此,请参考如下示例:

python">class MyDefaultdict(dict):
    def __init__(self, default_factory=None):
        # 将 default_factory 的地址传递给 self.default_factory
        self.default_factory = default_factory

    def __missing__(self, key):
        # 在每一次访问不存在的键时都将调用 default_factory()
        # 来获取返回值
        result = self.default_factory()
        self[key] = result
        return result


# 创建一个生成器(通过生成器推导式)
arr = (i for i in range(1, 100))
print(arr)

# 使用生成器对象的 __next__() 方法来生成默认值
myDefaultdict = MyDefaultdict(arr.__next__)
for c in 'Hello World':
    print(myDefaultdict[c])

执行效果

python"><generator object <genexpr> at 0x000001C0EDE99A10>
1
2
3
3
4
5
6
4
7
3
8

在正常情况下,建议使用 defaultdict 而不是此例中的第二种实现。defaultdict 提供了一个确定的默认值,有利于代码的维护与阅读。而第二种实现可能会提供不同的默认值,这可能会引起混乱。

setdefault__defaultdict__296">setdefault 还是 defaultdict ?

setdefault_298">setdefault

Python 的内置字典中提供了 setdefault 方法用于实现与 defaultdict 类似的功能。
在 Python 内置的字典对象中,setdefault 方法是一个常用的方法之一,可以用于获取字典中指定键对应的值。如果该键不存在,则将该键和指定的默认值添加到字典中并将默认值进行返回。

setdefault 方法的语法如下:

python">dict.setdefault(key, default=None)

举个栗子

python"># 创建一个空字典
d = dict()

# 尝试访问键 a
result = d.setdefault('a', [])
# 由于键 a 不存在,因此返回默认值
# 并将该键与默认值添加到字典中。
print(result)
print(d)

result.append('Hello World')
print(d)

执行效果

[]
{'a': []}
{'a': ['Hello World']}

对比

setdefaultdefaultdict 都可以用来设置字典中键的默认值,但它们在实现上有所不同。

  1. defaultdict 接受一个可调用对象(default_factory 参数的值 必须 为一个可调用对象,否则将引发异常),在需要默认值时将执行该可调用对象以获取默认值。而 setdefault 方法同样可以接受一个可调用对象,但在需要默认值时会直接将该可调用对象作为默认值进行返回。对此,请参考如下示例:
python"># 创建一个空字典
d = dict()

# 尝试获取键 a 的值
d.setdefault('a', int)
print(d)

执行效果

python">{'a': <class 'int'>}
  1. 使用 defaultdict 可以创建一个具有默认值的字典,而 dict 在可能需要使用到默认值的地方都需要使用到 setdefault 来设置默认值。这样不利于代码的维护与阅读。

结论

defaultdictsetdefault 两者并无优劣之分,在不同的场景中使用合适的工具才是重中之重。


http://www.niftyadmin.cn/n/225661.html

相关文章

SpringMVC 04 -静态资源放行与JSON交互

SpringMVC静态资源放行与JSON交互1 静态资源放行1.1 静态资源问题1.2 解决方案11.3 解决方案21.4 解决方案32 Json交互2.1 导入依赖2.2 使用**ResponseBody**2.3 使用**RestController**2.4 使用**RequestBody**2.4.1 前端请求2.4.2 定义Controller1 静态资源放行 1.1 静态资源…

C++-c语言词法分析器

一、运行截图 对于 Test.c 的词法分析结果 对于词法分析器本身的源代码的分析结果 二、主要功能 经过不断的修正和测试代码&#xff0c;分析测试结果&#xff0c;该词法分析器主要实现了以下功能&#xff1a; 1. 识别关键字 实验要求&#xff1a;if else while do for main…

使用java实现斗地主小游戏

Landlords 项目地址&#xff1a;https://github.com/chunlaiqingke/Landlords 环境要求&#xff1a; jdk8(新手推荐jdk8,自带javafx), jdk11及以上去除了javafx&#xff0c;需要单独安装,高版本jdk可见下面参考链接&#xff0c;jdk17的代码已升级maven 基于java实现的斗地主…

【mysql性能调优 • 三】字符集和校验规则

前言 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database Management System&#xff0c;关系…

基于51单片机的脉搏测量仪protues仿真设计

目录 一、设计背景 二、实现功能 三、仿真演示 四、源程序 一、设计背景 在中医四诊&#xff08;望﹑闻﹑问﹑切)中&#xff0c;脉诊占有非常重要的位置。脉诊是我国传统医学中最具特色的一项诊断方法&#xff0c;其历史悠久&#xff0c;内容丰富&#xff0c;是中医“整体观…

全网最详细SUMO仿真软件教程——入门篇

目录SUMO下载前提知识使用netedit创建路网需求生成SUMO-GUI可视化SUMO下载 SUMO官网: SUMO下载链接 配置SUMO_HOME系统变量&#xff0c;后续引入包需要。 前提知识 sumo仿真器跑起来需要三个文件&#xff0c;分别是Network、Route以及SUMO configuration file。 在sumo中&a…

Linux中安装新版minio(centos7版本)

1. 背景需求 由于一些限制,在客户现场的Linux操作系统中,没有安装docker k8s等容器,无法直接使用镜像安装,而且客户要求只能在原始的操作系统中安装最新版的minio,(为什么需要安装最新版的minio,因为检测国网检测到之前版本的minio有漏洞,需要安装新版的minio). 2. 安装minio…

20230415英语学习

Don’t Talk About Your Goals if You Want to Achieve Them 你下定决心的目标&#xff0c;并不必告诉他人 Have you ever heard that you shouldn’t tell anyone about your goals until you’ve fulfilled them?As with many popular sayings, this is good advice.And it…