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 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
  19. Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
  20. Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
  21. Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
  22. Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
  23. Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
  24. Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
  25. Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
  26. Python 系列(二十六) Python 模块入门,从 import 到模块复用
  27. Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
  28. Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
  29. Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
  30. Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
  31. Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
  32. Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
  33. Python 系列(三十三) 什么是类和对象?零基础理解 Python class
  34. Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
  35. Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
  36. Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
  37. Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
  38. Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
  39. Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
  40. Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
  41. Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
  42. Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
  43. Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
  44. Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
  45. Python 系列(四十五) 使用 sys 模块处理标准输入与输出
  46. Python 系列(四十六) 使用 open() 读取和写入文件
  47. Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
  48. Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
  49. Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
  50. Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
  51. Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
  52. Python 系列(五十二) 掌握字典推导式,让字典创建更简单
  53. Python 系列(五十三) 从零理解生成器,轻松处理大量数据
  54. Python 系列(五十五) 使用 zip() 同时遍历多个集合

前面我们学习了列表推导式,知道了可以通过一行代码,根据已有的数据快速创建一个列表。

例如,我们有一组数字,希望把它们转换成十六进制字符串,再保存到列表中,就可以使用列表推导式:

1
2
3
4
5
numbers = [10, 20, 30, 40]

hex_numbers = [hex(number) for number in numbers]

print(hex_numbers)

运行结果:

1
['0xa', '0x14', '0x1e', '0x28']

不过,在实际工作中,我们并不总是需要列表。

例如,假设我们正在管理 Linux 服务器,需要记录每台服务器的名称和 IP 地址:

1
2
3
4
5
servers = [
("web01", "192.168.1.10"),
("web02", "192.168.1.11"),
("db01", "192.168.1.20")
]

现在,这些数据保存在列表中,每台服务器的信息由一个元组记录。

如果希望通过服务器名称快速查找对应的 IP 地址,那么使用字典会更加方便。

我们可以手动创建一个字典:

1
2
3
4
5
6
7
server_ips = {
"web01": "192.168.1.10",
"web02": "192.168.1.11",
"db01": "192.168.1.20"
}

print(server_ips["web01"])

运行结果:

1
192.168.1.10

这种方式没有问题。

但是,如果服务器有几百台甚至几千台,数据又已经保存在列表中,我们难道还要手动把每台服务器的信息重新写进字典吗?

有没有一种方法,能够直接利用现有数据,自动生成我们需要的字典?

这就是今天要学习的内容:字典推导式。

什么是字典推导式

字典推导式是一种根据已有数据,快速创建字典的简洁写法。

它和列表推导式的思路非常相似,只不过最终创建出来的数据结构不同。

  • 列表推导式创建列表,使用方括号 []。
  • 字典推导式创建字典,使用花括号 {},并且每一项都需要包含键和值。

先来看一个简单的例子:

1
2
3
4
5
numbers = [1, 2, 3, 4]

number_dict = {number: number * number for number in numbers}

print(number_dict)

运行结果:

1
{1: 1, 2: 4, 3: 9, 4: 16}

这个结果可能有点陌生,我们一步一步理解。

原始数据是一个列表:

1
numbers = [1, 2, 3, 4]

我们希望把每个数字作为字典的键,再把这个数字的平方作为对应的值。

最终得到的字典,相当于:

1
2
3
4
5
6
number_dict = {
1: 1,
2: 4,
3: 9,
4: 16
}

例如,数字 3 对应的键是 3,对应的值是 9,因为 (3 \times 3 = 9)。

字典推导式就是把这个创建过程简洁地写在一行代码中。

字典推导式的基本语法

字典推导式的基本语法如下:

1
{键表达式: 值表达式 for 变量 in 可迭代对象}

这里有几个部分需要理解。

语法部分含义
{}表示创建一个字典
键表达式决定字典中每个项目的键
值表达式决定字典中每个项目的值
for 变量 in 可迭代对象依次取出数据,逐项生成字典内容

其中,所谓的可迭代对象,可以先简单理解为能够逐个取出数据的对象,例如列表、元组和字符串。

例如:

1
2
3
4
5
numbers = [1, 2, 3]

result = {number: number * 10 for number in numbers}

print(result)

运行结果:

1
{1: 10, 2: 20, 3: 30}

我们可以把这行代码拆开理解:

1
{number: number * 10 for number in numbers}
  • for number in numbers:依次取出列表中的每个数字。
  • number:把当前数字作为字典的键。
  • number * 10:把当前数字乘以 10,作为字典的值。
  • {}:将生成的键和值组合起来,创建字典。

需要注意,number 只是我们自己起的变量名,也可以换成其他容易理解的名字。

理解字典推导式的执行过程

对于刚接触 Python 的朋友来说,直接看一行代码可能不容易理解。

我们可以把它展开,看看 Python 是如何逐项创建字典的。

还是使用刚才的例子:

1
2
3
4
5
numbers = [1, 2, 3]

result = {number: number * 10 for number in numbers}

print(result)

它的执行过程可以理解为:

flowchart TD
    A["开始创建字典"] --> B["取出第一个数字 1"]
    B --> C["计算键和值:1 : 10"]
    C --> D["保存到字典"]
    D --> E["取出下一个数字"]
    E --> F{"还有数字吗?"}
    F -->|有| G["计算当前数字对应的键和值"]
    G --> D
    F -->|没有| H["得到最终字典"]

整个过程可以分为以下几步:

第一步:取出数字 1

Python 取出第一个数字 1。

第二步:计算键和值

键是 number,因此键为 1。

值是 number * 10,因此值为 10。

字典中就有了这样一个项目:

1
1: 10

第三步:继续处理其他数字

接下来,Python 依次处理数字 2 和 3,分别生成:

1
2
2: 20
3: 30

第四步:得到完整字典

最终生成:

1
{1: 10, 2: 20, 3: 30}

这就是字典推导式的基本执行过程:取出数据、计算键和值、将结果放入字典,直到所有数据处理完成。

使用字典推导式转换已有数据

理解基本语法后,我们再来看一个更贴近实际工作的例子。

假设我们管理着几台 Linux 服务器,已经将服务器名称和 IP 地址保存到了一个列表中:

1
2
3
4
5
servers = [
("web01", "192.168.1.10"),
("web02", "192.168.1.11"),
("db01", "192.168.1.20")
]

每个元组中,第一个元素是服务器名称,第二个元素是 IP 地址。

现在,我们希望创建一个字典,让服务器名称作为键,IP 地址作为值。

可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
servers = [
("web01", "192.168.1.10"),
("web02", "192.168.1.11"),
("db01", "192.168.1.20")
]

server_ips = {
name: ip
for name, ip in servers
}

print(server_ips)

运行结果:

1
2
3
4
5
{
'web01': '192.168.1.10',
'web02': '192.168.1.11',
'db01': '192.168.1.20'
}

这里出现了一个新的写法:

1
for name, ip in servers

为什么可以同时使用 name 和 ip 两个变量?

因为 servers 中的每个元素都是一个包含两个值的元组。

例如:

1
("web01", "192.168.1.10")

Python 可以把这个元组中的两个值分别赋给两个变量:

1
2
name = "web01"
ip = "192.168.1.10"

这种操作叫作序列解包。这里不需要急着深入研究,只要知道两个变量可以分别接收元组中的两个值即可。

接下来:

1
name: ip

表示将服务器名称作为字典的键,将 IP 地址作为字典的值。

因此,最终创建出来的字典可以通过服务器名称查找 IP 地址:

1
2
print(server_ips["web01"])
print(server_ips["db01"])

运行结果:

1
2
192.168.1.10
192.168.1.20

这就是字典推导式的一个实际用途:将已有的数据转换成更方便查询的字典结构。

在字典推导式中添加条件

有时候,我们并不希望所有数据都进入字典。

例如,服务器清单中可能同时包含生产服务器和测试服务器,但我们只想提取名称以 web 开头的服务器。

字典推导式也可以添加条件。

基本语法如下:

1
{键表达式: 值表达式 for 变量 in 可迭代对象 if 条件}

这里的 if 用来判断当前数据是否满足条件。

满足条件的数据才会进入最终字典,不满足条件的数据会被跳过。

来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
servers = [
("web01", "192.168.1.10"),
("web02", "192.168.1.11"),
("db01", "192.168.1.20"),
("web03", "192.168.1.12")
]

