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() 遍历

在上一篇文章中,我们认识了 Python 的四大内置集合类型:list、tuple、set 和 dict,了解了它们各自的特点和适用场景。

那么如何快速创建列表?如何通过索引和切片访问元素?如何动态添加、删除和修改数据?又该如何遍历列表,批量处理服务器巡检任务?

这篇文章,我们就从最常用的 list 入手,深入学习列表的创建、访问、切片、增删改、排序、解包和循环遍历。

还是以 Linux 运维工作为例,看看这些基础语法如何帮助我们编写更实用的自动化脚本。

从服务器清单认识 List

假设你是一名 Linux 运维工程师,需要每天检查公司的服务器运行状态。

刚开始,公司只有几台服务器,你可以直接登录服务器,执行 uptime、free -h 和 df -h,查看系统负载、内存和磁盘空间。

但是,随着业务发展,服务器数量从几台增加到几十台,甚至上百台。每天手动登录服务器进行检查,显然不太现实。

这时,我们就可以考虑使用 Python 编写一个巡检脚本,把需要检查的服务器保存起来,再通过循环逐台处理。

例如:

1
2
3
4
5
#!/usr/bin/env python3

servers = ["web01", "web02", "db01", "cache01"]

print(servers)

运行结果:

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

这里的 servers 就是一个列表,也就是 list。

列表是一种有序、可变的序列,可以保存零个或多个对象。列表中的元素可以是字符串、整数、浮点数,甚至还可以是其他列表。

列表有两个非常重要的特点:

  • 有序: 元素按照一定的顺序排列,可以通过索引访问,也支持切片操作。
  • 可变: 创建列表之后,可以根据需要添加、删除或修改其中的元素。

此外,列表允许出现重复元素。

例如:

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

print(servers)

运行结果:

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

两个 web01 都被保留了下来。

因此,如果需要保存一组有顺序、允许重复、后续可能发生变化的数据,列表通常是一个很合适的选择。

创建列表

Python 提供了多种创建列表的方式。根据数据来源和实际需求,我们可以选择最合适的写法。

使用方括号创建列表

最常见的方式,就是使用一对方括号 [],将需要保存的元素放在其中,并使用逗号分隔。

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]

可以看到,列表中的元素不一定需要是同一种类型。

例如,我们可以将服务器名称、IP 地址和端口号放在同一个列表中:

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

print(server_info)

运行结果:

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

虽然 Python 允许这样做,但在实际编写程序时,仍然应该根据数据的含义合理组织信息。如果需要保存具有明确字段关系的数据,后续还可以考虑使用元组或字典。

创建空列表

有时,我们暂时不知道列表中应该保存哪些数据,需要先创建一个空列表,等程序运行过程中再逐步添加元素。

这时可以使用两种方式:

1
2
3
4
5
servers = []
servers2 = list()

print(servers)
print(servers2)

运行结果:

1
2
[]
[]

两种方式都可以创建空列表。

例如,我们可以先创建一个空的巡检结果列表,之后再把每台服务器的检查结果添加进去:

1
2
3
4
5
6
7
check_results = []

check_results.append("web01: OK")
check_results.append("web02: OK")
check_results.append("db01: WARNING")

print(check_results)

运行结果:

1
['web01: OK', 'web02: OK', 'db01: WARNING']

这种写法在自动化脚本中非常常见:先准备一个容器,再随着程序执行逐步收集结果。

使用 list() 构造函数

除了直接使用方括号,我们还可以使用 list() 创建列表。

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

print(servers)

运行结果:

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

这里的 list() 接收了一个元组,并将其中的元素转换为列表。

它也可以将字符串转换为由单个字符组成的列表:

1
2
3
letters = list("Linux")

print(letters)

运行结果:

1
['L', 'i', 'n', 'u', 'x']

需要注意,list() 并不是简单地把任意对象放进列表,而是会从可迭代对象中逐个获取元素。

例如:

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

print(numbers)

运行结果:

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

这里的 range(1, 6) 生成从 1 到 5 的整数序列,再由 list() 将这些整数保存为列表。

后面学习循环时,我们还会经常用到 range()。

通过索引访问列表元素

