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() 同时遍历多个集合

前面我们已经学习了 Python 中的列表,知道可以使用列表保存多个数据。例如,我们可以把多台服务器的名称、IP 地址或者磁盘使用率保存到列表中,方便后续统一处理。

随着学习的深入,你可能会发现一个问题:如果想根据已有的数据,批量生成一个新的列表,难道必须使用循环,一项一项地添加吗?

例如,我们想生成一个包含 0 到 9 的平方数的列表。

如果使用之前学过的 for 循环,可以这样写:

1
2
3
4
5
6
numbers = []

for x in range(10):
numbers.append(x ** 2)

print(numbers)

执行结果:

1
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

这段代码并不复杂,但如果只是为了根据一组数据生成一个新列表,我们需要先创建空列表,再通过循环逐个计算,最后使用 append() 把结果添加进去。

有没有更简洁的写法呢?

Python 提供了一个非常实用的语法:列表推导式(List Comprehension)。

它可以把循环、数据处理和创建列表的过程写在一行代码中,让代码更加简洁。

认识列表推导式

我们先来看一下刚才的平方数示例,使用列表推导式应该怎么写:

1
2
3
numbers = [x ** 2 for x in range(10)]

print(numbers)

执行结果:

1
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

可以看到,最终得到的结果与使用 for 循环的写法完全相同。

原来需要多行代码完成的工作,现在只需要一行就可以完成。

不过,对于刚接触 Python 的初学者来说,这行代码可能有些难以理解。别着急,我们把它拆开来看。

1
numbers = [x ** 2 for x in range(10)]

其中包含几个关键部分:

  • []:表示创建一个列表。
  • x ** 2:表示计算变量 x 的平方。
  • for x in range(10):表示依次获取从 0 到 9 的数字,并将当前数字赋值给变量 x。

把它们组合起来,就表示:依次取出 0 到 9 的数字,计算每个数字的平方,并将计算结果依次放入一个新列表中。

可以通过下面的流程理解整个过程:

flowchart TD
    A["开始创建列表"] --> B["获取一个数字 x"]
    B --> C["计算 x ** 2"]
    C --> D["将计算结果放入新列表"]
    D --> E{"还有数字吗?"}
    E -->|有| B
    E -->|没有| F["得到最终列表"]

列表推导式并没有引入新的循环机制,它只是提供了一种更简洁的方式,把我们已经学过的循环和列表操作组合起来。

列表推导式的基本语法

了解了它的作用之后,我们来看一下列表推导式的基本语法。

1
[表达式 for 变量 in 可迭代对象]

这几个词可能有些陌生,我们逐个理解。

组成部分含义示例
[]创建一个新列表[]
表达式决定每一项最终保存什么x * 2
for表示循环获取数据for x
变量临时保存当前获取的数据x
in 后面的对象提供循环需要的数据range(5)

这里还有一个词叫作“可迭代对象”。

暂时不需要记住复杂的定义,可以先把它理解为:能够让我们逐个获取其中数据的对象。

我们之前接触过的列表、字符串和 range() 生成的数字序列,都可以用于列表推导式。

例如:

1
2
3
numbers = [x for x in range(5)]

print(numbers)

执行结果:

1
[0, 1, 2, 3, 4]

这里的 x 没有进行额外计算,因此每次循环取到什么数字,就把什么数字放进列表。

如果把表达式换成 x * 2:

1
2
3
numbers = [x * 2 for x in range(5)]

print(numbers)

执行结果:

1
[0, 2, 4, 6, 8]

循环取到的数字没有变化,但放入列表的内容发生了变化。

这说明列表推导式中的表达式决定了新列表中每一项的值。

从简单示例理解执行过程

为了更好地理解列表推导式,我们再来看一个例子。

假设我们需要生成 1 到 5 的平方数:

1
2
3
squares = [x ** 2 for x in range(1, 6)]

print(squares)

执行结果:

