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 集合实战,搞定数据去重与集合运算

在上一篇文章中,我们学习了 Python 的 list,掌握了列表的创建、索引、切片、增删改、排序和循环遍历。

但是,在实际开发中,并不是所有数据都需要随时修改。

比如,一台服务器的 IP 地址和 SSH 端口、一组固定的状态码映射,或者巡检过程中返回的主机名与检查结果。如果这些数据在创建后不希望被意外修改,应该怎么办?

这时,我们就可以考虑使用 Python 的另一种内置序列类型:tuple,也就是元组。

本文继续以 Linux 运维场景为例,学习元组的创建、访问、切片、解包和不可变特性,并重点掌握 enumerate() 如何同时获取元素的索引和值。

从服务器配置信息认识 Tuple

假设你是一名 Linux 运维工程师,需要编写一个自动化脚本,保存某台服务器的基本连接信息。

例如,服务器名称为 web01,IP 地址为 192.168.1.10,SSH 端口为 22。

我们可以使用列表保存这些信息:

1
2
3
server_info = ["web01", "192.168.1.10", 22]

print(server_info)

运行结果:

1
['web01', '192.168.1.10', 22]

这样当然没有问题。

但是,仔细想一想:如果这组信息在程序运行过程中不需要修改,我们真的需要使用一个可以随意增删改的列表吗?

例如,某个地方不小心执行了下面的代码:

1
2
3
server_info[1] = "192.168.1.100"

print(server_info)

运行结果:

1
['web01', '192.168.1.100', 22]

原本的 IP 地址就被修改了。

如果这组数据代表的是固定的服务器配置信息,那么这种意外修改可能导致后续程序连接错误的服务器。

这时,我们就可以使用元组:

1
2
3
server_info = ("web01", "192.168.1.10", 22)

print(server_info)

运行结果:

1
('web01', '192.168.1.10', 22)

元组与列表一样,都可以保存多个元素,也支持索引、切片和循环遍历。

最大的区别在于:元组创建后,不能直接添加、删除或替换其中的元素。

例如:

1
2
3
server_info = ("web01", "192.168.1.10", 22)

server_info[1] = "192.168.1.100"

运行时会出现错误:

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-1>", line 3, in <module>
server_info[1] = "192.168.1.100"
~~~~~~~~~~~^^^
TypeError: 'tuple' object does not support item assignment

这就是元组的不可变特性。

当然,不可变并不意味着元组在所有场景下都比列表更好。选择哪种类型,仍然需要结合数据的用途来判断。

Tuple 的特点

理解元组之前,先记住它的几个核心特点。

  • 有序: 元素按照指定顺序排列,可以通过索引访问,也支持切片。
  • 不可变: 创建后,不能直接修改元组中的元素,也不能直接增删元素。
  • 允许重复: 同一个值可以在元组中出现多次。
  • 可以保存不同类型的数据: 例如字符串、整数、浮点数,甚至列表和字典。
  • 可以遍历: 支持 for 循环,也可以配合 enumerate() 使用。

例如:

1
2
3
4
server_info = ("web01", "192.168.1.10", 22, "web01")

print(server_info)
print(len(server_info))

运行结果:

1
2
('web01', '192.168.1.10', 22, 'web01')
4

可以看到,元组中允许重复出现 web01,并且 len() 可以获取元组中元素的数量。

那么,为什么需要使用不可变的元组,而不是直接使用列表呢?

为什么要使用 Tuple,而不是 List?

可以从几个方面理解。

避免意外修改。

对于创建后不应该改变的数据,使用元组可以避免直接通过索引替换元素,也不能直接调用列表的 append()、remove() 等修改方法。

例如,保存固定的服务端口配置:

1
2
3
service_ports = ("ssh", 22)

print(service_ports)

运行结果:

1
('ssh', 22)

这组信息可以被读取和传递,但不能直接修改其中的元素。

表达固定的数据结构。

例如,一次巡检返回的结果可以包含主机名、CPU 使用率和检查状态:

1
2
3
4
5
6
7
check_result = ("web01", 75.6, "OK")

hostname, cpu_usage, status = check_result

print(hostname)
print(cpu_usage)
print(status)

运行结果:

1
2
3
web01
75.6
OK

