Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
- Python 系列(十一) list、tuple、set、dict,到底该怎么选?
- Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
前面我们学习了 Python 的 List 列表和 Set 集合。
List 适合保存一组有序数据,Set 适合去重和集合运算。那么,如果我们想保存服务器名称、IP 地址、运行状态等信息,并且希望通过服务器名称快速找到对应的数据,该怎么办?
比如,我们需要管理多台 Linux 服务器,希望通过服务器名称查询 IP 地址,通过服务名称记录运行状态,或者统计不同服务器的 CPU 使用率。
如果把这些信息全部放进列表中,每次查询都可能需要逐个查找。有没有一种更适合保存键值对应关系的数据结构?
这就是今天要学习的 Dict(字典)。
字典是 Python 中非常重要的数据类型。理解了它,不仅能让我们更方便地管理数据,还能为后续学习 JSON、调用云平台 API、处理 Kubernetes 资源信息打下基础。
为什么需要字典?
先来看一个简单的 Linux 运维场景。
假设我们有三台服务器,需要记录它们的 IP 地址:
1 | servers = ["192.168.1.10", "192.168.1.11", "192.168.1.12"] |
现在,如果想知道 web-02 对应哪个 IP 地址,就会发现一个问题:列表中只有 IP 地址,并没有记录每个地址对应的服务器名称。
我们当然可以再创建一个列表:
1 | server_names = ["web-01", "web-02", "db-01"] |
通过相同的索引,就能找到服务器名称和 IP 地址之间的对应关系。
但这种方式有一个明显的问题:两个列表必须保持顺序一致。一旦其中一个列表发生变化,就可能导致服务器名称与 IP 地址对应错误。
有没有更直观的办法?
可以使用字典:
1 | servers = { |
运行结果:
1 | 192.168.1.11 |
这下就简单多了!
我们直接通过服务器名称,就能找到对应的 IP 地址,不需要记住列表索引,也不需要维护两个列表之间的对应关系。
这就是字典的核心思想:使用键(Key)关联值(Value),通过键查找对应的值。
Dict 的特点
在 Python 中,字典用于保存一组键值对,每个条目都包含一个键和一个值。
例如:
1 | server = { |
这里包含三个键值对:
"name"是键,对应的值是"web-01"。"ip"是键,对应的值是"192.168.1.10"。"status"是键,对应的值是"running"。
理解字典时,需要掌握几个重要特点。
键必须唯一。 同一个字典中不能同时存在两个相同的键。如果重复赋值,后面的值会覆盖前面的值。
键必须可哈希。 常见的字符串、整数和元组(满足可哈希条件)都可以作为键,而列表和字典不能直接作为键。
值可以重复。 不同的键可以对应相同的值,值也可以是列表、字典等复杂数据结构。
字典保留插入顺序。 从 Python 3.7 开始,字典的插入顺序属于语言规范保证的行为。因此,遍历字典时通常会按照键值对插入的顺序进行。
创建字典
创建字典最常见的方式,是使用大括号 {} 保存键值对:
1 | servers = { |
运行结果:
1 | {'web-01': '192.168.1.10', 'web-02': '192.168.1.11'} |
如果暂时没有数据,也可以创建一个空字典:
1 | servers = {} |
运行结果:
1 | {} |
注意,空大括号 {} 创建的是字典,而不是集合。创建空集合需要使用 set()。
除了使用大括号,也可以使用 dict():
1 | server = dict(name="web-01", ip="192.168.1.10") |
运行结果:
1 | {'name': 'web-01', 'ip': '192.168.1.10'} |
这种写法适合键名符合 Python 标识符规则的简单场景。
添加和修改键值对
字典可以随时添加新的键值对,也可以修改已有键对应的值。
例如,我们先创建一个服务器字典:
1 | server = { |
运行结果:
1 | {'name': 'web-01', 'ip': '192.168.1.10', 'status': 'running'} |
第一次使用 server["status"] = "running" 时,字典中还没有 status 这个键,因此会新增一个键值对。
第二次使用 server["ip"] = "192.168.1.20" 时,ip 键已经存在,所以会更新它对应的值。
这里有一个需要注意的地方:使用 字典[键] = 值 时,如果键不存在,就新增;如果键已经存在,就修改。
我们还可以使用 update() 一次添加或更新多个键值对:
1 | server.update({ |
运行结果:
1 | {'name': 'web-01', 'ip': '192.168.1.20', 'status': 'running', 'cpu': 35} |
为什么 Dict 查找这么快?
字典最重要的优势之一,就是可以通过键快速查找对应的值。
例如,我们需要查询服务器 web-02 的 IP 地址:
1 | servers = { |
输出
1 | 192.168.1.11 |
对于普通字典的键查找,平均时间复杂度通常为 (O(1)),也就是平均来看,查找所需的时间不会随着字典中元素数量的增加而线性增长。不过,在特殊情况下,查找也可能退化。
这是为什么?
Python 字典底层使用哈希表。可以先把它理解为一种能够帮助程序快速定位数据的机制。
当我们使用一个键查找数据时,Python 会利用键的哈希值定位相应的存储位置,再判断键是否匹配,从而找到对应的值。
整个过程可以简单理解为:
flowchart TD
A["服务器名称"] --> B["计算键的哈希值"]
B --> C["定位可能存储的位置"]
C --> D{"键是否匹配?"}
D -->|是| E["返回对应的值"]
D -->|否| F["继续查找其他候选位置"]
F --> D
classDef start fill:#e3f2fd,stroke:#1976d2,color:#1565c0
classDef process fill:#f5f5f5,stroke:#757575,color:#333333
classDef decision fill:#fff3e0,stroke:#ef6c00,color:#e65100
classDef success fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
class A start
class B,C,F process
class D decision
class E success相比之下,如果使用列表查找某个服务器名称,通常需要从头开始逐个比较,直到找到目标。
因此,如果我们需要频繁查询大量数据,字典通常会比列表更适合。
不过,这并不意味着字典在所有场景下都更好。字典需要额外的存储空间,而且如果只是保存一组数据、根本不需要通过键查询,那么使用列表可能更加简单。
访问数据与安全取值
字典创建好以后,最常见的操作就是获取键对应的值。
使用方括号访问
我们可以直接通过键获取值:
1 | servers = { |
运行结果:
1 | 192.168.1.10 |
这种方式简单直接,适合我们确定键一定存在的场景。
但是,如果访问一个不存在的键,就会引发 KeyError:
1 | print(servers["db-01"]) |
运行时会出现类似下面的错误:
1 | KeyError: 'db-01' |
为什么会这样?
因为字典中并没有 db-01 这个键,Python 无法找到对应的值,于是抛出了异常。
在实际运维工作中,这种情况很常见。例如,我们查询某台服务器的状态,但这台服务器可能还没有录入管理系统。如果程序直接使用方括号访问,就可能因为缺少键而中断执行。
有没有更安全的取值方式?
使用 get() 安全取值
字典提供了 get() 方法,用于获取指定键对应的值。
它与方括号访问的主要区别是:当键不存在时,get() 默认返回 None,而不是抛出 KeyError。
1 | servers = { |
运行结果:
1 | 192.168.1.10 |
如果键不存在时,希望返回一个更有意义的默认值,可以给 get() 传入第二个参数:
1 | servers = { |
运行结果:
1 | 192.168.1.10 |
再来看一个更贴近 Linux 运维的例子。
假设我们需要查询多台服务器的运行状态:
1 | server_status = { |
运行结果:
1 | web-03 未登记 |
即使 web-03 尚未登记,程序也能正常执行。
这里需要注意:get() 返回默认值并不代表数据一定有问题,也不代表它会自动修改字典。它只是帮助我们在键不存在时,得到一个预先指定的返回值。
还有一个细节:如果字典中的键存在,但对应的值恰好是 None,那么 get() 同样可能返回 None。如果业务需要严格区分“键不存在”和“键存在但值为 None”,可以使用 in 判断键是否存在。
1 | config = { |
运行结果:
1 | True |
掌握方括号和 get() 的区别,就能避免很多初学者常见的字典访问错误。
删除字典中的数据
在实际工作中,字典不仅需要添加和查询数据,也需要删除不再使用的键值对。
Python 字典提供了 pop()、popitem() 和 del 等操作。
使用 pop() 删除指定键
pop() 可以删除指定的键,并返回这个键对应的值。
1 | servers = { |
运行结果:
1 | 192.168.1.11 |
可以看到,pop() 不仅删除了 web-02,还返回了它原本对应的 IP 地址。
如果指定的键不存在,pop() 默认会引发 KeyError。不过,我们也可以指定一个默认值:
1 | ip = servers.pop("web-03", "未找到") |
运行结果:
1 | 未找到 |
这时,即使 web-03 不存在,程序也不会因为这次删除操作而抛出 KeyError。
使用 popitem() 删除最后插入的键值对
popitem() 会删除并返回字典中最后插入的键值对,返回结果是一个包含两个元素的元组。
1 | servers = { |
运行结果:
1 | ('db-01', '192.168.1.12') |
因为 db-01 是最后插入的键,所以它被删除了。
如果想直接把键和值分别保存到两个变量中,也可以使用解包:
1 | name, ip = servers.popitem() |
输出
1 | web-02 |
注意,popitem() 不是随机删除。对于现代 Python 字典,它遵循后进先出的顺序。如果字典为空,调用 popitem() 会引发 KeyError。
使用 del 删除指定键
如果只需要删除某个键值对,而不需要获取被删除的值,可以使用 del:
1 | servers = { |
运行结果:
1 | {'web-02': '192.168.1.11'} |
如果指定的键不存在,del 同样会引发 KeyError。
另外,如果想清空整个字典,可以使用 clear():
1 | servers.clear() |
运行结果:
1 | {} |
遍历字典中的键和值
如果需要批量处理字典中的数据,就需要掌握遍历操作。
例如,我们保存了多台服务器的运行状态,现在想逐台检查哪些服务器正常运行,哪些服务器已经停止。
字典提供了 keys()、values() 和 items() 三个常用方法。
遍历所有键
直接使用 for 循环遍历字典时,默认遍历的是键。
1 | servers = { |
运行结果:
1 | web-01 |
当然,也可以显式调用 keys():
1 | for name in servers.keys(): |
两种写法都可以遍历字典中的键。通常情况下,如果只需要键,直接遍历字典就足够了。
遍历所有值
如果只需要获取每台服务器的状态,可以使用 values():
1 | for status in servers.values(): |
运行结果:
1 | running |
values() 返回的是字典值的动态视图,而不是一个独立复制出来的列表。
这意味着,当字典中的值发生变化时,视图会反映这些变化。
如果需要一个独立的列表,可以使用 list(servers.values()) 将其转换为列表。
使用 items() 同时获取键和值
在实际运维中,我们经常既需要服务器名称,又需要它对应的运行状态。
这种场景最适合使用 items()。
1 | for item in servers.items(): |
运行结果:
1 | ('web-01', 'running') |
items() 返回的每个元素都是一个包含键和值的二元组。
如果不想每次都通过 item[0] 和 item[1] 获取数据,可以直接在 for 循环中解包:
1 | for name, status in servers.items(): |
运行结果:
1 | web-01 running |
这种写法更加直观,也是实际开发中非常常见的方式。
实战:统计服务器运行状态
掌握了 items() 之后,我们就可以将它应用到一个简单的运维场景中。
假设我们已经收集了多台服务器的运行状态,希望统计正常运行和已停止的服务器数量:
1 | servers = { |
运行结果:
1 | web-01: running |
这个例子将字典遍历、条件判断和计数操作结合在了一起。
当然,这里的服务器状态是提前写入字典的模拟数据。实际工作中,我们可以通过 SSH、监控系统或云平台 API 获取状态,再使用类似的方式进行统计。
还需要注意:keys()、values() 和 items() 返回的都是动态视图。如果遍历字典时直接增删字典中的键,可能引发运行时错误。因此,如果确实需要在遍历过程中删除元素,可以先遍历 list(servers.items()) 等创建的副本,再执行修改操作。
Dict、List 和 Set 应该怎么选?
学到这里,我们已经认识了三种常见的数据类型。
它们都可以保存多个元素,但设计目的并不相同。
| 数据类型 | 核心特点 | 常见用途 |
|---|---|---|
| List | 有序、可重复、可修改 | 保存任务列表、日志记录、执行结果 |
| Set | 元素唯一、支持集合运算 | 数据去重、服务器清单对比 |
| Dict | 键唯一,通过键关联值 | 保存服务器信息、配置参数、API 返回数据 |
举个例子:
如果只需要保存一批服务器名称,可以使用 List:
1 | servers = ["web-01", "web-02", "db-01"] |
如果需要对服务器名称去重,或者比较两份服务器清单,可以使用 Set:
1 | servers = {"web-01", "web-02", "db-01"} |
如果需要通过服务器名称查找 IP 地址、运行状态等信息,就应该考虑 Dict:
1 | servers = { |
注意,这里字典的值本身又是一个字典。这种嵌套结构在实际开发中非常常见,尤其是在处理 JSON 数据、云平台 API 返回结果和 Kubernetes 资源信息时。
例如,我们可以通过两次键访问获取服务器状态:
1 | print(servers["web-01"]["status"]) |
运行结果:
1 | running |
如果外层的服务器名称可能不存在,就可以先使用 get() 进行安全访问:
1 | server = servers.get("web-03") |
运行结果:
1 | 服务器不存在 |
这也是为什么字典值得认真学习:它不仅适合保存简单的键值对,还可以作为组织复杂数据的基础。
写在最后
这一篇,我们学习了 Python 字典的核心用法,包括创建字典、添加和修改数据、通过键查找值、使用 get() 安全取值、删除键值对,以及使用 keys()、values() 和 items() 遍历字典。
其中最重要的是理解三件事:
- 需要通过键快速找到对应的数据时,优先考虑 Dict。
- 不确定某个键是否存在时,可以使用
get()或先判断键是否存在。 - 需要同时处理键和值时,
items()往往是最方便的选择。
从 List 到 Set,再到 Dict,我们已经掌握了 Python 中三种非常重要的数据结构。
在 Linux 运维自动化中,它们经常需要配合使用:List 保存任务,Set 对比清单,Dict 记录每台服务器的详细信息。
不过,数据保存好了,还需要考虑如何整理数据。比如,如何按照 CPU 使用率对服务器进行排序?如何按照磁盘使用率,从高到低找出最需要关注的服务器?又该如何对列表中的数据进行升序或降序排列?
下一篇,我们就来学习 Python 的排序操作,看看如何使用 sort() 和 sorted() 对数据进行排序,让杂乱的数据变得更加清晰、有序。
