Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
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 标准库,知道遇到不熟悉的功能时,可以使用 dir()、help() 和官方文档寻找答案。
这一篇,我们就来认识一个在 Python 程序中非常常见的标准库模块:sys。
如果你经常使用 Linux 命令行,应该对下面这样的操作并不陌生:
1 | df -h |
这些命令可以接收参数,并根据参数执行不同的操作。
那么,我们能不能用 Python 编写类似的脚本呢?
例如,希望编写一个磁盘检查脚本,让用户可以通过命令行指定检查路径:
1 | python3 disk_check.py / |
当然可以!而 sys 模块就能帮助我们获取命令行参数、了解 Python 解释器的运行环境,以及控制程序的退出行为。
sys 是 Python 标准库中的一个模块,主要用于访问与 Python 解释器相关的信息和功能。它不需要单独安装,通常只需要导入即可使用:
1 | import sys |
接下来,我们结合实际示例,逐步认识 sys 模块中几个常用的属性和功能。
使用 sys.argv 获取命令行参数
先来看一个问题。
假设我们编写了一个 Python 脚本,用于检查服务器磁盘使用情况。现在希望用户能够指定检查哪个目录,而不是每次都修改代码中的路径。
应该怎么做?
这时就可以使用 sys.argv。
sys.argv 是一个列表,用于保存启动 Python 脚本时传入的命令行参数。
例如,创建一个名为 args_demo.py 的文件:
1 | import sys |
在终端中执行:
1 | python3 args_demo.py hello 123 |
输出:
1 | ['args_demo.py', 'hello', '123'] |
看到这里,可能有人会问:明明只传入了 hello 和 123 两个参数,为什么结果中还有一个 args_demo.py?
原因很简单:sys.argv[0] 通常表示脚本名称或启动脚本时使用的路径,真正传入的命令行参数从 sys.argv[1] 开始。
可以通过下面的代码进一步观察:
1 | import sys |
执行:
1 | python3 args_demo.py hello 123 |
输出:
1 | 脚本名称:args_demo.py |
注意,sys.argv 中的参数都是字符串。如果传入的是数字 123,获取到的依然是字符串 "123"。需要进行数学运算时,必须根据需要转换为 int 或 float。
还需要注意,如果没有传入足够的参数,就直接访问不存在的列表下标,程序可能会抛出 IndexError 异常。因此,在实际编程中,不能假设用户一定会传入正确的参数。
实战:让磁盘检查脚本接收参数
回到前面的 Linux 运维场景。
我们希望编写一个磁盘检查脚本,默认检查根目录 /,也允许用户指定其他路径。
创建 disk_check.py:
1 | import sys |
输出
1 | 检查路径:/ |
运行时,可以不指定路径:
1 | python3 disk_check.py |
此时脚本会检查根目录 /。
也可以指定目录:
1 | python3 disk_check.py /var |
或者:
1 | python3 disk_check.py /home |
这样,同一份代码就能检查不同路径,不需要每次修改脚本。
这里有一个细节值得注意:shutil.disk_usage() 查询的是指定路径所在文件系统的空间信息。如果 /var 与 / 位于同一个文件系统,查询结果可能相同;如果 /var 是独立挂载点,结果则可能不同。
此外,sys.argv 适合简单的命令行参数处理。如果脚本需要支持 --path、--threshold、--help 等选项,后续可以学习 argparse 模块,它更适合构建规范的命令行工具。
使用 sys.path 查看模块搜索路径
接下来,我们来看另一个常见问题。
为什么有些 Python 模块可以直接导入,而另一些模块却会报错:
1 | ModuleNotFoundError: No module named 'xxx' |
原因之一,是 Python 解释器没有在模块搜索路径中找到目标模块。
而 sys.path 就是一个与模块查找有关的重要属性。
当我们执行:
1 | import sys |
Python 会输出一个列表,其中包含解释器查找模块时使用的路径。
不同的操作系统、Python 版本、虚拟环境和启动方式,都可能影响这个列表的内容。
例如,输出可能类似于:
1 | [ |
以上仅为示例,实际路径以当前 Python 环境为准。
我们可以把 sys.path 理解为 Python 查找模块时会参考的路径列表。当执行 import 时,Python 会根据模块的导入规则,在相关位置查找模块。
例如,当前目录下有两个文件:
1 | python-demo/ |
tools.py 的内容如下:
1 | def hello(): |
main.py 的内容如下:
1 | import tools |
在合适的启动方式下,如果当前项目目录位于模块搜索路径中,执行:
1 | python3 main.py |
就会输出:
1 | Hello, Python! |
但是,如果模块不在可搜索的位置,就可能出现 ModuleNotFoundError。
这时可以先查看搜索路径:
1 | import sys |
逐行输出后,更容易看清楚 Python 当前会搜索哪些位置。
sys.path 能不能手动修改?
可以。sys.path 是一个列表,在当前 Python 进程中可以进行修改。
例如:
1 | import sys |
这样就把 /opt/my-python-modules 添加到了当前进程的模块搜索路径列表中。
不过,需要注意:修改 sys.path 只影响当前 Python 进程,不会自动修改系统的全局 Python 配置,也不会自动安装模块。
如果模块依赖没有安装,或者模块内部还需要其他依赖,单纯添加路径并不能解决所有问题。
对于日常项目开发,通常更推荐使用合理的项目目录结构、虚拟环境和规范的安装方式,而不是到处手动修改搜索路径。
使用 sys.version 查看 Python 版本
在 Linux 运维工作中,不同服务器可能安装了不同版本的 Python。
例如,一台服务器使用 Python 3.11,另一台服务器使用 Python 3.12。如果脚本依赖某个较新版本才支持的功能,就可能在部分服务器上运行失败。
这时可以使用 sys.version 查看当前解释器的版本信息。
1 | import sys |
输出可能类似于:
1 | 3.12.3 (main, Apr 9 2024, 08:12:10) [GCC 13.2.0] |
这里的版本信息仅为示例,实际输出会因 Python 版本、构建方式和系统环境而不同。
通常可以从中看到:
- Python 解释器的版本号。
- Python 的构建信息。
- 部分编译环境信息。
如果只想获取结构化的版本号,还可以使用 sys.version_info:
1 | import sys |
输出示例:
1 | sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0) |
与 sys.version 相比,sys.version_info 更适合在程序中进行版本判断。
例如,某个脚本要求 Python 3.10 或更高版本,可以这样检查:
1 | import sys |
这里的 (3, 10) 表示最低要求为 Python 3.10。使用 sys.version_info 进行比较,比直接对版本字符串进行比较更加可靠。
使用 sys.exit() 退出程序
编写运维脚本时,程序不仅要能够输出信息,还应该告诉调用者执行是否成功。
例如,在 Shell 中执行:
1 | python3 disk_check.py / |
第二条命令会显示上一条命令的退出状态码。
通常情况下:
0表示程序正常完成。- 非
0值通常表示程序执行失败,或出现了某种需要调用者关注的情况。
Python 可以使用 sys.exit() 退出程序,并向操作系统返回退出状态。
例如:
1 | import sys |
运行后:
1 | 开始执行检查任务 |
因为程序已经退出,后面的 print() 不会执行。
如果需要表示执行失败,可以使用非零状态码:
1 | import sys |
在 Linux 终端中执行:
1 | python3 check_config.py |
输出示例:
1 | 配置文件不存在 |
这里的 1 是我们主动设置的退出状态码,表示发生了错误。
在前面的磁盘检查示例中,如果指定路径不存在或无法访问,我们就可以使用 sys.exit(1) 结束程序,让调用者知道检查没有正常完成。
还需要注意,sys.exit() 实际上会引发 SystemExit 异常。正常情况下,如果没有被捕获,解释器会结束程序;但如果代码捕获了该异常,或者在特殊运行环境中执行,行为可能有所不同。
在 Shell 脚本、定时任务和自动化平台中,合理使用退出状态码非常重要,因为调用者可以根据退出码决定后续操作。
使用 sys.stdout 理解标准输出
前面我们已经使用过很多次 print(),例如:
1 | print("磁盘检查完成") |
默认情况下,print() 会将内容输出到标准输出,也就是 sys.stdout。
sys.stdout 是一个与标准输出流关联的对象。我们可以直接使用它输出内容:
1 | import sys |
输出:
1 | 磁盘检查完成 |
这里需要注意,sys.stdout.write() 与 print() 有一个常见区别:
print()默认会在输出末尾添加换行符。sys.stdout.write()不会自动添加换行符,需要时应自行写入\n。
例如:
1 | import sys |
输出:
1 | Hello Python |
两次调用的内容连接在一起,是因为第一次调用没有写入换行符。
将程序输出重定向到文件
在 Linux 中,我们经常使用 > 将命令输出保存到文件:
1 | python3 disk_check.py / > disk_report.txt |
这种方式会把标准输出重定向到文件中。
在 Python 程序内部,也可以临时将 sys.stdout 指向文件对象。不过,这种做法需要注意关闭文件以及恢复原来的标准输出,因此初学者通常优先使用 Shell 重定向,或者在 Python 中使用 with open(...) 配合文件写入。
例如:
1 | with open("report.txt", "w", encoding="utf-8") as file: |
这段代码将内容写入 report.txt,并在退出 with 代码块时自动关闭文件。
因此,虽然 sys.stdout 可以帮助我们理解 Python 的标准输出机制,但在实际项目中,应该根据需求选择合适的输出方式。
如果需要将正常信息和错误信息分开处理,还可以进一步了解 sys.stderr。它通常用于输出错误或诊断信息,在 Linux 中可以通过 2> 单独重定向。
使用 dir() 和 help() 探索 sys 模块
前面我们学习过 dir() 和 help()。这两个工具同样可以用于探索 sys 模块。
例如,先导入模块:
1 | import sys |
查看模块中有哪些名称:
1 | print(dir(sys)) |
返回结果会包含许多属性和函数,例如:
1 | ['argv', 'exit', 'path', 'stderr', 'stdin', 'stdout', |
这里仅展示部分示例,实际结果会随 Python 版本和实现有所变化。
如果想进一步了解某个属性或函数,可以使用 help():
1 | help(sys.argv) |
这样,即使遇到之前没有学习过的属性,也可以通过帮助信息了解其用途。
需要注意,dir(sys) 中的名称不一定全部是函数,也可能是属性、类型或其他对象。探索时应结合具体对象的帮助信息判断其用途。
通过这种方式,我们不仅能够学习 sys 模块,也能把之前学过的模块探索技巧应用到新的场景中。
常用 sys 属性与功能小结
为了方便查阅,我们把本篇涉及的内容整理如下。
| 属性或函数 | 主要用途 | 常见场景 |
|---|---|---|
sys.argv | 获取命令行参数 | 指定检查路径、接收脚本参数 |
sys.path | 查看模块搜索路径 | 排查模块导入失败 |
sys.version | 获取解释器版本信息 | 查看 Python 版本及构建信息 |
sys.version_info | 获取结构化版本信息 | 判断最低版本要求 |
sys.exit() | 退出程序并设置退出状态 | 任务失败时返回非零状态码 |
sys.stdout | 访问标准输出流 | 输出文本、理解输出重定向 |
sys.stderr | 访问标准错误流 | 输出错误和诊断信息 |
需要注意,sys.stdout 和 sys.stderr 是流对象,不是函数。它们提供了与标准输出和标准错误相关的接口,因此在分类时应该与 sys.exit() 这样的函数区分开来。
学习时不需要一次性记住所有内容。先掌握命令行参数、解释器版本和退出状态码这几个常见用法,就足以应对不少简单的 Python 自动化任务。
写在最后
本篇我们认识了 Python 标准库中的 sys 模块,学习了如何获取命令行参数、查看模块搜索路径、判断解释器版本,以及控制程序的退出状态和输出内容。
对于 Linux 运维工程师来说,这些功能尤其适合用来编写可配置、可自动化、便于集成到 Shell 脚本和定时任务中的 Python 工具。
下一篇,我们将继续探索 Python 标准库中的数字与数学相关功能,了解如何进行数值计算、处理常见数学问题,以及在实际编程中选择合适的数学工具。
