Python 系列(十一) list、tuple、set、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 列表实战,搞定数据增删改查与批量处理
假设你是一名 Linux 运维工程师,每天都需要检查公司的服务器运行状态。
刚开始,服务器只有三五台,你可以手动登录服务器,执行 uptime、free -h 和 df -h,看看系统负载、内存和磁盘空间是否正常。
后来,服务器越来越多,从几台变成几十台,甚至上百台。每天手动检查显然不太现实,于是你决定写一个 Python 脚本,把这些重复工作自动化。
问题来了:程序怎么知道需要检查哪些服务器?每台服务器的 IP 地址存在哪里?如果服务器列表里出现了重复的主机名,该怎么处理?如果一组检查项目是固定的,又该如何保存?
你可能会想到,直接把这些信息写在程序里不就行了吗?
当然可以,但随着需求越来越复杂,你会发现,不同的数据其实有不同的特点。
有些数据需要按照指定顺序依次处理,有些数据不能重复,有些数据需要通过服务器名称找到对应的 IP 地址,还有些数据在定义之后就不希望被随意修改。
如果全部使用同一种方式存储,处理起来就不那么方便了。
这就像整理日常运维工作中的各种信息:服务器清单、IP 地址表、巡检项目,各有各的用途,自然也需要不同的组织方式。
Python 已经为我们准备好了几种常用的数据组织工具。它们分别是 list、tuple、set 和 dict。
本篇我们先不急着深入具体操作,而是通过服务器巡检这个场景,认识这四种类型,了解它们各自适合解决什么问题,以及在编写 Python 程序时应该如何选择。
先从服务器清单说起:为什么需要不同的数据类型?
假设我们需要检查以下四台服务器:
web01web02db01web01
等等,为什么 web01 出现了两次?
这可能是录入时不小心重复添加了,也可能是不同的配置文件合并之后产生了重复数据。无论是什么原因,我们都需要根据实际需求决定如何处理。
再往下想,巡检脚本还需要知道每台服务器的 IP 地址。例如,web01 对应 192.168.1.10,web02 对应 192.168.1.11。
如果只是保存一组服务器名称,和保存服务器名称与 IP 地址之间的对应关系,显然不是同一回事。
此外,我们可能还有一组固定的巡检项目,例如 CPU、内存和磁盘。它们需要按照一定的顺序执行,但在当前程序设计中并不需要随意增删。
到这里,我们已经遇到了四种不同的数据组织需求:
- 保存有顺序、可能重复的服务器名称。
- 保存一组不允许重复的服务器名称。
- 根据服务器名称找到对应的 IP 地址。
- 保存一组结构固定的巡检项目。
Python 的四大内置集合类型,恰好可以帮助我们处理这些问题。
认识 Python 的四大内置集合类型
List:保存需要依次处理的服务器清单
先解决第一个问题:如何保存需要检查的服务器名称?
我们可以使用 Python 的列表,也就是 list。
1 | servers = ["web01", "web02", "db01", "web01"] |
运行结果:
1 | ['web01', 'web02', 'db01', 'web01'] |
可以看到,四个元素都被保存了下来,包括重复出现的 web01。
列表有两个很重要的特点:
- 有序: 元素按照列表中的位置排列,可以通过索引访问,也可以使用切片获取一部分元素。
- 可变: 程序运行过程中,可以添加、删除或修改元素。
例如,巡检过程中又增加了一台缓存服务器:
1 | servers.append("cache01") |
此时,cache01 就被添加到了列表末尾。
运行结果:
1 | ['web01', 'web02', 'db01', 'web01', 'cache01'] |
在实际运维工作中,我们可以使用列表保存待巡检服务器、待处理日志文件、需要执行的任务,以及其他需要按照一定顺序处理的数据。
记住:如果需要保存一组有顺序、允许重复、后续可能增删改的数据,可以优先考虑 list。
下一篇博客,我们就会专门学习列表的创建、索引、切片和动态修改等操作。
Tuple:保存结构固定的巡检信息
再来看另一个需求。
假设当前巡检脚本固定检查三个项目:
- CPU 使用情况
- 内存使用情况
- 磁盘空间
如果这些项目在当前设计中不需要直接增加、删除或替换,我们可以考虑使用元组,也就是 tuple。
1 | check_items = ("cpu", "memory", "disk") |
运行结果:
1 | ('cpu', 'memory', 'disk') |
乍一看,元组和列表很像,都是用来保存多个元素的,而且都可以通过索引访问元素。
它们的关键区别在于可变性。
列表创建之后,可以通过相应操作修改其中的元素;元组则不能修改自身的元素引用,也不能直接添加或删除元素。
例如,下面的操作会引发 TypeError:
1 | check_items = ("cpu", "memory", "disk") |
报错如下:
1 | Traceback (most recent call last): |
因为元组不支持通过索引替换元素。
需要注意,元组的不可变性并不意味着它内部引用的所有对象都不可变。如果元组中包含一个列表,该列表内部的元素仍然可以修改。
另外,元组通常比保存相同元素的列表占用更少的内存,但并不能笼统地认为元组在所有操作中都更快。
记住:如果需要保存有顺序、结构相对固定的数据,可以考虑 tuple。
后续其他文章我们将进一步学习元组的创建、访问、解包,以及它与列表的区别。
Set:处理重复出现的服务器名称
回到最开始的服务器清单:
1 | servers = ["web01", "web02", "db01", "web01"] |
如果我们的目标是获得不重复的服务器名称,该怎么办?
当然可以编写循环,逐个检查服务器名称是否已经出现过。不过,Python 已经提供了一种更直接的数据类型:集合,也就是 set。
1 | servers = ["web01", "web02", "db01", "web01"] |
运行结果可能类似于:
1 | {'web01', 'web02', 'db01'} |
重复的 web01 只保留了一份。
这里发生了什么?
set() 将列表转换为集合。由于集合中的元素不重复,重复的服务器名称就被合并掉了。
不过,这里有一个容易忽略的细节:集合不保证元素的迭代顺序,因此不能依赖它按照原始列表的顺序输出数据。
如果后续巡检任务必须按照指定顺序执行,就不能简单地把集合当成原列表的替代品。
除了去重,集合还特别适合比较两组数据。
例如,昨天巡检的服务器和今天巡检的服务器并不完全相同,我们可能需要找出新增的服务器,或者发现哪些服务器已经不在当前清单中。
这时,集合的交集、并集和差集等运算就能派上用场。
记住:如果需要去重、检查成员关系,或者比较两组数据,可以考虑 set。
后续我们将在其他文章中深入学习集合的创建、增删、成员测试,以及交集、并集和差集等操作。
Dict:根据服务器名称查找 IP 地址
现在,服务器清单已经有了,但巡检脚本还需要知道每台服务器的 IP 地址。
例如:
web01对应192.168.1.10web02对应192.168.1.11db01对应192.168.1.20
如果我们仍然使用一个列表保存所有名称,另一个列表保存所有 IP 地址,那么就必须确保两个列表中的元素始终一一对应。随着数据增多,维护起来很容易出错。
有没有更合适的方式?
这时候就可以使用字典,也就是 dict。
1 | server_ips = { |
字典使用键(key)和值(value)之间的映射关系来组织数据。
在这个例子中,服务器名称是键,IP 地址是值。
如果需要查找 db01 的 IP 地址,可以直接通过键访问:
1 | print(server_ips["db01"]) |
运行结果:
1 | 192.168.1.20 |
这样,我们就不需要手动遍历一组 IP 地址,再判断它属于哪台服务器了。
字典的键必须唯一,并且必须是可哈希的对象。字符串和整数通常可以作为键,而列表不能直接作为字典的键。
字典的值则可以重复,也可以保存不同类型的数据。
从 Python 3.7 开始,字典按照插入顺序迭代属于语言规范保证的行为。
在实际运维工作中,字典的用途非常广泛。例如:
- 根据主机名查找 IP 地址。
- 保存服务器的 CPU、内存和磁盘使用率。
- 保存应用程序的配置参数。
- 处理云平台 API 或 Kubernetes API 返回的结构化数据。
记住:如果需要通过一个标识查找对应的信息,可以优先考虑 dict。
我们在后续的文章中将深入学习字典的创建、查询、更新、删除和遍历等操作。
四大内置集合类型有什么区别?
经过前面的例子,我们已经认识了四种类型。
现在把它们放在一起,就更容易看出区别了。
| 特性 | list | tuple | set | dict |
|---|---|---|---|---|
| 中文名称 | 列表 | 元组 | 集合 | 字典 |
| 主要用途 | 保存有序数据 | 保存固定结构的数据 | 去重、集合运算 | 键值映射 |
| 是否保证顺序 | 是 | 是 | 否 | 是,按插入顺序 |
| 能否修改容器 | 可以 | 不可以 | 可以 | 可以 |
| 是否允许重复 | 允许 | 允许 | 元素不重复 | 键不重复 |
| 典型访问方式 | 索引、切片 | 索引、切片 | 成员测试 | 通过键查找 |
| 运维场景 | 服务器巡检清单 | 固定巡检项目 | 不重复的主机集合 | 主机名与 IP 的映射 |
这里还需要明确几个细节。
第一,列表和元组都属于序列类型。
它们可以通过整数索引访问元素,也支持切片操作。
第二,集合不保证迭代顺序。
如果业务需要保留输入顺序,就不能依赖集合的输出顺序。去重时应该根据具体需求选择合适的处理方式。
第三,字典的键必须唯一且可哈希。
字典可以通过键查找对应的值,但键并不是任意对象都能充当的。
第四,不可变并不代表一定更高效。
元组在某些情况下具有内存优势,但选择数据结构时,应优先考虑数据的特点和程序的实际需求。
实际编写程序时,应该如何选择?
看到这里,你可能会问:既然四种类型都可以保存数据,实际写程序时到底该选哪个?
其实不用一开始就记住所有规则。只要先想清楚自己需要解决什么问题,通常就能找到合适的类型。
场景一:我需要保存一组服务器,并按顺序巡检
选择 list。
1 | servers = ["web01", "web02", "db01"] |
列表能够保留服务器的排列顺序,也允许后续添加或删除服务器。
场景二:我需要保存一组固定的参数
可以考虑 tuple。
1 | server_endpoint = ("192.168.1.10", 8080) |
这里用元组保存服务器地址和端口。假设程序设计要求这两个字段在创建之后不通过元组本身修改,那么元组就是一种合适的选择。
场景三:我只关心有哪些不同的服务器
选择 set。
1 | servers = {"web01", "web02", "db01"} |
集合适合去重、判断某台服务器是否属于某个集合,以及比较不同来源的服务器清单。
场景四:我需要根据主机名查找 IP 地址
选择 dict。
1 | server_ips = { |
字典可以根据键快速找到对应的值,而列表通常需要从头开始,一个一个地检查元素。
例如,服务器只有 10 台时,在列表中查找某台服务器并不麻烦。但如果服务器增加到 1000 台,甚至 1 万台,就可能需要检查更多的元素才能找到目标。
字典和集合则不同。它们通常能够更快地定位目标,不需要像列表那样逐个检查。因此,当需要频繁查找数据时,使用字典或集合往往更加合适。
当然,这并不意味着字典和集合在任何情况下都更快。具体选择哪种数据类型,还要看我们需要存储什么数据,以及后续要如何使用这些数据。
归纳起来,可以先记住下面四条:
- 有序、可变: 优先考虑
list。 - 有序、容器自身不可变: 考虑
tuple。 - 元素不重复、集合运算: 优先考虑
set。 - 键值映射、按键查找: 优先考虑
dict。
当然,这些只是入门阶段的选型原则。随着学习深入,你还会接触更多数据结构,以及不同操作对程序性能的影响。
如何创建这些集合类型?
除了直接使用 []、()、{} 等语法,Python 还提供了对应的内置构造函数:
list()tuple()set()dict()
它们可以用于创建对应类型的对象,也可以在符合条件时将其他数据转换为相应类型。
例如:
1 | servers = ["web01", "web02", "web01"] |
输出:
1 | ('web01', 'web02', 'web01') |
这里,tuple(servers) 将列表转换为元组,保留原有元素及其顺序;set(servers) 则将列表转换为集合,去除重复元素,但不保证原有顺序。
还有一个容易踩的坑:创建空集合时,需要使用 set()。
1 | empty_set = set() |
为什么不使用 {} 创建空集合?
因为在 Python 中,空花括号 {} 创建的是空字典,而不是空集合。
这个细节看起来很小,但在刚接触 Python 时很容易弄混。
至于列表、元组、集合和字典还有哪些创建方式,以及不同操作的具体用法,我们会在后续文章中逐一展开。
写在最后
回到最开始的服务器巡检场景。
随着自动化需求不断增加,我们需要保存服务器清单、去除重复的主机名、查找 IP 地址,还需要组织固定的巡检项目。
这些需求看起来很普通,却对应着不同的数据组织方式。
Python 提供的 list、tuple、set 和 dict,正是为了帮助我们更方便地处理这些问题。
本篇我们先建立了整体认知,了解四种类型的特点、区别和适用场景。至于如何创建列表、怎样使用索引和切片、如何进行集合运算,以及字典为什么适合通过键查找数据,还需要通过后续的学习逐步掌握。
接下来,我们将从最常见的 list 入手,学习如何创建列表、访问元素、使用切片,以及在程序运行过程中动态修改列表。
掌握这些基础知识之后,再逐步学习 tuple、set 和 dict,最后通过综合示例,把它们应用到 Linux 运维脚本和实际自动化任务中。
学会一门编程语言,不只是记住语法,更重要的是知道面对实际问题时,应该选择什么工具,以及为什么这样选择。