列表创建完成后,接下来就需要访问其中的数据。

例如,巡检脚本需要知道第一台服务器的名称,或者获取最后一台服务器的名称。这时可以使用索引。

Python 列表的索引从 0 开始,而不是从 1 开始。

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

print(servers[0])
print(servers[1])
print(servers[2])
print(servers[3])

运行结果:

1
2
3
4
web01
web02
db01
cache01

对应关系如下:

索引元素
0web01
1web02
2db01
3cache01

为什么索引从 0 开始?

这是 Python 等许多编程语言采用的索引规则。刚开始可能需要适应,但理解之后就很容易掌握。

使用负数索引

如果想访问列表最后一个元素,难道每次都要先计算列表的长度吗?

其实不需要。Python 支持负数索引,可以从列表末尾向前访问元素。

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

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

运行结果:

1
2
cache01
db01

可以这样理解:

  • servers[0]:第一个元素。
  • servers[1]:第二个元素。
  • servers[-1]:最后一个元素。
  • servers[-2]:倒数第二个元素。

在实际工作中,如果我们只关心最近一次巡检结果,或者需要获取列表末尾的数据,负数索引就很方便。

索引越界会发生什么?

假设列表中只有四台服务器:

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

print(servers[4])

运行时会出现错误:

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-29>", line 3, in <module>
print(servers[4])
~~~~~~~^^^
IndexError: list index out of range

为什么会报错?

因为列表的有效索引是 0 到 3,而不是 0 到 4。

在编写自动化脚本时,如果索引来自用户输入,或者来自动态计算的结果,就需要注意检查索引范围,避免程序因为访问不存在的位置而中断。

使用切片获取部分元素

索引可以获取单个元素,但如果我们希望一次取出多台服务器,该怎么办?

例如,有一个包含十台服务器的列表,现在只想获取前五台,或者获取其中的第二台到第四台。

这时就可以使用切片。

切片的基本语法是:

1
list[start:stop:step]

其中:

  • start:切片开始的位置,包含该位置。
  • stop:切片结束的位置,但不包含该位置。
  • step:每次跨过多少个位置,默认值为 1。

先看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
servers = [
"web01",
"web02",
"web03",
"db01",
"db02",
"cache01"
]

print(servers[0:3])
print(servers[2:5])

运行结果:

1
2
['web01', 'web02', 'web03']
['web03', 'db01', 'db02']

第一个切片 servers[0:3] 从索引 0 开始,到索引 3 之前结束,因此取出前三个元素。

第二个切片 servers[2:5] 则取出索引 2、3 和 4 对应的元素。

这里有一个非常重要的规则:切片包含开始位置,但不包含结束位置。

记住这一点,后续使用切片就不容易出错。

省略切片的开始或结束位置

如果希望从列表开头一直取到指定位置,可以省略开始索引。

1
2
3
servers = ["web01", "web02", "web03", "db01", "db02"]

print(servers[:3])

运行结果:

1
['web01', 'web02', 'web03']

如果希望从某个位置一直取到列表末尾,则可以省略结束索引。

1
print(servers[2:])

运行结果:

1
['web03', 'db01', 'db02']

如果两个位置都省略:

1
print(servers[:])

就会得到包含全部元素的一个新列表。

输出

1
['web01', 'web02', 'web03', 'db01', 'db02']

这在需要复制列表时也很常见,不过需要注意:这种切片操作创建的是浅拷贝。如果列表中包含其他可变对象,内部对象仍然可能被多个列表共同引用。

使用步长进行切片

切片还支持第三个参数 step,用于指定每次取元素时跨过多少个位置。

例如:

1
2
3
4
numbers = [10, 20, 30, 40, 50, 60, 70, 80]

print(numbers[::2])
print(numbers[1::2])

运行结果:

1
2
[10, 30, 50, 70]
[20, 40, 60, 80]

numbers[::2] 表示从头开始,每隔一个元素取一次。

numbers[1::2] 则从索引 1 开始,同样每隔一个元素取一次。

步长还可以是负数:

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

print(numbers[::-1])

运行结果:

1
[50, 40, 30, 20, 10]