web_servers = {
name: ip
for name, ip in servers
if name.startswith("web")
}

print(web_servers)

运行结果:

1
2
3
4
5
{
'web01': '192.168.1.10',
'web02': '192.168.1.11',
'web03': '192.168.1.12'
}

其中:

1
if name.startswith("web")

表示检查服务器名称是否以 web 开头。

例如:

1
"web01".startswith("web")

结果是:

1
True

而:

1
"db01".startswith("web")

结果是:

1
False

因此,web01、web02 和 web03 会进入字典,而 db01 会被跳过。

我们可以把整个过程理解为:

flowchart TD
    A["读取服务器信息"] --> B["获取服务器名称和 IP"]
    B --> C{"名称是否以 web 开头?"}
    C -->|是| D["将名称和 IP 加入字典"]
    C -->|否| E["跳过当前服务器"]
    D --> F{"还有服务器吗?"}
    E --> F
    F -->|有| B
    F -->|没有| G["得到筛选后的字典"]

这里需要注意,if 放在 for 后面,表示筛选数据。

它并不是让整个字典创建过程只执行一次,而是对每一条数据分别进行判断。

使用字典推导式调整字典中的值

除了创建新字典和筛选数据,字典推导式还可以根据已有字典生成一个新字典,并修改其中的值。

例如,我们有一份服务器 CPU 使用率清单:

1
2
3
4
5
cpu_usage = {
"web01": 35,
"web02": 68,
"db01": 82
}

假设这些数字表示 CPU 使用率的百分比。

现在,我们希望把所有服务器的使用率转换成带有百分号的字符串,方便后续展示。

可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
cpu_usage = {
"web01": 35,
"web02": 68,
"db01": 82
}

cpu_text = {
name: f"{usage}%"
for name, usage in cpu_usage.items()
}

print(cpu_text)

运行结果:

1
2
3
4
5
{
'web01': '35%',
'web02': '68%',
'db01': '82%'
}

这里使用了:

1
cpu_usage.items()

它可以让我们逐项获取字典中的键和值。

例如,第一项可以理解为:

1
("web01", 35)

于是:

1
for name, usage in cpu_usage.items()

就可以分别获取服务器名称和 CPU 使用率。

而:

1
f"{usage}%"

则使用 f-string 将数字和百分号组合成字符串。

例如,usage 的值是 35 时,结果就是字符串 "35%"。

这样,我们就根据原始字典创建了一个新的字典,而且没有修改原来的数据。

字典推导式与普通循环有什么区别

学到这里,你可能会问:如果字典推导式可以完成这些操作,那么普通的 for 循环是不是就没有用了?

当然不是。

字典推导式的优势在于,当需求比较简单时,可以用更简洁的代码完成字典创建。

例如,使用普通循环创建数字平方字典:

1
2
3
4
5
6
7
8
numbers = [1, 2, 3, 4]

number_dict = {}

for number in numbers:
number_dict[number] = number * number

print(number_dict)

运行结果:

1
{1: 1, 2: 4, 3: 9, 4: 16}

使用字典推导式,可以写成:

1
2
3
4
5
6
7
8
numbers = [1, 2, 3, 4]

number_dict = {
number: number * number
for number in numbers
}

print(number_dict)

运行结果相同。

两种方式的区别在于:

比较项目普通 for 循环字典推导式
代码长度通常较长通常较短
阅读方式一步一步执行赋值直接描述生成规则
适合场景逻辑复杂、需要多步处理根据数据快速生成字典
可读性复杂逻辑下更容易组织简单转换时通常更直观

对于初学者来说,不需要为了追求代码简短,就把所有循环都改成推导式。

如果逻辑比较简单,字典推导式可以让代码更加紧凑;如果需要多层判断、复杂计算或者多步操作,普通循环往往更容易理解和维护。

代码简洁很重要,但让别人看得懂同样重要。

使用字典推导式时需要注意什么

字典的键不能重复

字典中的键具有唯一性。

例如:

1
2
3
4
5
6
7
8
numbers = [1, 1, 2, 2, 3]

result = {
number: number * 10
for number in numbers
}

print(result)

