Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
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 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
- Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
- Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
- Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
- Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
- Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
- Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
- Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
- Python 系列(二十六) Python 模块入门,从 import 到模块复用
- Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
- Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
- Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
- Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
- Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
- Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
- Python 系列(三十三) 什么是类和对象?零基础理解 Python class
- Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
- Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
- Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
- Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
- Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
- Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
- Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
- Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
- Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
- Python 系列(四十五) 使用 sys 模块处理标准输入与输出
- Python 系列(四十六) 使用 open() 读取和写入文件
- Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
- Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
- Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
- Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
- Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
- Python 系列(五十二) 掌握字典推导式,让字典创建更简单
- Python 系列(五十三) 从零理解生成器,轻松处理大量数据
- Python 系列(五十五) 使用 zip() 同时遍历多个集合
前面我们已经学习了 Python 中的列表,知道可以使用列表保存多个数据。例如,我们可以把多台服务器的名称、IP 地址或者磁盘使用率保存到列表中,方便后续统一处理。
随着学习的深入,你可能会发现一个问题:如果想根据已有的数据,批量生成一个新的列表,难道必须使用循环,一项一项地添加吗?
例如,我们想生成一个包含 0 到 9 的平方数的列表。
如果使用之前学过的 for 循环,可以这样写:
1 | numbers = [] |
执行结果:
1 | [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] |
这段代码并不复杂,但如果只是为了根据一组数据生成一个新列表,我们需要先创建空列表,再通过循环逐个计算,最后使用 append() 把结果添加进去。
有没有更简洁的写法呢?
Python 提供了一个非常实用的语法:列表推导式(List Comprehension)。
它可以把循环、数据处理和创建列表的过程写在一行代码中,让代码更加简洁。
认识列表推导式
我们先来看一下刚才的平方数示例,使用列表推导式应该怎么写:
1 | numbers = [x ** 2 for x in range(10)] |
执行结果:
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 | numbers = [x for x in range(5)] |
执行结果:
1 | [0, 1, 2, 3, 4] |
这里的 x 没有进行额外计算,因此每次循环取到什么数字,就把什么数字放进列表。
如果把表达式换成 x * 2:
1 | numbers = [x * 2 for x in range(5)] |
执行结果:
1 | [0, 2, 4, 6, 8] |
循环取到的数字没有变化,但放入列表的内容发生了变化。
这说明列表推导式中的表达式决定了新列表中每一项的值。
从简单示例理解执行过程
为了更好地理解列表推导式,我们再来看一个例子。
假设我们需要生成 1 到 5 的平方数:
1 | squares = [x ** 2 for x in range(1, 6)] |
执行结果:
1 | [1, 4, 9, 16, 25] |
我们重点关注下面这部分:
1 | range(1, 6) |
它生成的数字是 1、2、3、4、5。注意,结束值 6 不包含在内。
列表推导式会依次处理这些数字:
当前变量 x | 表达式 x ** 2 | 放入列表的结果 |
|---|---|---|
| 1 | 1 ** 2 | 1 |
| 2 | 2 ** 2 | 4 |
| 3 | 3 ** 2 | 9 |
| 4 | 4 ** 2 | 16 |
| 5 | 5 ** 2 | 25 |
所有数字处理完毕后,就得到了最终列表:
1 | [1, 4, 9, 16, 25] |
通过这个例子,我们可以发现,列表推导式并不是把所有操作同时完成,而是按照循环顺序逐项处理数据,再将处理结果放入新列表。
使用列表推导式转换数据
列表推导式不仅可以用于数学计算,还可以用于数据转换。
例如,我们希望把一组整数转换成十六进制字符串。
在 Linux 运维中,十六进制经常出现在内存地址、数据编码、网络协议以及调试信息中。虽然平时不一定需要手动转换大量数字,但通过这个例子,我们可以学习如何批量处理数据。
先来看普通的循环写法:
1 | hex_numbers = [] |
执行结果:
1 | ['0x0', '0x1', '0x2', '0x3', '0x4', '0x5', '0x6', '0x7', |
这里使用了一个之前可能还不熟悉的函数:hex()。
hex() 是 Python 内置函数,用于将整数转换成以 0x 开头的十六进制字符串。
例如:
1 | print(hex(10)) |
执行结果:
1 | 0xa |
现在,我们使用列表推导式改写刚才的循环:
1 | hex_numbers = [hex(x) for x in range(16)] |
执行结果与前面的循环版本相同。
我们可以把这行代码理解为:
range(16)提供从 0 到 15 的整数。for x in range(16)依次获取每个整数。hex(x)将当前整数转换为十六进制字符串。- 列表推导式将转换结果依次保存到新列表中。
如果你在其他教程中见过下面这种写法:
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 | even_numbers = [] |
执行结果:
1 | [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] |
这里出现了一个之前学过的运算符:%。
它表示取余数。例如,10 % 2 的结果是 0,说明 10 除以 2 没有余数。因此,我们可以使用 x % 2 == 0 判断一个数字是不是偶数。
那么,如何使用列表推导式完成相同的工作呢?
1 | even_numbers = [x for x in range(20) if x % 2 == 0] |
执行结果:
1 | [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] |
与之前相比,这次多了一个 if 条件。
列表推导式支持在循环后面添加 if,用来决定哪些数据可以放进新列表。
它的语法是:
1 | [表达式 for 变量 in 可迭代对象 if 条件] |
可以这样理解:
- 如果条件成立,就把表达式的结果放入列表。
- 如果条件不成立,就跳过当前数据,不将它放入列表。
再来看一个运维场景。
假设我们通过某种方式获得了多台服务器的磁盘使用率,现在只想筛选出使用率达到 80% 或以上的服务器:
1 | disk_usage = [35, 62, 81, 45, 93, 78, 86] |
执行结果:
1 | [81, 93, 86] |
这里的 usage 是循环变量,每次代表一个磁盘使用率。
当使用率大于或等于 80 时,当前值才会被添加到 high_usage 列表中。
需要注意,这个例子使用的是已经获取到的模拟数据,并没有真正连接服务器或读取磁盘信息。实际运维脚本还需要通过命令、接口或监控系统获取真实数据。
在筛选数据时同时进行转换
有时,我们既需要筛选数据,又需要对符合条件的数据进行转换。
例如,我们希望找出 1 到 10 之间的偶数,并计算它们的平方。
1 | squares = [x ** 2 for x in range(1, 11) if x % 2 == 0] |
执行结果:
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 | disk_usage = [35, 81, 62, 93, 45] |
执行结果:
1 | ['正常', '告警', '正常', '告警', '正常'] |
这里的语法与前面有所不同:
1 | [结果1 if 条件 else 结果2 for 变量 in 可迭代对象] |
它表示:
- 条件成立时,生成
if前面的结果。 - 条件不成立时,生成
else后面的结果。 - 无论条件是否成立,都会为当前数据生成一个结果。
这与之前的筛选写法有本质区别。
使用 if 筛选数据时,不符合条件的数据会被跳过;使用 if...else 条件表达式时,每项数据都会生成一个结果,只是结果可能不同。
对初学者来说,建议先熟练掌握基本列表推导式,再学习条件筛选,最后再尝试这种条件表达式写法。
列表推导式可以遍历字符串
除了数字和列表,字符串也可以用于列表推导式。
例如,我们有一个字符串:
1 | name = "python" |
执行结果:
1 | ['p', 'y', 't', 'h', 'o', 'n'] |
这是因为字符串可以逐个提供其中的字符。
列表推导式依次获取每个字符,再把它们放入一个新列表中。
我们也可以对字符进行转换:
1 | name = "python" |
执行结果:
1 | ['P', 'Y', 'T', 'H', 'O', 'N'] |
这里使用了字符串的 upper() 方法,它可以将英文字母转换为大写形式。
如果希望得到一个完整的大写字符串,而不是字符列表,直接使用 name.upper() 会更合适:
1 | name = "python" |
执行结果:
1 | PYTHON |
这也提醒我们:列表推导式虽然简洁,但并不意味着所有操作都应该使用它。选择合适的方式,才能让代码更容易理解。
列表推导式与普通循环应该怎么选
学习到这里,你可能会觉得列表推导式很方便,是不是以后所有循环都应该写成列表推导式?
并不是。
列表推导式适合用来根据已有数据快速生成新列表,尤其适合简单的转换和筛选任务。
如果循环内部包含很多步骤,例如执行复杂的判断、调用多个函数、记录日志或者处理异常,普通的 for 循环往往更清晰。
我们来看一个对比:
1 | # 使用列表推导式 |
两种写法都可以完成任务。
在这种简单场景下,列表推导式更简洁;如果需要在循环中执行多步复杂操作,普通循环更容易阅读和维护。
还有一点需要注意:列表推导式会创建一个新列表,并将生成的结果保存在内存中。如果需要处理数百万条甚至更多的数据,就应该考虑内存占用问题,必要时可以使用生成器表达式等其他方式。
对于刚开始学习 Python 的同学,现阶段只需要记住:简单的数据转换和筛选,可以优先考虑列表推导式;复杂的处理逻辑,继续使用普通循环。
动手练习
理论学习之后,我们通过几个小练习检查一下是否真正理解了列表推导式。
练习:生成 1 到 10 的数字列表
请使用列表推导式生成一个包含 1 到 10 的整数列表。
参考答案:
1 | numbers = [x for x in range(1, 11)] |
执行结果:
1 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
练习:生成 1 到 10 的立方数
请使用列表推导式,计算 1 到 10 每个数字的立方。
提示:使用 x ** 3 计算立方。
参考答案:
1 | cubes = [x ** 3 for x in range(1, 11)] |
执行结果:
1 | [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] |
练习:筛选出磁盘使用率较高的数据
假设有以下磁盘使用率:
1 | disk_usage = [35, 62, 81, 45, 93, 78, 86] |
请使用列表推导式,筛选出所有大于或等于 80 的数值。
参考答案:
1 | high_usage = [usage for usage in disk_usage if usage >= 80] |
执行结果:
1 | [81, 93, 86] |
练习:将服务器名称转换为大写
假设有以下服务器名称:
1 | servers = ["web01", "web02", "db01", "redis01"] |
请使用列表推导式,将所有服务器名称转换为大写形式。
参考答案:
1 | upper_servers = [server.upper() for server in servers] |
执行结果:
1 | ['WEB01', 'WEB02', 'DB01', 'REDIS01'] |
写在最后
通过这篇文章,我们认识了 Python 中的列表推导式,学习了如何使用它批量生成列表、转换数据、筛选数据,以及根据条件生成不同的结果。
回顾一下,最重要的是下面三种形式:
基本形式:
1 | [表达式 for 变量 in 可迭代对象] |
带条件筛选的形式:
1 | [表达式 for 变量 in 可迭代对象 if 条件] |
带条件表达式的形式:
1 | [结果1 if 条件 else 结果2 for 变量 in 可迭代对象] |
刚开始学习时,不必急着记住所有语法。先从最简单的例子开始,理解循环如何获取数据、表达式如何处理数据,以及结果如何进入新列表。等这些内容熟悉之后,再逐步尝试更复杂的写法。
接下来,我们将继续学习推导式家族中的另一个重要成员:字典推导式。
列表推导式用于快速创建列表,而字典推导式可以帮助我们根据已有数据,快速构建由键和值组成的字典。例如,将服务器名称与 IP 地址组合成对应关系,或者根据监控数据生成服务器状态字典。
学会字典推导式后,我们就能进一步简化日常数据整理和自动化运维脚本中的一些操作。