这里的三个元素分别代表主机名、CPU 使用率和检查状态。元组适合表达这种有固定位置含义的数据。

不过,如果字段很多,或者需要通过字段名称访问数据,那么字典或专门的数据类可能更合适。

作为某些需要不可变对象的场景中的数据载体。

例如,元组在满足条件时可以作为字典的键,也可以放入集合中。前提是元组中的所有元素都必须是可哈希的。

1
2
3
4
5
6
7
server_ports = {
("web01", 22): "SSH",
("web01", 80): "HTTP",
("web01", 443): "HTTPS"
}

print(server_ports[("web01", 22)])

运行结果:

1
SSH

这里使用 (服务器名称, 端口号) 作为字典的键,能够将两个值组合起来标识一项配置。

需要注意,包含列表等不可哈希对象的元组不能直接作为字典的键。

至于性能方面,元组有时会比列表占用更少的内存,但具体情况取决于数据和使用方式。不要仅仅因为元组可能更节省内存,就把所有列表都改成元组。

创建 Tuple

Python 提供了几种创建元组的方式。最常见的是使用圆括号,也可以通过 tuple() 构造函数创建。

使用圆括号创建元组

最直观的方式,就是使用一对圆括号 (),并使用逗号分隔其中的元素。

1
2
3
4
5
6
7
servers = ("web01", "web02", "db01")
ports = (22, 80, 443)
cpu_usage = (35.6, 72.4, 91.2)

print(servers)
print(ports)
print(cpu_usage)

运行结果:

1
2
3
('web01', 'web02', 'db01')
(22, 80, 443)
(35.6, 72.4, 91.2)

这三组数据分别保存服务器名称、端口号和 CPU 使用率。

与列表不同,元组通常使用圆括号表示,而列表使用方括号表示。

创建空元组

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

1
2
3
4
empty_tuple = ()

print(empty_tuple)
print(len(empty_tuple))

运行结果:

1
2
()
0

空元组中没有任何元素,因此长度为 0。

也可以使用 tuple() 创建空元组:

1
2
3
empty_tuple = tuple()

print(empty_tuple)

运行结果:

1
()

这两种写法都可以。

创建只有一个元素的元组

这里有一个 Python 初学者经常踩到的坑。

如果想创建只包含一个服务器名称的元组,下面的写法正确吗?

1
2
3
4
server = ("web01")

print(server)
print(type(server))

运行结果:

1
2
web01
<class 'str'>

你可能会发现,变量 server 并不是元组,而是字符串。

为什么?

因为在 Python 中,决定元组是否成立的关键是逗号,而不是圆括号。

正确的写法应该是:

1
2
3
4
server = ("web01",)

print(server)
print(type(server))

运行结果:

1
2
('web01',)
<class 'tuple'>

注意最后的逗号。

如果不使用圆括号,也可以创建元组:

1
2
3
4
server = "web01",

print(server)
print(type(server))

运行结果:

1
2
('web01',)
<class 'tuple'>

所以,如果需要创建只有一个元素的元组,一定要记住这个细节。

使用 tuple() 构造函数

除了使用圆括号,还可以使用 tuple() 将可迭代对象转换为元组。

例如,将列表转换为元组:

1
2
3
4
5
servers = ["web01", "web02", "db01"]

server_tuple = tuple(servers)

print(server_tuple)

运行结果:

1
('web01', 'web02', 'db01')

也可以将字符串转换为元组:

1
2
3
letters = tuple("Linux")

print(letters)

运行结果:

1
('L', 'i', 'n', 'u', 'x')

这里需要注意,tuple() 会从可迭代对象中逐个获取元素,而不是将整个对象作为单个元素放入元组。

例如:

1
2
3
numbers = tuple(range(1, 6))

print(numbers)

运行结果:

1
(1, 2, 3, 4, 5)

range(1, 6) 产生从 1 到 5 的整数序列,tuple() 则将这些整数收集到一个元组中。

直接使用逗号创建元组

在 Python 中,圆括号通常可以省略,只要元素之间使用逗号分隔即可。

1
2
3
4
server_info = "web01", "192.168.1.10", 22

print(server_info)
print(type(server_info))

运行结果:

1
2
('web01', '192.168.1.10', 22)
<class 'tuple'>

这种写法同样创建了元组。