1
[1, 4, 9, 16, 25]

我们重点关注下面这部分:

1
range(1, 6)

它生成的数字是 1、2、3、4、5。注意,结束值 6 不包含在内。

列表推导式会依次处理这些数字:

当前变量 x表达式 x ** 2放入列表的结果
11 ** 21
22 ** 24
33 ** 29
44 ** 216
55 ** 225

所有数字处理完毕后,就得到了最终列表:

1
[1, 4, 9, 16, 25]

通过这个例子,我们可以发现,列表推导式并不是把所有操作同时完成,而是按照循环顺序逐项处理数据,再将处理结果放入新列表。

使用列表推导式转换数据

列表推导式不仅可以用于数学计算,还可以用于数据转换。

例如,我们希望把一组整数转换成十六进制字符串。

在 Linux 运维中,十六进制经常出现在内存地址、数据编码、网络协议以及调试信息中。虽然平时不一定需要手动转换大量数字,但通过这个例子,我们可以学习如何批量处理数据。

先来看普通的循环写法:

1
2
3
4
5
6
hex_numbers = []

for x in range(16):
hex_numbers.append(hex(x))

print(hex_numbers)

执行结果:

1
2
['0x0', '0x1', '0x2', '0x3', '0x4', '0x5', '0x6', '0x7',
'0x8', '0x9', '0xa', '0xb', '0xc', '0xd', '0xe', '0xf']

这里使用了一个之前可能还不熟悉的函数:hex()。

hex() 是 Python 内置函数,用于将整数转换成以 0x 开头的十六进制字符串。

例如:

1
2
3
print(hex(10))
print(hex(15))
print(hex(255))

执行结果:

1
2
3
0xa
0xf
0xff

现在,我们使用列表推导式改写刚才的循环:

1
2
3
hex_numbers = [hex(x) for x in range(16)]

print(hex_numbers)

执行结果与前面的循环版本相同。

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

  1. range(16) 提供从 0 到 15 的整数。
  2. for x in range(16) 依次获取每个整数。
  3. hex(x) 将当前整数转换为十六进制字符串。
  4. 列表推导式将转换结果依次保存到新列表中。

如果你在其他教程中见过下面这种写法:

1
sample = [hex(x) for x in range(0, 16)]

它与下面的写法完全相同:

1
sample = [hex(x) for x in range(16)]

因为 range(16) 默认从 0 开始,所以这里可以省略起始值 0。

使用条件筛选需要的数据

前面介绍的列表推导式会处理循环中获取的每一项数据。

但是,在实际工作中,我们有时并不希望保留所有数据,而是只想要满足某个条件的内容。

例如,我们只需要 0 到 19 之间的偶数。

如果使用普通循环,可以这样写:

1
2
3
4
5
6
7
even_numbers = []

for x in range(20):
if x % 2 == 0:
even_numbers.append(x)

print(even_numbers)

执行结果:

1
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

这里出现了一个之前学过的运算符:%。

它表示取余数。例如,10 % 2 的结果是 0,说明 10 除以 2 没有余数。因此,我们可以使用 x % 2 == 0 判断一个数字是不是偶数。

那么,如何使用列表推导式完成相同的工作呢?

1
2
3
even_numbers = [x for x in range(20) if x % 2 == 0]

print(even_numbers)

执行结果:

1
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

与之前相比,这次多了一个 if 条件。

列表推导式支持在循环后面添加 if,用来决定哪些数据可以放进新列表。

它的语法是:

1
[表达式 for 变量 in 可迭代对象 if 条件]

可以这样理解:

  • 如果条件成立,就把表达式的结果放入列表。
  • 如果条件不成立,就跳过当前数据,不将它放入列表。

再来看一个运维场景。

假设我们通过某种方式获得了多台服务器的磁盘使用率,现在只想筛选出使用率达到 80% 或以上的服务器:

1
2
3
4
5
disk_usage = [35, 62, 81, 45, 93, 78, 86]