这是一种常见的列表反向取值方式。

需要注意,[::-1] 返回的是一个新列表,并不会直接修改原列表。

在运维场景中,切片可以用于拆分批次任务。例如,一次需要处理一百台服务器,但每批只希望处理十台,就可以使用切片按批次提取服务器清单。

列表的动态操作

列表最大的特点之一,就是创建之后仍然可以修改。

随着服务器数量变化,我们可能需要添加新的服务器、删除下线的服务器、修改已有信息,或者重新调整服务器的排列顺序。

Python 提供了多种列表操作方法。

使用 append() 添加元素

如果巡检范围增加了一台服务器,我们可以使用 append() 将它添加到列表末尾。

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

servers.append("cache01")

print(servers)

运行结果:

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

append() 每次添加一个对象,并将这个对象作为一个整体放入列表。

例如:

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

servers.append(["db01", "cache01"])

print(servers)

运行结果:

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

注意,最后一个元素本身是一个列表,而不是两个独立的服务器名称。

如果希望把另一个列表中的元素逐个添加进来,就应该使用 extend()。

使用 extend() 扩展列表

假设现有服务器清单保存在一个列表中,新增服务器则保存在另一个列表中。

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

servers.extend(new_servers)

print(servers)

运行结果:

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

extend() 会遍历传入的可迭代对象,并把其中的元素逐个添加到原列表。

这与 append() 有明显区别:

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

servers.append(new_servers)
print(servers)

结果是:

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

如果把 append() 换成 extend(),结果就会变成一个包含四个字符串的列表。

因此可以记住:

  • append():添加一个对象。
  • extend():把另一个可迭代对象中的元素逐个添加进来。

使用 insert() 在指定位置插入元素

有时,新服务器需要插入列表的指定位置,而不是直接追加到末尾。

例如:

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

servers.insert(1, "web02")

print(servers)

运行结果:

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

insert(1, "web02") 表示将 web02 插入索引 1 的位置,原来位于该位置及之后的元素会向后移动。

在需要维护特定处理顺序的任务清单中,可以使用 insert() 将任务插入指定位置。

不过,如果经常在列表中间插入大量元素,可能会带来额外的移动开销。对于这类场景,应该根据实际需求选择合适的数据结构。

使用 pop() 删除元素

如果一台服务器已经下线,我们可能需要将它从巡检清单中移除。

pop() 可以删除并返回指定位置的元素。

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

removed_server = servers.pop(2)

print("删除的服务器:", removed_server)
print("剩余服务器:", servers)

运行结果:

1
2
删除的服务器: db01
剩余服务器: ['web01', 'web02', 'cache01']

如果不指定索引,pop() 默认删除并返回最后一个元素。

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

server = servers.pop()

print(server)
print(servers)

运行结果:

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

需要注意,pop() 会直接修改原列表。如果列表为空,或者指定的索引不存在,就会引发 IndexError。

使用 remove() 删除指定元素

如果我们知道需要删除的服务器名称,也可以使用 remove()。

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

servers.remove("web02")

print(servers)

运行结果:

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

这里需要特别注意:如果列表中存在多个相同元素,remove() 只会删除第一个匹配的元素。

如果列表中不存在指定元素,则会引发 ValueError。

因此,在删除元素之前,如果不能确定目标一定存在,可以先进行成员测试:

1
2
if "web02" in servers:
servers.remove("web02")

这种写法能够避免因为目标元素不存在而直接报错。

使用 del 删除元素或切片

除了 pop() 和 remove(),Python 还提供了 del 语句。

例如:

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

del servers[1]

print(servers)

运行结果:

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

del servers[1] 删除索引 1 对应的元素。

如果希望删除一段范围,也可以使用切片:

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

del servers[1:3]

print(servers)

运行结果:

1
['web01', 'db01', 'db02']

这里删除的是索引 1 和 2 对应的元素,不包含结束索引 3。

如果希望清空列表中的所有元素,还可以使用 clear():

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

servers.clear()

print(servers)

运行结果:

1
[]

需要区分的是,clear() 会清空原列表,而 del servers 则会删除变量 servers 这个名称的绑定。两者并不是一回事。