不过,在实际编写教程和业务代码时,通常建议使用圆括号,让数据结构更加直观,方便阅读和维护。

通过索引访问 Tuple 元素

元组是有序序列,因此可以像列表一样通过索引访问元素。

例如:

1
2
3
4
5
server_info = ("web01", "192.168.1.10", 22)

print(server_info[0])
print(server_info[1])
print(server_info[2])

运行结果:

1
2
3
web01
192.168.1.10
22

元组的索引同样从 0 开始。

索引元素含义
0web01服务器名称
1192.168.1.10IP 地址
222SSH 端口

如果需要访问最后一个元素,也可以使用负数索引:

1
2
3
4
server_info = ("web01", "192.168.1.10", 22)

print(server_info[-1])
print(server_info[-2])

运行结果:

1
2
22
192.168.1.10

其中,-1 表示最后一个元素,-2 表示倒数第二个元素。

如果索引超出范围,同样会引发 IndexError。这一点与列表完全相同。

使用切片获取部分元素

除了访问单个元素,元组也支持切片。

切片的基本语法仍然是:

1
tuple[start:stop:step]

例如:

1
2
3
4
5
6
7
8
9
10
server_info = (
"web01",
"192.168.1.10",
22,
"production",
"running"
)

print(server_info[0:3])
print(server_info[3:])

运行结果:

1
2
('web01', '192.168.1.10', 22)
('production', 'running')

第一个切片取出索引 0 到 2 的元素,第二个切片则取出从索引 3 开始直到末尾的元素。

与列表一样,切片包含开始位置,但不包含结束位置。

元组切片的另一个特点是:切片会返回一个新的元组,而不是列表。

1
2
3
4
5
6
server_info = ("web01", "192.168.1.10", 22)

result = server_info[:2]

print(result)
print(type(result))

运行结果:

1
2
('web01', '192.168.1.10')
<class 'tuple'>

因此,如果需要从元组中提取一部分数据,切片仍然是非常方便的操作。

Tuple 的不可变特性

前面多次提到,元组创建后不能直接修改其中的元素。

现在,我们再具体看看哪些操作不允许执行,以及不可变特性需要注意什么。

不能直接修改元组中的元素

例如:

1
2
3
servers = ("web01", "web02", "db01")

servers[1] = "cache01"

运行时会引发 TypeError,因为元组不支持通过索引直接赋值。

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-101>", line 3, in <module>
servers[1] = "cache01"
~~~~~~~^^^
TypeError: 'tuple' object does not support item assignment

同样,元组也没有列表的 append()、extend()、remove() 和 clear() 等修改方法。

例如:

1
2
3
servers = ("web01", "web02", "db01")

servers.append("cache01")

运行时会引发 AttributeError,因为元组没有 append() 方法。

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-102>", line 3, in <module>
servers.append("cache01")
^^^^^^^^^^^^^^
AttributeError: 'tuple' object has no attribute 'append'. Did you mean to use a 'list' object?

如果业务确实需要修改数据,可以根据需求将元组转换为列表,完成修改后再转换回元组。

1
2
3
4
5
6
7
8
servers = ("web01", "web02", "db01")

server_list = list(servers)
server_list.append("cache01")

servers = tuple(server_list)

print(servers)

运行结果:

1
('web01', 'web02', 'db01', 'cache01')

需要注意,这并不是直接修改原元组,而是创建了新的列表,再将结果重新赋值给变量 servers。

元组不可变,不代表内部对象一定不可变

这个知识点很重要。

假设我们创建一个元组,其中包含一个列表:

1
2
3
server_info = ("web01", ["running", "healthy"])

print(server_info)

运行结果:

1
('web01', ['running', 'healthy'])

我们尝试修改元组中的第二个元素:

1
server_info[1] = ["stopped"]

这会引发 TypeError,因为不能替换元组中索引 1 对应的对象。

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-105>", line 1, in <module>
server_info[1] = ["stopped"]
~~~~~~~~~~~^^^
TypeError: 'tuple' object does not support item assignment

但是,如果我们修改这个列表对象本身呢?

1
2
3
4
5
server_info = ("web01", ["running", "healthy"])

server_info[1].append("monitored")

print(server_info)

运行结果:

1
('web01', ['running', 'healthy', 'monitored'])