运行结果:

1
{1: 10, 2: 20, 3: 30}

虽然原始列表中有重复数字,但最终字典中只会保留每个键对应的一项。

如果同一个键生成了多次,后面生成的值会覆盖前面同一个键对应的值。

因此,使用字典推导式时,要留意生成的键是否可能重复。

字典推导式不一定让程序运行得更快

字典推导式主要是为了让代码更加简洁,表达数据转换的意图。

它不意味着所有情况下都会比普通循环运行得更快,也不能减少创建字典本身所需要的存储空间。

如果处理的是大量服务器信息、日志数据或者其他业务数据,除了代码写法,还需要考虑数据规模、处理逻辑和内存使用情况。

不要把代码写得过于复杂

例如,简单的数据转换可以直接使用字典推导式:

1
2
3
4
5
6
numbers = [1, 2, 3]

result = {
number: number * 2
for number in numbers
}

但如果一行代码中塞入多层循环、复杂条件和大量计算,就可能变得难以阅读。

遇到这种情况,可以先使用普通循环把逻辑写清楚,再考虑是否适合改写成推导式。

动手练习

掌握一个知识点,最好的办法就是自己动手写一遍。

下面有三个小练习,可以先尝试独立完成,再查看参考答案。

练习:创建数字平方字典

给定一个数字列表:

1
numbers = [2, 4, 6, 8]

请使用字典推导式,让每个数字作为键,它的平方作为值。

预期结果:

1
{2: 4, 4: 16, 6: 36, 8: 64}

参考答案:

1
2
3
4
5
6
7
8
numbers = [2, 4, 6, 8]

result = {
number: number ** 2
for number in numbers
}

print(result)

这里的 ** 2 表示求平方,例如 4 ** 2 的结果是 16。

练习:筛选指定范围内的数字

给定一个数字列表:

1
numbers = [10, 25, 40, 55, 70]

请创建一个字典,只保留大于或等于 40 的数字,并让每个数字作为键、数字乘以 2 的结果作为值。

预期结果:

1
{40: 80, 55: 110, 70: 140}

参考答案:

1
2
3
4
5
6
7
8
9
numbers = [10, 25, 40, 55, 70]

result = {
number: number * 2
for number in numbers
if number >= 40
}

print(result)

这里的 if number >= 40 用于筛选数据,只有满足条件的数字才会进入字典。

练习:生成服务器 IP 地址字典

给定服务器信息:

1
2
3
4
5
servers = [
("web01", "192.168.1.10"),
("db01", "192.168.1.20"),
("web02", "192.168.1.11")
]

请使用字典推导式,创建一个只包含 web 开头的服务器名称及其 IP 地址的字典。

预期结果:

1
2
3
4
{
'web01': '192.168.1.10',
'web02': '192.168.1.11'
}

参考答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
servers = [
("web01", "192.168.1.10"),
("db01", "192.168.1.20"),
("web02", "192.168.1.11")
]

result = {
name: ip
for name, ip in servers
if name.startswith("web")
}

print(result)

这个练习结合了字典创建、序列解包和条件筛选,也是字典推导式在实际数据处理中的一种常见用法。

写在最后

今天我们学习了字典推导式,了解了如何利用已有数据快速创建字典,以及如何通过条件筛选数据、转换字典中的值。

回顾一下,字典推导式的基本语法是:

1
{键表达式: 值表达式 for 变量 in 可迭代对象}

如果需要筛选数据,可以在后面添加 if 条件:

1
{键表达式: 值表达式 for 变量 in 可迭代对象 if 条件}

记住这两个基本形式,再结合实际需求练习,就能逐渐掌握字典推导式。

从列表推导式到字典推导式,我们已经接触了两种使用简洁语法创建数据结构的方式。

不过,还有一个问题值得思考:如果数据量非常大,我们暂时不需要一次性创建并保存所有结果,而是希望程序用到一个数据时,再生成一个数据,该怎么办?

例如,处理大型日志文件、逐条读取服务器监控数据时,我们有时并不需要提前把所有结果放进列表中。

下一篇,我们将学习 生成器,看看 Python 如何在需要时逐个生成数据,帮助我们更灵活地处理数据。