修改列表中的元素

列表不仅可以增删元素,还可以通过索引直接修改指定位置的值。

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

servers[1] = "web03"

print(servers)

运行结果:

1
['web01', 'web03', 'db01']

原来的 web02 被替换成了 web03。

也可以通过切片一次修改多个元素:

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

servers[1:3] = ["web03", "db02"]

print(servers)

运行结果:

1
['web01', 'web03', 'db02', 'cache01']

切片赋值可以替换一段元素,甚至可以改变列表长度。因此,使用时需要特别注意切片范围和替换内容。

列表的查找与判断

在巡检脚本中,我们经常需要判断某台服务器是否存在于当前清单中,或者查找某个元素所在的位置。

Python 提供了 in、not in、index() 和 count() 等常用操作。

使用 in 判断元素是否存在

例如,我们希望在执行巡检之前,先确认某台服务器是否已经被添加到清单中。

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

print("web01" in servers)
print("cache01" in servers)
print("cache01" not in servers)

运行结果:

1
2
3
True
False
True

in 用于判断元素是否存在于列表中,not in 则用于判断元素是否不存在。

这类判断通常会返回布尔值,也就是 True 或 False。

例如:

1
2
3
4
if "web01" in servers:
print("web01 已加入巡检清单")
else:
print("web01 不在巡检清单中")

在实际脚本中,可以根据判断结果决定是否执行后续操作。

使用 index() 查找元素位置

如果不仅需要判断元素是否存在,还希望知道它位于哪个位置,可以使用 index()。

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

print(servers.index("db01"))

运行结果:

1
2

因为 db01 位于索引 2 的位置。

如果列表中存在重复元素,index() 默认返回第一个匹配元素的索引。

如果目标元素不存在,则会引发 ValueError,因此需要根据实际情况判断是否应该先检查元素是否存在。

使用 count() 统计元素出现次数

如果我们怀疑服务器清单中存在重复数据,可以使用 count() 统计某个元素出现了多少次。

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

print(servers.count("web01"))
print(servers.count("db01"))

运行结果:

1
2
2
1

这里,web01 出现了两次,而 db01 只出现了一次。

count() 适合统计指定元素在列表中的出现次数,但如果需要处理大量数据并频繁统计不同元素的数量,就应该考虑其他更合适的数据组织方式,例如字典或集合。

列表的反转与排序

服务器清单有时需要按照特定顺序排列。例如,按照名称排序,或者将处理顺序反转。

Python 提供了 reverse() 和 sort() 两种常用方法。

使用 reverse() 反转列表

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

servers.reverse()

print(servers)

运行结果:

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

reverse() 会直接修改原列表,将元素的排列顺序反转。

需要注意,它并不是按照字母顺序重新排序,而只是将现有顺序倒过来。

如果原列表是:

1
['web03', 'web01', 'web02']

反转后就会变成:

1
['web02', 'web01', 'web03']

它不会自动变成按名称升序排列的结果。

使用 sort() 排序列表

如果希望按照服务器名称排序,可以使用 sort()。

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

servers.sort()

print(servers)

运行结果:

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

默认情况下,字符串按照字典序进行比较,整数则按照数值大小进行排序。

例如:

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

numbers.sort()

print(numbers)

运行结果:

1
[3, 5, 10, 20, 100]

如果希望按照降序排列,可以使用 reverse=True:

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

numbers.sort(reverse=True)

print(numbers)

运行结果:

1
[100, 20, 10, 5, 3]

使用 sorted() 保留原列表

除了 sort(),Python 还提供了内置函数 sorted()。

两者的区别在于:

  • sort():直接修改原列表。
  • sorted():返回一个新的排序结果,不会直接修改原列表。

例如:

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

sorted_servers = sorted(servers)

print("原列表:", servers)
print("排序后:", sorted_servers)

运行结果:

1
2
原列表: ['web03', 'web01', 'web02']
排序后: ['web01', 'web02', 'web03']

如果后续还需要保留原始顺序,使用 sorted() 会更加方便。

还有一个容易踩的坑:sort() 和 reverse() 都是直接修改列表的方法,调用它们不会返回排序后的列表或反转后的列表。