这段代码却可以正常运行。

为什么?

因为元组中保存的是对列表对象的引用。我们没有替换元组中的元素,而是修改了这个列表对象内部的内容。

因此,元组的不可变特性约束的是元组自身包含的元素引用,而不是递归地冻结所有内部对象。

如果希望数据结构中的内容也不被意外修改,就不能简单地认为使用元组就足够了,还需要考虑内部对象的可变性。

Tuple 解包:一次获取多个变量

在前面的例子中,我们使用索引获取服务器名称、IP 地址和端口号。

例如:

1
2
3
4
5
6
7
server_info = ("web01", "192.168.1.10", 22)

hostname = server_info[0]
ip_address = server_info[1]
port = server_info[2]

print(hostname, ip_address, port)

运行结果:

1
web01 192.168.1.10 22

如果每次都需要通过索引取出数据,代码就会显得有些重复。

这时,可以使用解包。

1
2
3
4
5
6
7
server_info = ("web01", "192.168.1.10", 22)

hostname, ip_address, port = server_info

print(hostname)
print(ip_address)
print(port)

运行结果:

1
2
3
web01
192.168.1.10
22

Python 会按照位置,将元组中的元素依次赋值给左侧对应的变量。

普通解包要求左侧变量的数量与元组中的元素数量一致,否则就会引发 ValueError。

例如:

1
2
3
server_info = ("web01", "192.168.1.10", 22)

hostname, ip_address = server_info

运行时会报错,因为元组有三个元素,而左侧只有两个变量。

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-110>", line 3, in <module>
hostname, ip_address = server_info
^^^^^^^^^^^^^^^^^^^^
ValueError: too many values to unpack (expected 2, got 3)

如果只希望获取部分元素,也可以使用带星号的解包语法:

1
2
3
4
5
6
servers = ("web01", "web02", "db01", "cache01")

first, *others = servers

print(first)
print(others)

运行结果:

1
2
web01
['web02', 'db01', 'cache01']

这里的 first 保存第一个元素,而 others 保存剩余元素,并且是一个列表。

需要注意,即使原始数据是元组,带星号的解包变量也会得到列表。

解包在处理固定结构的数据时非常实用。例如,函数可以返回多个值,而调用方可以使用多个变量一次接收它们。

1
2
3
4
5
6
7
8
9
def get_server_info():
return "web01", "192.168.1.10", 22


hostname, ip_address, port = get_server_info()

print(hostname)
print(ip_address)
print(port)

运行结果:

1
2
3
web01
192.168.1.10
22

这里函数返回的多个值实际上会被打包成一个元组,再由调用方解包。

使用 enumerate() 同时获取索引和值

接下来,学习一个非常实用的内置函数:enumerate()。

假设我们有一组服务器名称,需要逐台输出它们的编号。

1
2
3
4
servers = ("web01", "web02", "db01", "cache01")

for server in servers:
print(server)

运行结果:

1
2
3
4
web01
web02
db01
cache01

这种写法可以遍历所有服务器,但如果还需要知道当前服务器是第几台,就需要额外处理索引。

一种方式是使用 range() 和 len():

1
2
3
4
servers = ("web01", "web02", "db01", "cache01")

for index in range(len(servers)):
print(index, servers[index])

运行结果:

1
2
3
4
0 web01
1 web02
2 db01
3 cache01

这种写法可以正常工作,但如果只是为了同时获取索引和值,还有更简洁的方式:enumerate()。

直接使用 enumerate()

1
2
3
4
servers = ("web01", "web02", "db01", "cache01")

for item in enumerate(servers):
print(item)

运行结果:

1
2
3
4
(0, 'web01')
(1, 'web02')
(2, 'db01')
(3, 'cache01')

可以看到,enumerate() 会在遍历过程中,为每个元素配上一个计数值。

默认情况下,计数从 0 开始。

每次循环,enumerate() 都会产生一个包含索引和值的二元组,例如 (0, 'web01')。

这也正好说明了为什么理解元组很重要:enumerate() 返回的每一项,就是一个包含两个元素的元组。

使用解包获取索引和值

既然每次循环得到的是一个元组,我们就可以直接使用解包,同时获取索引和值。

1
2
3
4
servers = ("web01", "web02", "db01", "cache01")

