1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Python 系列(一) 从零开始认识 Python,入门其实没那么难
  2. Python 系列(二) 学 Python 的第一步,把开发环境搭建好
  3. Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
  4. Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
  5. Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
  6. Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
  7. Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
  8. Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
  9. Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
  10. Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
  11. Python 系列(十一) list、tuple、set、dict,到底该怎么选?
  12. Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
  13. Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
  14. Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
  15. Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
  16. Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
  17. Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
  18. 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
2
server_names = ["web-01", "web-02", "db-01"]
server_ips = ["192.168.1.10", "192.168.1.11", "192.168.1.12"]

通过相同的索引,就能找到服务器名称和 IP 地址之间的对应关系。

但这种方式有一个明显的问题:两个列表必须保持顺序一致。一旦其中一个列表发生变化,就可能导致服务器名称与 IP 地址对应错误。

有没有更直观的办法?

可以使用字典:

1
2
3
4
5
6
7
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11",
"db-01": "192.168.1.12"
}

print(servers["web-02"])

运行结果:

1
192.168.1.11

这下就简单多了!

我们直接通过服务器名称,就能找到对应的 IP 地址,不需要记住列表索引,也不需要维护两个列表之间的对应关系。

这就是字典的核心思想:使用键(Key)关联值(Value),通过键查找对应的值。

Dict 的特点

在 Python 中,字典用于保存一组键值对,每个条目都包含一个键和一个值。

例如:

1
2
3
4
5
server = {
"name": "web-01",
"ip": "192.168.1.10",
"status": "running"
}

这里包含三个键值对:

  • "name" 是键,对应的值是 "web-01"。
  • "ip" 是键,对应的值是 "192.168.1.10"。
  • "status" 是键,对应的值是 "running"。

理解字典时,需要掌握几个重要特点。

键必须唯一。 同一个字典中不能同时存在两个相同的键。如果重复赋值,后面的值会覆盖前面的值。

键必须可哈希。 常见的字符串、整数和元组(满足可哈希条件)都可以作为键,而列表和字典不能直接作为键。

值可以重复。 不同的键可以对应相同的值,值也可以是列表、字典等复杂数据结构。

字典保留插入顺序。 从 Python 3.7 开始,字典的插入顺序属于语言规范保证的行为。因此,遍历字典时通常会按照键值对插入的顺序进行。

创建字典

创建字典最常见的方式,是使用大括号 {} 保存键值对:

1
2
3
4
5
6
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11"
}

print(servers)

运行结果:

1
{'web-01': '192.168.1.10', 'web-02': '192.168.1.11'}

如果暂时没有数据,也可以创建一个空字典:

1
2
servers = {}
print(servers)

运行结果:

1
{}

注意,空大括号 {} 创建的是字典,而不是集合。创建空集合需要使用 set()。

除了使用大括号,也可以使用 dict():

1
2
server = dict(name="web-01", ip="192.168.1.10")
print(server)

运行结果:

1
{'name': 'web-01', 'ip': '192.168.1.10'}

这种写法适合键名符合 Python 标识符规则的简单场景。

添加和修改键值对

字典可以随时添加新的键值对,也可以修改已有键对应的值。

例如,我们先创建一个服务器字典:

1
2
3
4
5
6
7
8
9
10
server = {
"name": "web-01",
"ip": "192.168.1.10"
}

server["status"] = "running"
print(server)

server["ip"] = "192.168.1.20"
print(server)

运行结果:

1
2
{'name': 'web-01', 'ip': '192.168.1.10', 'status': 'running'}
{'name': 'web-01', 'ip': '192.168.1.20', 'status': 'running'}

第一次使用 server["status"] = "running" 时,字典中还没有 status 这个键,因此会新增一个键值对。

第二次使用 server["ip"] = "192.168.1.20" 时,ip 键已经存在,所以会更新它对应的值。

这里有一个需要注意的地方:使用 字典[键] = 值 时,如果键不存在,就新增;如果键已经存在,就修改。

我们还可以使用 update() 一次添加或更新多个键值对:

1
2
3
4
5
6
server.update({
"status": "running",
"cpu": 35
})

print(server)

运行结果:

1
{'name': 'web-01', 'ip': '192.168.1.20', 'status': 'running', 'cpu': 35}

为什么 Dict 查找这么快?

字典最重要的优势之一,就是可以通过键快速查找对应的值。

例如,我们需要查询服务器 web-02 的 IP 地址:

1
2
3
4
5
6
7
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11",
"db-01": "192.168.1.12"
}

print(servers["web-02"])

输出

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
2
3
4
5
6
7
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11"
}

ip = servers["web-01"]
print(ip)

运行结果:

1
192.168.1.10

这种方式简单直接,适合我们确定键一定存在的场景。

但是,如果访问一个不存在的键,就会引发 KeyError:

1
print(servers["db-01"])

运行时会出现类似下面的错误:

1
KeyError: 'db-01'

为什么会这样?

因为字典中并没有 db-01 这个键,Python 无法找到对应的值,于是抛出了异常。

在实际运维工作中,这种情况很常见。例如,我们查询某台服务器的状态,但这台服务器可能还没有录入管理系统。如果程序直接使用方括号访问,就可能因为缺少键而中断执行。

有没有更安全的取值方式?

使用 get() 安全取值

字典提供了 get() 方法,用于获取指定键对应的值。

它与方括号访问的主要区别是:当键不存在时,get() 默认返回 None,而不是抛出 KeyError。

1
2
3
4
5
6
7
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11"
}

print(servers.get("web-01"))
print(servers.get("db-01"))

运行结果:

1
2
192.168.1.10
None

如果键不存在时,希望返回一个更有意义的默认值,可以给 get() 传入第二个参数:

1
2
3
4
5
6
7
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11"
}

print(servers.get("web-01", "未知服务器"))
print(servers.get("db-01", "未知服务器"))

运行结果:

1
2
192.168.1.10
未知服务器

再来看一个更贴近 Linux 运维的例子。

假设我们需要查询多台服务器的运行状态:

1
2
3
4
5
6
7
8
9
10
server_status = {
"web-01": "running",
"web-02": "running",
"db-01": "stopped"
}

name = "web-03"
status = server_status.get(name, "未登记")

print(name, status)

运行结果:

1
web-03 未登记

即使 web-03 尚未登记,程序也能正常执行。

这里需要注意:get() 返回默认值并不代表数据一定有问题,也不代表它会自动修改字典。它只是帮助我们在键不存在时,得到一个预先指定的返回值。

还有一个细节:如果字典中的键存在,但对应的值恰好是 None,那么 get() 同样可能返回 None。如果业务需要严格区分“键不存在”和“键存在但值为 None”,可以使用 in 判断键是否存在。

1
2
3
4
5
6
config = {
"timeout": None
}

print("timeout" in config)
print("retries" in config)

运行结果:

1
2
True
False

掌握方括号和 get() 的区别,就能避免很多初学者常见的字典访问错误。

删除字典中的数据

在实际工作中,字典不仅需要添加和查询数据,也需要删除不再使用的键值对。

Python 字典提供了 pop()、popitem() 和 del 等操作。

使用 pop() 删除指定键

pop() 可以删除指定的键,并返回这个键对应的值。

1
2
3
4
5
6
7
8
9
10
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11",
"db-01": "192.168.1.12"
}

ip = servers.pop("web-02")

print(ip)
print(servers)

运行结果:

1
2
192.168.1.11
{'web-01': '192.168.1.10', 'db-01': '192.168.1.12'}

可以看到,pop() 不仅删除了 web-02,还返回了它原本对应的 IP 地址。

如果指定的键不存在,pop() 默认会引发 KeyError。不过,我们也可以指定一个默认值:

1
2
ip = servers.pop("web-03", "未找到")
print(ip)

运行结果:

1
未找到

这时,即使 web-03 不存在,程序也不会因为这次删除操作而抛出 KeyError。

使用 popitem() 删除最后插入的键值对

popitem() 会删除并返回字典中最后插入的键值对,返回结果是一个包含两个元素的元组。

1
2
3
4
5
6
7
8
9
10
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11",
"db-01": "192.168.1.12"
}

item = servers.popitem()

print(item)
print(servers)

运行结果:

1
2
('db-01', '192.168.1.12')
{'web-01': '192.168.1.10', 'web-02': '192.168.1.11'}

因为 db-01 是最后插入的键,所以它被删除了。

如果想直接把键和值分别保存到两个变量中,也可以使用解包:

1
2
3
4
name, ip = servers.popitem()

print(name)
print(ip)

输出

1
2
web-02
192.168.1.11

注意,popitem() 不是随机删除。对于现代 Python 字典,它遵循后进先出的顺序。如果字典为空,调用 popitem() 会引发 KeyError。

使用 del 删除指定键

如果只需要删除某个键值对,而不需要获取被删除的值,可以使用 del:

1
2
3
4
5
6
7
8
servers = {
"web-01": "192.168.1.10",
"web-02": "192.168.1.11"
}

del servers["web-01"]

print(servers)

运行结果:

1
{'web-02': '192.168.1.11'}

如果指定的键不存在,del 同样会引发 KeyError。

另外,如果想清空整个字典,可以使用 clear():

1
2
servers.clear()
print(servers)

运行结果:

1
{}

遍历字典中的键和值

如果需要批量处理字典中的数据,就需要掌握遍历操作。