例如,下面的写法是不正确的:

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

servers = servers.sort()

print(servers)

输出结果是:

1
None

这是因为 sort() 的返回值是 None,而不是排序后的列表。

正确的写法应该是:

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

servers.sort()

print(servers)

输出

1
['web01', 'web02', 'web03']

列表解包:把元素分配给不同变量

前面我们通过索引访问列表元素。

例如:

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 会按照位置,将列表中的元素依次赋值给对应的变量。

需要注意,普通解包要求变量数量与元素数量一致。

例如:

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

first, second = servers

这段代码会引发 ValueError,因为列表有三个元素,但左侧只有两个变量。

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-68>", line 3, in <module>
first, second = servers
^^^^^^^^^^^^^
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 则接收剩余元素,并且仍然是一个列表。

解包常用于处理字段数量固定的数据,例如服务器地址与端口、命令执行结果中的多个返回值等。不过,如果数据具有明确的字段名称,而且字段较多,字典往往更容易维护。

使用 for 循环遍历列表

列表最常见的用途之一,就是保存一组需要依次处理的数据。

在 Linux 运维自动化中,我们可能需要逐台检查服务器,或者逐个处理日志文件。

这时就可以使用 for 循环遍历列表。

直接遍历元素

例如:

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

for server in servers:
print(server)

运行结果:

1
2
3
4
web01
web02
db01
cache01

这里的 for server in servers 表示依次从 servers 中取出元素,并将当前元素赋值给变量 server。

每次循环,server 都会指向当前处理的服务器名称。

这种方式简单直观,也是 Python 中推荐优先使用的遍历方式之一。

例如,我们可以用类似的结构组织巡检任务:

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

for server in servers:
print(f"正在检查服务器:{server}")

运行结果:

1
2
3
正在检查服务器:web01
正在检查服务器:web02
正在检查服务器:db01

这里还没有真正连接服务器,但循环结构已经具备了批量处理任务的基本形式。后续学习 SSH 自动化或调用 API 时,就可以在循环内部执行实际的检查逻辑。

使用索引遍历列表

如果除了元素本身,还需要知道它在列表中的位置,也可以使用索引进行遍历。

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

这里涉及两个常见的内置函数:

  • len(servers):返回列表的元素数量。
  • range(len(servers)):生成从 0 到列表长度减 1 的整数序列。

通过这些索引,我们就可以逐个访问列表中的元素。

不过,如果只是遍历元素,通常直接写 for server in servers 就足够了。

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

如果既需要索引,又需要元素本身,可以使用 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

enumerate() 会在遍历过程中,同时提供元素的索引和值。

如果希望编号从 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

在输出巡检报告、生成带编号的任务清单时,这种写法非常实用。

在遍历过程中修改列表,需要注意什么?

列表是可变的,这意味着我们可以在程序运行过程中修改它。

不过,在使用 for 循环遍历列表时,如果一边遍历,一边直接添加或删除元素,可能会导致部分元素被跳过,或者程序行为与预期不一致。

例如:

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

for server in servers:
if server == "web02":
servers.remove(server)

print(servers)

输出

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

这样的代码虽然可能运行,但直接在遍历同一个列表时删除元素,很容易造成逻辑问题。

如果确实需要筛选数据,通常可以创建一个新列表保存符合条件的元素:

1
2
3
4
5
6
7
8
servers = ["web01", "web02", "db01", "cache01"]

remaining_servers = [
server for server in servers
if server != "web02"
]

print(remaining_servers)

运行结果:

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

这里使用的是列表推导式。它可以根据条件创建一个新的列表,后续我们还可以专门学习这种简洁的写法。

对于初学者来说,先记住一个原则:如果需要在遍历过程中筛选元素,优先考虑创建新列表,而不是直接修改正在遍历的列表。

综合实战:整理服务器巡检清单

前面介绍了列表的创建、访问、切片、增删改、查找、排序、解包和循环遍历。

现在,我们把这些知识点组合起来,完成一个简单的服务器清单整理示例。

假设公司有一份初始巡检清单,其中包含重复的服务器名称。我们需要添加一台新服务器、移除一台已经下线的服务器,再按照名称排序,最后输出巡检任务列表。

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

