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 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承

前面我们学习了如何查找 Python 标准库,知道遇到不熟悉的功能时,可以使用 dir()、help() 和官方文档寻找答案。

这一篇,我们就来认识一个在 Python 程序中非常常见的标准库模块:sys。

如果你经常使用 Linux 命令行,应该对下面这样的操作并不陌生:

1
2
3
df -h
free -h
ps aux

这些命令可以接收参数,并根据参数执行不同的操作。

那么,我们能不能用 Python 编写类似的脚本呢?

例如,希望编写一个磁盘检查脚本,让用户可以通过命令行指定检查路径:

1
2
3
python3 disk_check.py /
python3 disk_check.py /var
python3 disk_check.py /home

当然可以!而 sys 模块就能帮助我们获取命令行参数、了解 Python 解释器的运行环境,以及控制程序的退出行为。

sys 是 Python 标准库中的一个模块,主要用于访问与 Python 解释器相关的信息和功能。它不需要单独安装,通常只需要导入即可使用:

1
import sys

接下来,我们结合实际示例,逐步认识 sys 模块中几个常用的属性和功能。

使用 sys.argv 获取命令行参数

先来看一个问题。

假设我们编写了一个 Python 脚本,用于检查服务器磁盘使用情况。现在希望用户能够指定检查哪个目录,而不是每次都修改代码中的路径。

应该怎么做?

这时就可以使用 sys.argv。

sys.argv 是一个列表,用于保存启动 Python 脚本时传入的命令行参数。

例如,创建一个名为 args_demo.py 的文件:

1
2
3
import sys

print(sys.argv)

在终端中执行:

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
2
3
4
5
import sys

print(f"脚本名称:{sys.argv[0]}")
print(f"第一个参数:{sys.argv[1]}")
print(f"第二个参数:{sys.argv[2]}")

执行:

1
python3 args_demo.py hello 123

输出:

1
2
3
脚本名称:args_demo.py
第一个参数:hello
第二个参数:123

注意,sys.argv 中的参数都是字符串。如果传入的是数字 123,获取到的依然是字符串 "123"。需要进行数学运算时,必须根据需要转换为 int 或 float。

还需要注意,如果没有传入足够的参数,就直接访问不存在的列表下标,程序可能会抛出 IndexError 异常。因此,在实际编程中,不能假设用户一定会传入正确的参数。

实战:让磁盘检查脚本接收参数

回到前面的 Linux 运维场景。

我们希望编写一个磁盘检查脚本,默认检查根目录 /,也允许用户指定其他路径。

创建 disk_check.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import sys
import shutil

# 如果用户没有传入路径,就默认检查根目录
if len(sys.argv) > 1:
path = sys.argv[1]
else:
path = "/"

try:
usage = shutil.disk_usage(path)
except OSError as e:
print(f"无法检查路径 {path}:{e}")
sys.exit(1)

percent = usage.used / usage.total * 100

print(f"检查路径:{path}")
print(f"磁盘总容量:{usage.total / 1024**3:.2f} GiB")
print(f"磁盘已使用:{usage.used / 1024**3:.2f} GiB")
print(f"磁盘剩余空间:{usage.free / 1024**3:.2f} GiB")
print(f"磁盘使用率:{percent:.2f}%")

if percent > 80:
print("警告:磁盘使用率超过 80%!")

输出

1
2
3
4
5
检查路径:/
磁盘总容量:200.00 GiB
磁盘已使用:65.56 GiB
磁盘剩余空间:134.44 GiB
磁盘使用率:32.78%

运行时,可以不指定路径:

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
2
3
import sys

print(sys.path)

Python 会输出一个列表,其中包含解释器查找模块时使用的路径。

不同的操作系统、Python 版本、虚拟环境和启动方式,都可能影响这个列表的内容。

例如,输出可能类似于:

1
2
3
4
5
6
7
[
'/home/user/python-demo',
'/usr/lib/python312.zip',
'/usr/lib/python3.12',
'/usr/lib/python3.12/lib-dynload',
'/home/user/.local/lib/python3.12/site-packages'
]

以上仅为示例,实际路径以当前 Python 环境为准。

我们可以把 sys.path 理解为 Python 查找模块时会参考的路径列表。当执行 import 时,Python 会根据模块的导入规则,在相关位置查找模块。

例如,当前目录下有两个文件:

1
2
3
python-demo/
├── main.py
└── tools.py

tools.py 的内容如下:

1
2
def hello():
print("Hello, Python!")

main.py 的内容如下:

1
2
3
import tools

tools.hello()

在合适的启动方式下,如果当前项目目录位于模块搜索路径中,执行:

1
python3 main.py

就会输出:

1
Hello, Python!

但是,如果模块不在可搜索的位置,就可能出现 ModuleNotFoundError。

这时可以先查看搜索路径:

1
2
3
4
import sys

for path in sys.path:
print(path)

逐行输出后,更容易看清楚 Python 当前会搜索哪些位置。

sys.path 能不能手动修改?

可以。sys.path 是一个列表,在当前 Python 进程中可以进行修改。

例如:

1
2
3
4
5
import sys

sys.path.append("/opt/my-python-modules")

print(sys.path)

这样就把 /opt/my-python-modules 添加到了当前进程的模块搜索路径列表中。

不过,需要注意:修改 sys.path 只影响当前 Python 进程,不会自动修改系统的全局 Python 配置,也不会自动安装模块。

如果模块依赖没有安装,或者模块内部还需要其他依赖,单纯添加路径并不能解决所有问题。

对于日常项目开发,通常更推荐使用合理的项目目录结构、虚拟环境和规范的安装方式,而不是到处手动修改搜索路径。

使用 sys.version 查看 Python 版本

在 Linux 运维工作中,不同服务器可能安装了不同版本的 Python。

例如,一台服务器使用 Python 3.11,另一台服务器使用 Python 3.12。如果脚本依赖某个较新版本才支持的功能,就可能在部分服务器上运行失败。

这时可以使用 sys.version 查看当前解释器的版本信息。

1
2
3
import sys

print(sys.version)

输出可能类似于:

1
3.12.3 (main, Apr  9 2024, 08:12:10) [GCC 13.2.0]

这里的版本信息仅为示例,实际输出会因 Python 版本、构建方式和系统环境而不同。

通常可以从中看到:

  • Python 解释器的版本号。
  • Python 的构建信息。
  • 部分编译环境信息。

如果只想获取结构化的版本号,还可以使用 sys.version_info:

1
2
3
4
5
6
import sys

print(sys.version_info)
print(sys.version_info.major)
print(sys.version_info.minor)
print(sys.version_info.micro)

输出示例:

1
2
3
4
sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)
3
12
3

与 sys.version 相比,sys.version_info 更适合在程序中进行版本判断。

例如,某个脚本要求 Python 3.10 或更高版本,可以这样检查:

1
2
3
4
5
6
7
import sys

if sys.version_info < (3, 10):
print("当前 Python 版本过低,请升级到 Python 3.10 或更高版本。")
sys.exit(1)

print("Python 版本符合要求,可以继续运行。")

这里的 (3, 10) 表示最低要求为 Python 3.10。使用 sys.version_info 进行比较,比直接对版本字符串进行比较更加可靠。

使用 sys.exit() 退出程序

编写运维脚本时,程序不仅要能够输出信息,还应该告诉调用者执行是否成功。

例如,在 Shell 中执行:

1
2
python3 disk_check.py /
echo $?

第二条命令会显示上一条命令的退出状态码。

通常情况下:

  • 0 表示程序正常完成。
  • 非 0 值通常表示程序执行失败,或出现了某种需要调用者关注的情况。

Python 可以使用 sys.exit() 退出程序,并向操作系统返回退出状态。

例如:

1
2
3
4
5
6
7
import sys

print("开始执行检查任务")

sys.exit(0)

print("这行代码不会执行")

运行后:

1
开始执行检查任务

因为程序已经退出,后面的 print() 不会执行。

如果需要表示执行失败,可以使用非零状态码:

1
2
3
4
import sys

print("配置文件不存在")
sys.exit(1)

在 Linux 终端中执行:

1
2
python3 check_config.py
echo $?

输出示例:

1
2
配置文件不存在
1

这里的 1 是我们主动设置的退出状态码,表示发生了错误。

在前面的磁盘检查示例中,如果指定路径不存在或无法访问,我们就可以使用 sys.exit(1) 结束程序,让调用者知道检查没有正常完成。

还需要注意,sys.exit() 实际上会引发 SystemExit 异常。正常情况下,如果没有被捕获,解释器会结束程序;但如果代码捕获了该异常,或者在特殊运行环境中执行,行为可能有所不同。

在 Shell 脚本、定时任务和自动化平台中,合理使用退出状态码非常重要,因为调用者可以根据退出码决定后续操作。

使用 sys.stdout 理解标准输出

前面我们已经使用过很多次 print(),例如:

1
print("磁盘检查完成")

默认情况下,print() 会将内容输出到标准输出,也就是 sys.stdout。

sys.stdout 是一个与标准输出流关联的对象。我们可以直接使用它输出内容:

1
2
3
import sys

sys.stdout.write("磁盘检查完成\n")

输出:

1
磁盘检查完成

这里需要注意,sys.stdout.write() 与 print() 有一个常见区别:

  • print() 默认会在输出末尾添加换行符。
  • sys.stdout.write() 不会自动添加换行符,需要时应自行写入 \n。

例如:

1
2
3
4
import sys

sys.stdout.write("Hello ")
sys.stdout.write("Python\n")

输出:

1
Hello Python

两次调用的内容连接在一起,是因为第一次调用没有写入换行符。

将程序输出重定向到文件

在 Linux 中,我们经常使用 > 将命令输出保存到文件:

1
python3 disk_check.py / > disk_report.txt

这种方式会把标准输出重定向到文件中。

在 Python 程序内部,也可以临时将 sys.stdout 指向文件对象。不过,这种做法需要注意关闭文件以及恢复原来的标准输出,因此初学者通常优先使用 Shell 重定向,或者在 Python 中使用 with open(...) 配合文件写入。

例如:

1
2
3
with open("report.txt", "w", encoding="utf-8") as file:
file.write("磁盘检查完成\n")
file.write("检查结果:正常\n")

这段代码将内容写入 report.txt,并在退出 with 代码块时自动关闭文件。

因此,虽然 sys.stdout 可以帮助我们理解 Python 的标准输出机制,但在实际项目中,应该根据需求选择合适的输出方式。

如果需要将正常信息和错误信息分开处理,还可以进一步了解 sys.stderr。它通常用于输出错误或诊断信息,在 Linux 中可以通过 2> 单独重定向。

使用 dir() 和 help() 探索 sys 模块

前面我们学习过 dir() 和 help()。这两个工具同样可以用于探索 sys 模块。

例如,先导入模块:

1
import sys

查看模块中有哪些名称:

1
print(dir(sys))

返回结果会包含许多属性和函数,例如:

1
2
['argv', 'exit', 'path', 'stderr', 'stdin', 'stdout',
'version', 'version_info', ...]

这里仅展示部分示例,实际结果会随 Python 版本和实现有所变化。

如果想进一步了解某个属性或函数,可以使用 help():

1
2
3
help(sys.argv)
help(sys.exit)
help(sys.version_info)

这样,即使遇到之前没有学习过的属性,也可以通过帮助信息了解其用途。

需要注意,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 标准库中的数字与数学相关功能,了解如何进行数值计算、处理常见数学问题,以及在实际编程中选择合适的数学工具。