例如,我们保存了多台服务器的运行状态,现在想逐台检查哪些服务器正常运行,哪些服务器已经停止。

字典提供了 keys()、values() 和 items() 三个常用方法。

遍历所有键

直接使用 for 循环遍历字典时,默认遍历的是键。

1
2
3
4
5
6
7
8
servers = {
"web-01": "running",
"web-02": "running",
"db-01": "stopped"
}

for name in servers:
print(name)

运行结果:

1
2
3
web-01
web-02
db-01

当然,也可以显式调用 keys():

1
2
for name in servers.keys():
print(name)

两种写法都可以遍历字典中的键。通常情况下,如果只需要键,直接遍历字典就足够了。

遍历所有值

如果只需要获取每台服务器的状态,可以使用 values():

1
2
for status in servers.values():
print(status)

运行结果:

1
2
3
running
running
stopped

values() 返回的是字典值的动态视图,而不是一个独立复制出来的列表。

这意味着,当字典中的值发生变化时,视图会反映这些变化。

如果需要一个独立的列表,可以使用 list(servers.values()) 将其转换为列表。

使用 items() 同时获取键和值

在实际运维中,我们经常既需要服务器名称,又需要它对应的运行状态。

这种场景最适合使用 items()。

1
2
for item in servers.items():
print(item)

运行结果:

1
2
3
('web-01', 'running')
('web-02', 'running')
('db-01', 'stopped')

items() 返回的每个元素都是一个包含键和值的二元组。

如果不想每次都通过 item[0] 和 item[1] 获取数据,可以直接在 for 循环中解包:

1
2
for name, status in servers.items():
print(name, status)

运行结果:

1
2
3
web-01 running
web-02 running
db-01 stopped

这种写法更加直观,也是实际开发中非常常见的方式。

实战:统计服务器运行状态

掌握了 items() 之后,我们就可以将它应用到一个简单的运维场景中。

假设我们已经收集了多台服务器的运行状态,希望统计正常运行和已停止的服务器数量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
servers = {
"web-01": "running",
"web-02": "running",
"web-03": "stopped",
"db-01": "running",
"db-02": "stopped"
}

running_count = 0
stopped_count = 0

for name, status in servers.items():
print(f"{name}: {status}")

if status == "running":
running_count += 1
elif status == "stopped":
stopped_count += 1

print("正常运行:", running_count)
print("已停止:", stopped_count)

运行结果:

1
2
3
4
5
6
7
web-01: running
web-02: running
web-03: stopped
db-01: running
db-02: stopped
正常运行: 3
已停止: 2

这个例子将字典遍历、条件判断和计数操作结合在了一起。

当然,这里的服务器状态是提前写入字典的模拟数据。实际工作中,我们可以通过 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
2
3
4
5
6
7
8
9
10
servers = {
"web-01": {
"ip": "192.168.1.10",
"status": "running"
},
"web-02": {
"ip": "192.168.1.11",
"status": "stopped"
}
}

注意,这里字典的值本身又是一个字典。这种嵌套结构在实际开发中非常常见,尤其是在处理 JSON 数据、云平台 API 返回结果和 Kubernetes 资源信息时。

例如,我们可以通过两次键访问获取服务器状态:

1
print(servers["web-01"]["status"])

运行结果:

1
running

如果外层的服务器名称可能不存在,就可以先使用 get() 进行安全访问:

1
2
3
4
5
6
server = servers.get("web-03")

if server is not None:
print(server.get("status", "未知"))
else:
print("服务器不存在")

运行结果:

1
服务器不存在

这也是为什么字典值得认真学习:它不仅适合保存简单的键值对,还可以作为组织复杂数据的基础。

写在最后

这一篇,我们学习了 Python 字典的核心用法,包括创建字典、添加和修改数据、通过键查找值、使用 get() 安全取值、删除键值对,以及使用 keys()、values() 和 items() 遍历字典。

其中最重要的是理解三件事:

  • 需要通过键快速找到对应的数据时,优先考虑 Dict。
  • 不确定某个键是否存在时,可以使用 get() 或先判断键是否存在。
  • 需要同时处理键和值时,items() 往往是最方便的选择。

从 List 到 Set,再到 Dict,我们已经掌握了 Python 中三种非常重要的数据结构。

在 Linux 运维自动化中,它们经常需要配合使用:List 保存任务,Set 对比清单,Dict 记录每台服务器的详细信息。

不过,数据保存好了,还需要考虑如何整理数据。比如,如何按照 CPU 使用率对服务器进行排序?如何按照磁盘使用率,从高到低找出最需要关注的服务器?又该如何对列表中的数据进行升序或降序排列?

下一篇,我们就来学习 Python 的排序操作,看看如何使用 sort() 和 sorted() 对数据进行排序,让杂乱的数据变得更加清晰、有序。