# 初始服务器清单
servers = ["web03", "web01", "db01", "web02", "web01"]

print("初始清单:", servers)

# 添加一台新服务器
servers.append("cache01")

# 移除一台已经下线的服务器
if "db01" in servers:
servers.remove("db01")

# 按名称排序
servers.sort()

print("整理后的清单:", servers)

# 生成带编号的巡检任务
for index, server in enumerate(servers, start=1):
print(f"第 {index} 台服务器:{server}")

运行结果:

1
2
3
4
5
6
7
初始清单: ['web03', 'web01', 'db01', 'web02', 'web01']
整理后的清单: ['cache01', 'web01', 'web01', 'web02', 'web03']
第 1 台服务器:cache01
第 2 台服务器:web01
第 3 台服务器:web01
第 4 台服务器:web02
第 5 台服务器:web03

从结果可以看出,列表中的重复元素仍然存在。sort() 只负责排序,并不会自动去重。

如果业务要求每台服务器只巡检一次,就需要额外处理重复数据。例如,可以使用集合去重,但集合不保证保留原始顺序。如果既需要去重,又需要保留服务器第一次出现的顺序,可以使用:

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

unique_servers = list(dict.fromkeys(servers))

print(unique_servers)

运行结果:

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

从 Python 3.7 开始,字典保证按照插入顺序迭代,因此这里可以借助字典的键唯一性,在去重的同时保留元素第一次出现的顺序。

当然,实际的服务器巡检系统还需要考虑 SSH 连接、身份认证、超时处理、异常捕获和巡检结果记录等问题。这里我们先把重点放在列表操作上,为后续编写更完整的自动化脚本打好基础。

常见列表操作速查

学完上面的内容,我们把常用方法整理在一起,方便后续查阅。

操作作用示例
append(x)在末尾添加一个对象servers.append("web01")
extend(iterable)将可迭代对象中的元素逐个添加servers.extend(new_servers)
insert(i, x)在指定位置插入元素servers.insert(1, "web02")
pop()删除并返回最后一个元素servers.pop()
pop(i)删除并返回指定位置的元素servers.pop(0)
remove(x)删除第一个匹配的元素servers.remove("web01")
clear()清空列表servers.clear()
index(x)查找第一个匹配元素的索引servers.index("web01")
count(x)统计元素出现次数servers.count("web01")
reverse()原地反转列表servers.reverse()
sort()原地排序列表servers.sort()
sorted(list)返回新的排序列表sorted(servers)
len(list)获取元素数量len(servers)

另外,还需要记住几个常见的操作规则:

  • 使用索引访问元素时,索引从 0 开始。
  • 切片包含开始位置,但不包含结束位置。
  • append() 添加一个对象,extend() 添加多个元素。
  • remove() 删除第一个匹配项,pop() 删除并返回指定位置的元素。
  • sort() 和 reverse() 会直接修改原列表,不会返回修改后的列表。
  • 如果需要同时获取索引和值,可以优先考虑 enumerate()。

写在最后

回到最开始的服务器巡检场景。

当服务器数量从几台增加到几十台、上百台时,我们需要的不只是一个保存服务器名称的地方,还需要能够灵活地添加服务器、移除下线节点、调整处理顺序,并逐个执行巡检任务。

Python 的 list 正好提供了这些基础能力。

通过这篇文章,我们学习了列表的创建、索引、切片、动态增删改、成员测试、排序、解包和循环遍历,也通过服务器清单示例把这些操作串联起来。

需要注意的是,列表虽然灵活,但并不是所有数据都适合用列表保存。如果数据不允许重复,可以考虑 set;如果需要根据主机名查找 IP 地址,可以考虑 dict;如果需要保存一组有顺序且容器自身不可变的数据,则可以考虑 tuple。

下一篇文章,我们将继续学习 Python 的 tuple,了解元组的创建、访问、解包,以及它与列表的区别。

学会使用列表,是编写自动化脚本的重要一步。真正掌握它的关键,不只是记住方法名称,而是能够根据实际需求,把这些操作组合起来解决问题。