high_usage = [usage for usage in disk_usage if usage >= 80]

print(high_usage)

执行结果:

1
[81, 93, 86]

这里的 usage 是循环变量,每次代表一个磁盘使用率。

当使用率大于或等于 80 时,当前值才会被添加到 high_usage 列表中。

需要注意,这个例子使用的是已经获取到的模拟数据,并没有真正连接服务器或读取磁盘信息。实际运维脚本还需要通过命令、接口或监控系统获取真实数据。

在筛选数据时同时进行转换

有时,我们既需要筛选数据,又需要对符合条件的数据进行转换。

例如,我们希望找出 1 到 10 之间的偶数,并计算它们的平方。

1
2
3
squares = [x ** 2 for x in range(1, 11) if x % 2 == 0]

print(squares)

执行结果:

1
[4, 16, 36, 64, 100]

我们再把这行代码拆开:

  • range(1, 11):提供 1 到 10 的整数。
  • if x % 2 == 0:只保留偶数。
  • x ** 2:计算符合条件的数字的平方。
  • []:把计算结果组成一个新列表。

这里有一个值得注意的地方:条件判断写在表达式后面,但表达式只会对符合条件的数据产生最终列表项。

我们也可以用流程图理解:

flowchart TD
    A["获取下一个数字 x"] --> B{"x 是偶数吗?"}
    B -->|是| C["计算 x 的平方"]
    C --> D["将平方结果加入列表"]
    B -->|否| E["跳过当前数字"]
    D --> F{"还有数字吗?"}
    E --> F
    F -->|有| A
    F -->|没有| G["得到最终结果"]

需要区分的是,这里的 if 用于筛选数据,而不是决定表达式的结果。如果你希望条件成立时生成一种结果、不成立时生成另一种结果,就需要使用另一种写法。

使用条件表达式生成不同的结果

假设我们希望把一组磁盘使用率转换成状态标签:

  • 使用率达到 80% 或以上,标记为 告警。
  • 使用率低于 80%,标记为 正常。

可以使用条件表达式完成这个操作:

1
2
3
4
5
6
7
8
disk_usage = [35, 81, 62, 93, 45]

status = [
"告警" if usage >= 80 else "正常"
for usage in disk_usage
]

print(status)

执行结果:

1
['正常', '告警', '正常', '告警', '正常']

这里的语法与前面有所不同:

1
[结果1 if 条件 else 结果2 for 变量 in 可迭代对象]

它表示:

  • 条件成立时,生成 if 前面的结果。
  • 条件不成立时,生成 else 后面的结果。
  • 无论条件是否成立,都会为当前数据生成一个结果。

这与之前的筛选写法有本质区别。

使用 if 筛选数据时,不符合条件的数据会被跳过;使用 if...else 条件表达式时,每项数据都会生成一个结果,只是结果可能不同。

对初学者来说,建议先熟练掌握基本列表推导式,再学习条件筛选,最后再尝试这种条件表达式写法。

列表推导式可以遍历字符串

除了数字和列表,字符串也可以用于列表推导式。

例如,我们有一个字符串:

1
2
3
4
5
name = "python"

letters = [char for char in name]

print(letters)

执行结果:

1
['p', 'y', 't', 'h', 'o', 'n']

这是因为字符串可以逐个提供其中的字符。

列表推导式依次获取每个字符,再把它们放入一个新列表中。

我们也可以对字符进行转换:

1
2
3
4
5
name = "python"

upper_letters = [char.upper() for char in name]

print(upper_letters)

执行结果:

1
['P', 'Y', 'T', 'H', 'O', 'N']

这里使用了字符串的 upper() 方法,它可以将英文字母转换为大写形式。

如果希望得到一个完整的大写字符串,而不是字符列表,直接使用 name.upper() 会更合适:

1
2
3
name = "python"

print(name.upper())

执行结果:

1
PYTHON