for index, server in enumerate(servers):
print(index, ":", server)

运行结果:

1
2
3
4
0 : web01
1 : web02
2 : db01
3 : cache01

这里的 index 接收索引,server 接收对应的服务器名称。

与下面这种写法相比:

1
2
for index in range(len(servers)):
print(index, ":", servers[index])

enumerate() 通常更直观,也不需要手动通过索引再次访问元素。

因此,当你需要同时获取元素的索引和值时,可以优先考虑 enumerate()。

使用 start 参数指定起始编号

默认索引从 0 开始,但在输出巡检报告时,我们通常更习惯从第 1 台服务器开始编号。

这时可以使用 start 参数:

1
2
3
4
servers = ("web01", "web02", "db01", "cache01")

for index, server in enumerate(servers, start=1):
print(f"第 {index} 台服务器:{server}")

运行结果:

1
2
3
4
第 1 台服务器:web01
第 2 台服务器:web02
第 3 台服务器:db01
第 4 台服务器:cache01

需要注意,start=1 只会改变 enumerate() 提供的计数起点,并不会修改元组本身的索引。

也就是说,元组中 web01 的实际索引仍然是 0,只是输出编号从 1 开始。

同时遍历两组关联数据

假设我们有一组成绩等级,以及每个等级对应的分数范围。

1
2
3
4
5
grades = ("A", "B", "C", "D", "F")
points = ("90-100", "80-89", "70-79", "60-69", "00-59")

for index, grade in enumerate(grades):
print(grade, ":", points[index])

运行结果:

1
2
3
4
5
A : 90-100
B : 80-89
C : 70-79
D : 60-69
F : 00-59

这里通过 enumerate() 获取等级的索引,再用相同索引访问分数范围。

这种写法能够运行,但它依赖两组元组的元素数量和排列顺序保持一致。如果两组数据长度不一致,或者顺序发生变化,就容易产生问题。

如果只是希望将两组数据按位置配对,可以使用 zip():

1
2
3
4
5
grades = ("A", "B", "C", "D", "F")
points = ("90-100", "80-89", "70-79", "60-69", "00-59")

for grade, point in zip(grades, points):
print(grade, ":", point)

运行结果:

1
2
3
4
5
A : 90-100
B : 80-89
C : 70-79
D : 60-69
F : 00-59

zip() 会将多个可迭代对象中相同位置的元素配对。默认情况下,当其中一个对象耗尽时,遍历就会结束。

如果希望同时获得配对后的编号和值,也可以组合使用:

1
2
3
4
5
grades = ("A", "B", "C", "D", "F")
points = ("90-100", "80-89", "70-79", "60-69", "00-59")

for index, (grade, point) in enumerate(zip(grades, points), start=1):
print(f"第 {index} 档:{grade},分数范围:{point}")

运行结果:

1
2
3
4
5
第 1 档:A,分数范围:90-100
第 2 档:B,分数范围:80-89
第 3 档:C,分数范围:70-79
第 4 档:D,分数范围:60-69
第 5 档:F,分数范围:00-59

这个例子把前面学到的几个知识点串联了起来:元组、循环遍历、解包、enumerate() 和 zip()。

Tuple 与 List 如何选择?

学习完元组之后,一个常见的问题就是:既然列表和元组都能保存多个元素,什么时候应该选择哪一种?

可以参考下面的对比。

对比项List 列表Tuple 元组
表示方式[](),或使用逗号构造
是否有序有序有序
是否允许重复允许允许
是否可变可变不可变
是否支持索引支持支持
是否支持切片支持支持
是否支持循环遍历支持支持
常见场景动态任务清单、待处理数据固定记录、多个返回值、不可变键

例如,服务器巡检清单通常会随着业务变化而增加或删除服务器,这时使用列表就比较合适:

1
2
3
4
5
servers = ["web01", "web02", "db01"]

servers.append("cache01")

print(servers)

运行结果:

1
['web01', 'web02', 'db01', 'cache01']

而对于一次巡检过程中形成的固定记录,例如主机名、IP 地址和端口号,则可以考虑使用元组:

1
2
3
4
5
server_info = ("web01", "192.168.1.10", 22)

hostname, ip_address, port = server_info

print(hostname, ip_address, port)

运行结果:

1
web01 192.168.1.10 22

当然,这只是常见的选择方式,并不是硬性规定。元组中的元素可以是可变对象,列表也可以被程序约定为不修改。真正需要关注的是数据的语义、是否需要修改,以及后续如何使用这些数据。

综合实战:生成服务器巡检报告

现在,我们将元组、解包和 enumerate() 组合起来,模拟一个简单的服务器巡检报告。

假设巡检程序已经获得几台服务器的检查结果,每条记录包含主机名、CPU 使用率和检查状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3

check_results = (
("web01", 35.6, "OK"),
("web02", 82.4, "WARNING"),
("db01", 61.2, "OK"),
("cache01", 91.8, "WARNING"),
)

print("服务器巡检报告")
print("-" * 45)

for index, (hostname, cpu_usage, status) in enumerate(check_results, start=1):
print(
f"第 {index} 台服务器:{hostname},"
f"CPU 使用率:{cpu_usage}%,状态:{status}"
)

运行结果:

1
2
3
4
5
6
服务器巡检报告
---------------------------------------------
第 1 台服务器:web01,CPU 使用率:35.6%,状态:OK
第 2 台服务器:web02,CPU 使用率:82.4%,状态:WARNING
第 3 台服务器:db01,CPU 使用率:61.2%,状态:OK
第 4 台服务器:cache01,CPU 使用率:91.8%,状态:WARNING

这个示例中有几个值得注意的地方。

  • check_results 是一个元组,其中的每个元素又是一个元组。
  • 内层元组保存一台服务器的固定记录,包括主机名、CPU 使用率和检查状态。
  • enumerate(check_results, start=1) 为每条记录提供编号。
  • for index, (hostname, cpu_usage, status) 使用嵌套解包,一次获取编号和记录中的三个字段。

需要注意,实际巡检时,CPU 使用率和状态通常由程序动态采集,这里只是用预先准备的数据演示如何组织和遍历记录。

当数据量增加时,这种结构能够让代码保持清晰。不过,如果记录字段越来越多,或者需要通过字段名称访问数据,字典、数据类等结构可能更加合适。

常见 Tuple 操作速查

操作作用示例
()创建空元组items = ()
(x,)创建单元素元组items = ("web01",)
tuple(iterable)将可迭代对象转换为元组tuple([1, 2, 3])
items[i]通过索引访问元素items[0]
items[-1]获取最后一个元素items[-1]
items[start:stop]获取切片items[:2]
x in items判断元素是否存在"web01" in items
items.count(x)统计元素出现次数items.count("web01")
items.index(x)查找第一个匹配元素的索引items.index("web01")
len(items)获取元组长度len(items)
a, b = items解包元组hostname, port = items
enumerate(items)遍历时同时获取计数和值enumerate(items)
zip(a, b)将多个可迭代对象按位置配对zip(grades, points)

还需要记住几个常见规则:

  • 元组有序、允许重复,但不能直接替换、添加或删除其中的元素。
  • 创建单元素元组时,元素后面的逗号不能省略。
  • 元组切片会返回新的元组。
  • 元组不可变,不代表其内部包含的列表或字典也不可变。
  • enumerate() 每次迭代提供一个包含计数和值的元组。
  • 如果只需要同时获取索引和值,优先考虑 enumerate();如果需要配对遍历多个序列,可以考虑 zip()。

写在最后

回到最开始的问题:为什么需要使用 Tuple,而不是直接使用 List?

答案并不是元组在所有情况下都更好,而是不同的数据结构适合不同的需求。

如果数据需要频繁添加、删除或修改,列表通常更加方便;如果需要表达一组有顺序、创建后不应直接修改的数据,元组则是一个不错的选择。

通过这篇文章,我们学习了元组的创建、索引、切片、不可变特性和解包,也掌握了 enumerate() 如何在遍历时同时提供索引和值,并通过服务器巡检报告将这些知识点串联起来。

下一篇文章,我们将继续学习 Python 的 set,看看如何使用集合去除重复元素、进行成员判断,以及利用交集、并集和差集解决实际的数据处理问题。

当你逐渐理解 list、tuple 和 set 的区别之后,就能在编写 Python 自动化脚本时,根据数据的特点选择合适的类型,而不是只知道把所有数据都放进列表。