这也提醒我们:列表推导式虽然简洁,但并不意味着所有操作都应该使用它。选择合适的方式,才能让代码更容易理解。

列表推导式与普通循环应该怎么选

学习到这里,你可能会觉得列表推导式很方便,是不是以后所有循环都应该写成列表推导式?

并不是。

列表推导式适合用来根据已有数据快速生成新列表,尤其适合简单的转换和筛选任务。

如果循环内部包含很多步骤,例如执行复杂的判断、调用多个函数、记录日志或者处理异常,普通的 for 循环往往更清晰。

我们来看一个对比:

1
2
3
4
5
6
7
8
# 使用列表推导式
squares = [x ** 2 for x in range(5)]

# 使用普通循环
squares = []

for x in range(5):
squares.append(x ** 2)

两种写法都可以完成任务。

在这种简单场景下,列表推导式更简洁;如果需要在循环中执行多步复杂操作,普通循环更容易阅读和维护。

还有一点需要注意:列表推导式会创建一个新列表,并将生成的结果保存在内存中。如果需要处理数百万条甚至更多的数据,就应该考虑内存占用问题,必要时可以使用生成器表达式等其他方式。

对于刚开始学习 Python 的同学,现阶段只需要记住:简单的数据转换和筛选,可以优先考虑列表推导式;复杂的处理逻辑,继续使用普通循环。

动手练习

理论学习之后,我们通过几个小练习检查一下是否真正理解了列表推导式。

练习:生成 1 到 10 的数字列表

请使用列表推导式生成一个包含 1 到 10 的整数列表。

参考答案:

1
2
3
numbers = [x for x in range(1, 11)]

print(numbers)

执行结果:

1
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

练习:生成 1 到 10 的立方数

请使用列表推导式,计算 1 到 10 每个数字的立方。

提示:使用 x ** 3 计算立方。

参考答案:

1
2
3
cubes = [x ** 3 for x in range(1, 11)]

print(cubes)

执行结果:

1
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

练习:筛选出磁盘使用率较高的数据

假设有以下磁盘使用率:

1
disk_usage = [35, 62, 81, 45, 93, 78, 86]

请使用列表推导式,筛选出所有大于或等于 80 的数值。

参考答案:

1
2
3
high_usage = [usage for usage in disk_usage if usage >= 80]

print(high_usage)

执行结果:

1
[81, 93, 86]

练习:将服务器名称转换为大写

假设有以下服务器名称:

1
servers = ["web01", "web02", "db01", "redis01"]

请使用列表推导式,将所有服务器名称转换为大写形式。

参考答案:

1
2
3
upper_servers = [server.upper() for server in servers]

print(upper_servers)

执行结果:

1
['WEB01', 'WEB02', 'DB01', 'REDIS01']

写在最后

通过这篇文章,我们认识了 Python 中的列表推导式,学习了如何使用它批量生成列表、转换数据、筛选数据,以及根据条件生成不同的结果。

回顾一下,最重要的是下面三种形式:

基本形式:

1
[表达式 for 变量 in 可迭代对象]

带条件筛选的形式:

1
[表达式 for 变量 in 可迭代对象 if 条件]

带条件表达式的形式:

1
[结果1 if 条件 else 结果2 for 变量 in 可迭代对象]

刚开始学习时,不必急着记住所有语法。先从最简单的例子开始,理解循环如何获取数据、表达式如何处理数据,以及结果如何进入新列表。等这些内容熟悉之后,再逐步尝试更复杂的写法。

接下来,我们将继续学习推导式家族中的另一个重要成员:字典推导式。

列表推导式用于快速创建列表,而字典推导式可以帮助我们根据已有数据,快速构建由键和值组成的字典。例如,将服务器名称与 IP 地址组合成对应关系,或者根据监控数据生成服务器状态字典。

学会字典推导式后,我们就能进一步简化日常数据整理和自动化运维脚本中的一些操作。