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

前面我们认识了 Python 的基本数据类型,了解了字符串、整数、浮点数以及类型转换。

但程序光能处理数据还不够。比如,我们编写了一个 CPU 监控脚本:CPU 使用率低于 50% 时显示正常,达到 50% 时进入警告级别,达到 80% 时升级为严重告警,达到或超过 95% 时提示危险。计算机怎么知道应该执行哪一段代码?

这就需要用到 Python 的流程控制结构。

不过,在学习 if 之前,我们还需要解决一个看似简单、实际上非常重要的问题:为什么 Python 对缩进如此严格?

在 Linux 运维中,一个缩进错误可能导致告警逻辑失效,甚至让本来只应该执行一次的操作被重复执行。今天我们就从缩进开始,逐步掌握 Python 的决策结构,并通过实际例子理解它在运维自动化中的应用。

为什么 Python 必须使用缩进?

如果你之前接触过 Shell、C 或 Java,可能已经习惯使用大括号 {} 来表示代码块。

例如,Shell 可以这样写:

1
2
3
if [ "$cpu" -gt 80 ]; then
echo "CPU 使用率过高"
fi

而 Python 不需要 then、fi 或大括号,只需要冒号和缩进:

1
2
3
4
cpu = 85

if cpu > 80:
print("CPU 使用率过高")

看起来简单了不少,但 Python 对代码格式也提出了明确要求。

在 Python 中,缩进不仅仅是为了让代码看起来整齐,它还是程序语法的一部分。

缩进决定代码属于哪个代码块

先看一个例子:

1
2
3
4
5
6
7
cpu = 85

if cpu > 80:
print("CPU 使用率过高")
print("请检查服务器负载")

print("监控程序执行完毕")

运行结果:

1
2
3
CPU 使用率过高
请检查服务器负载
监控程序执行完毕

这里有三个需要注意的地方。

  • if cpu > 80: 是条件判断的标头。
  • 前面有 4 个空格的两条 print() 语句属于 if 代码块。
  • 最后一条 print() 没有缩进,属于外层代码,因此不受这个 if 条件控制。

换句话说,当 cpu > 80 成立时,Python 会执行缩进在 if 下面的两条语句;无论条件是否成立,最后一条 print() 都会执行。

我们可以把代码的执行结构理解为:

flowchart TD
    A["开始"] --> B["cpu = 85"]
    B --> C{"cpu > 80 ?"}
    C -->|是| D["打印 CPU 使用率过高"]
    D --> E["打印检查提示"]
    C -->|否| F["跳过 if 代码块"]
    E --> G["打印监控程序执行完毕"]
    F --> G

这也是为什么学习 Python 时,不能只关注每一行代码写了什么,还要关注它的缩进位置。

为什么一个缩进错误就可能导致程序报错?

先看一段错误代码:

1
2
3
4
cpu = 85

if cpu > 80:
print("CPU 使用率过高")

运行后,Python 会报出类似下面的错误:

1
2
3
4
  File "<python-input-3>", line 4
print("CPU 使用率过高")
^^^^^
IndentationError: expected an indented block after 'if' statement on line 3

原因很简单:if 后面需要一个代码块,但是 Python 没有找到正确缩进的语句。

再看一个例子:

1
2
3
4
5
cpu = 85

if cpu > 80:
print("CPU 使用率过高")
print("请检查服务器负载")

第二条 print() 比第一条多缩进了一层,但前面并没有新的代码块来接收它,因此可能出现:

1
2
3
  File "<python-input-5>", line 5
print("请检查服务器负载")
IndentationError: unexpected indent

注意,并不是少一个空格就一定会报错。真正的要求是:同一代码块中的语句必须具有一致的缩进层级,新的缩进层级必须符合语法结构。

例如,下面的代码也是错误的:

1
2
3
if cpu > 80:
print("CPU 使用率过高")
print("请检查服务器负载")

输出

1
IndentationError: unindent does not match any outer indentation level

同一个代码块中的两条语句缩进不一致,Python 无法将它们正确归入同一语句组。

Python 推荐使用 4 个空格

根据 PEP 8,也就是 Python 官方推荐的代码风格指南,每一级缩进推荐使用 4 个空格。

例如:

1
2
3
4
5
if cpu > 80:
print("CPU 使用率过高")

if cpu > 95:
print("CPU 使用率达到危险级别")

这里出现了两个层级:

  • 第一层 if 下面的语句缩进 4 个空格。
  • 第二层 if 下面的语句缩进 8 个空格。

对于初学者,建议遵守以下规范:

  1. 每一级缩进使用 4 个空格。
  2. 不要在同一个代码块中混用 Tab 和空格。
  3. 使用 VS Code、PyCharm 等编辑器时,开启自动缩进。
  4. 不要为了让代码看起来对齐,随意修改某一行的缩进。

Tab 键不一定等于 4 个空格。编辑器可以将一个 Tab 显示成多个空格宽度,但它在文件中仍然可能是一个制表符。如果同一个代码块混用了 Tab 和空格,Python 可能报出 TabError。

对于 Linux 运维脚本,建议从一开始就统一使用 4 个空格。这样代码更容易阅读,也更便于多人协作。

认识复合语句:冒号到底有什么用?

在 Python 中,if、for、while、def 等语句都可以引出一个代码块。这样的结构称为复合语句。

复合语句通常由两部分组成:

  • 标头(header):例如 if cpu > 80:。
  • 语句组(suite):也就是标头下面缩进的代码。

例如:

1
2
3
if cpu > 80:
print("CPU 使用率过高")
print("请检查服务器负载")

这里的冒号 : 表示标头结束,后面开始一个语句组。

如果遗漏冒号:

1
2
if cpu > 80
print("CPU 使用率过高")

Python 会报出 SyntaxError,因为条件判断的语法不完整。

1
2
3
4
  File "<python-input-10>", line 1
if cpu > 80
^
SyntaxError: expected ':'

所以,看到 if、elif 或 else 时,可以先检查两个地方:

  • 标头末尾有没有冒号。
  • 下一行的缩进是否正确。

对于刚开始学习 Python 的同学,这两项检查能够解决不少常见问题。

if:让程序根据条件作出判断

前面我们已经知道,缩进决定了代码块的范围。那么,代码块什么时候执行呢?

答案是由条件表达式决定。

if 的基本语法如下:

1
2
3
if condition:
statement_1
statement_2

当 condition 的结果为真时,执行缩进在 if 下面的语句;当条件为假时,跳过这个代码块,继续执行后面的代码。

先看一个最简单的例子。

1
2
3
4
5
6
cpu = 85

if cpu > 80:
print("CPU 使用率过高")

print("监控程序执行完毕")

输出

1
2
CPU 使用率过高
监控程序执行完毕

因为 85 > 80 成立,所以第一条 print() 会执行,最后一条 print() 也会执行。

如果将 cpu 修改为 50,第一条 print() 就会被跳过,但监控程序执行完毕这句话仍然会输出。

Python 支持哪些比较运算符?

编写条件判断时,经常会使用比较运算符。

运算符含义示例
==等于cpu == 80
!=不等于cpu != 80
>大于cpu > 80
<小于cpu < 80
>=大于或等于cpu >= 80
<=小于或等于cpu <= 80

需要特别注意,= 和 == 的含义不同。

1
cpu = 80

这里的 = 用于赋值,将 80 赋给变量 cpu。

而:

1
cpu == 80

这里的 == 用于比较,判断 cpu 是否等于 80,结果是一个布尔值 True 或 False。

初学者经常会将二者混淆,尤其是在编写复杂条件时。

为什么建议先赋值,再统一打印?

我们来看一个与运维监控有关的例子。

假设我们输入一个数字,并判断它与目标值 50 的关系。

第一种写法:

1
2
3
4
5
6
7
number = int(input("请输入一个整数:"))
target = 50

if number < target:
print(number, "小于", target)
else:
print(number, "大于或等于", target)

这段代码完全正确,但两条分支都调用了一次 print()。

如果后续还要输出判断结果、记录日志、生成监控报告,就可能出现更多重复代码。

我们可以将变化的内容先保存到变量中:

1
2
3
4
5
6
7
8
9
number = int(input("请输入一个整数:"))
target = 50

if number < target:
result = "小于"
else:
result = "大于或等于"

print(number, result, target)

这样,无论进入哪个分支,最后都只需要调用一次 print()。

例如,输入 30:

1
2
请输入一个整数:30
30 小于 50

输入 70:

1
2
请输入一个整数:70
70 大于或等于 50

这种写法体现了一个值得养成的编程习惯:当多个分支最终需要完成相同的操作时,可以先计算出不同的结果,再统一执行公共操作。

这与 DRY 原则有关。DRY 是 Don’t Repeat Yourself 的缩写,强调尽量减少不必要的重复。

当然,并不是所有场景都必须先赋值再打印。如果不同分支需要执行完全不同的操作,直接在分支内部执行也很合理。关键是让代码结构清晰,而不是机械地追求少写一行。

elif:多个条件应该怎么判断?

如果只有两种情况,使用 if 和 else 通常就够了。

但在实际运维工作中,我们经常需要区分多种状态。

例如:

  • CPU 使用率低于 50%:正常。
  • CPU 使用率达到 50%,但低于 80%:注意观察。
  • CPU 使用率达到 80%,但低于 95%:严重。
  • CPU 使用率达到或超过 95%:危险。

如果只使用 if 和 else,代码就会变得复杂。这时可以使用 elif。

elif 是 else if 的缩写,表示前面的条件不成立时,继续判断另一个条件。

基本语法:

1
2
3
4
5
6
7
8
if condition_1:
statement_1
elif condition_2:
statement_2
elif condition_3:
statement_3
else:
statement_4

执行过程可以概括为:

  1. 首先检查 condition_1。
  2. 如果成立,执行第一段代码,跳过其余分支。
  3. 如果不成立,继续检查 condition_2。
  4. 按照相同的方式依次判断后续的 elif。
  5. 如果所有条件都不成立,并且存在 else,就执行 else 代码块。

这里有一个非常重要的特点:在同一组 if、elif、else 结构中,最多只有一个分支会被执行。

一个完整的多分支示例

1
2
3
4
5
6
7
8
9
10
value = int(input("请输入一个整数:"))

if value <= 5:
print("小于或等于 5")
elif value <= 10:
print("大于 5,小于或等于 10")
elif value <= 15:
print("大于 10,小于或等于 15")
else:
print("大于 15")

输入 12,运行结果:

1
2
请输入一个整数:12
大于 10,小于或等于 15

为什么程序没有继续判断后面的条件?

因为 value <= 15 成立后,Python 就执行对应的代码块,并跳过剩余分支。

这里还有一个值得注意的地方:第二个条件只写了 value <= 10,没有写 value > 5。

这是因为程序只有在第一个条件 value <= 5 不成立时,才会继续判断第二个条件。此时已经可以确定 value > 5。

同理,第三个条件也不必再写 value > 10。

这种写法能够让代码更加简洁,但它依赖于分支的判断顺序。如果将条件顺序随意调换,程序的结果就可能发生变化。

为什么条件顺序如此重要?

看下面的错误示例:

1
2
3
4
5
6
7
8
9
10
cpu = 96

if cpu >= 50:
level = "正常"
elif cpu >= 80:
level = "严重"
elif cpu >= 95:
level = "危险"

print(level)

你可能会认为,CPU 使用率为 96,应该输出危险。

但实际输出是:

1
正常

为什么?

因为 96 >= 50 已经成立,Python 进入第一个分支后,就不会继续检查后面的 elif。

因此,多分支判断必须考虑条件之间的包含关系。对于这种从低到高的阈值判断,应该从较低阈值开始使用递增上界,或者按照从高到低的顺序判断。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
cpu = 96

if cpu >= 95:
level = "危险"
elif cpu >= 80:
level = "严重"
elif cpu >= 50:
level = "警告"
else:
level = "正常"

print(level)

输出:

1
危险

这次程序会先检查最高阈值,命中后立即结束整个分支结构。

实际编写监控程序时,建议先明确每个状态的范围,再决定判断顺序。不要只看条件本身是否正确,还要检查前面的条件会不会提前截获本该属于后面分支的数据。

else:处理剩下的情况

else 用于处理前面所有条件都不成立的情况。

例如:

1
2
3
4
5
6
cpu = 35

if cpu >= 80:
print("CPU 使用率过高")
else:
print("CPU 使用率未达到告警阈值")

输出

1
CPU 使用率未达到告警阈值

由于 cpu >= 80 不成立,程序执行 else 分支。

需要注意,else 后面不需要再写条件,但必须加冒号。

另外,else 不是必需的。如果程序只关心某个条件成立时该做什么,也可以只使用 if:

1
2
3
4
cpu = 85

if cpu >= 80:
print("CPU 使用率过高")

输出

1
CPU 使用率过高

当 CPU 使用率低于 80% 时,程序什么也不做,然后继续执行后面的代码。

这在运维脚本中非常常见。例如,只有发现异常时才发送告警,正常情况下不输出额外信息。

运维实战:编写一个 CPU 使用率分级程序

前面的示例都是为了理解语法。现在,我们将 if、elif 和 else 组合起来,写一个更接近实际工作的 CPU 使用率分级程序。

第一版:实现基本分级

1
2
3
4
5
6
7
8
9
10
11
12
13
cpu = float(input("请输入 CPU 使用率:"))

if cpu < 50:
level = "正常"
elif cpu < 80:
level = "警告"
elif cpu < 95:
level = "严重"
else:
level = "危险"

print(f"CPU 使用率:{cpu}%")
print(f"当前状态:{level}")

运行示例:

1
2
3
请输入 CPU 使用率:85
CPU 使用率:85.0%
当前状态:严重

这里有两个新知识点。

首先,float() 可以将符合格式的数字字符串转换为浮点数,因此我们可以输入 85.5 这样的数值。

其次,最后两条语句使用了 f-string,也就是格式化字符串。f"CPU 使用率:{cpu}%" 中的 {cpu} 会被替换为变量的值。

这种方式非常适合输出监控信息、日志内容和运行报告。

为什么这个例子不需要写完整的范围?

你可能发现,代码没有写成:

1
if cpu >= 0 and cpu < 50:

而是直接使用:

1
if cpu < 50:

这是因为 elif 只有在前面条件不成立时才会执行。

对于输入 85:

  • cpu < 50 不成立。
  • cpu < 80 不成立。
  • cpu < 95 成立。

因此,程序会将状态设置为“严重”。

不过,这段代码有一个前提:我们假设输入的 CPU 使用率是合法的,且处于 0 到 100 之间。真实的运维程序不能只考虑正常输入,还应该处理非法输入。

第二版:增加输入合法性检查

例如,用户可能输入:

  • abc:不是有效的数字。
  • -10:小于合法范围。
  • 120:超过 CPU 使用率的正常取值范围。

我们可以先将类型转换放在 try 代码块中,再进行范围检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try:
cpu = float(input("请输入 CPU 使用率:"))

if not 0 <= cpu <= 100:
print("输入错误:CPU 使用率必须在 0 到 100 之间")
else:
if cpu < 50:
level = "正常"
elif cpu < 80:
level = "警告"
elif cpu < 95:
level = "严重"
else:
level = "危险"

print(f"CPU 使用率:{cpu}%")
print(f"当前状态:{level}")

except ValueError:
print("输入错误:请输入有效的数字")

这段代码同时引入了 try 和 except,用于捕获类型转换时可能发生的 ValueError。

例如,输入 abc 时,float() 无法完成转换,程序就会执行 except 中的错误提示,而不是直接抛出异常并终止。

而输入 120 时,类型转换本身没有问题,但范围检查会发现这个值超出了允许范围。

这说明两件事情并不相同:

  • 类型合法性:输入的内容能不能转换成数字。
  • 业务合法性:转换后的数字是否符合程序要求。

真实的监控程序通常需要同时检查这两项。

边界值测试:50%、80%、95% 到底属于哪个状态?

写阈值判断时,最容易遗漏的问题之一就是边界值。

我们将 CPU 分级规则整理成表格:

CPU 使用率判断结果
0%正常
49.9%正常
50%警告
79.9%警告
80%严重
94.9%严重
95%危险
100%危险

注意,cpu < 50 与 cpu <= 50 的区别非常重要。

  • cpu < 50 不包含 50。
  • cpu <= 50 包含 50。

如果没有明确边界条件,就可能出现某个数值被分到错误的级别。

在生产环境中,建议至少测试每个阈值的前一个值、阈值本身和后一个值。对于浮点数,可以使用 49.9、50、50.1 这样的测试数据。

还要注意,示例中的 0 到 100 范围是针对 CPU 百分比的简化假设。真实监控系统可能会因为采样方式、容器 CPU 配额或指标定义不同而使用其他判断规则。

Linux 运维中的实际应用

掌握了条件判断,就可以开始思考:它到底能帮助我们完成哪些运维任务?

例如,日常工作中经常需要检查磁盘使用率、内存使用率、服务状态和系统负载。

根据磁盘使用率决定是否告警

假设我们已经从监控系统中获取了某个文件系统的使用率:

1
2
3
4
5
6
7
8
disk_usage = 92

if disk_usage >= 95:
print("严重告警:磁盘空间即将耗尽")
elif disk_usage >= 85:
print("警告:磁盘使用率较高")
else:
print("磁盘使用率正常")

输出

1
警告:磁盘使用率较高

如果磁盘使用率达到 92%,程序会输出警告。

这里需要注意,示例中的 disk_usage = 92 是模拟数据,并没有真正读取 Linux 文件系统的使用率。

在实际项目中,我们可以进一步使用 Python 的 subprocess 模块调用 df -h,或者使用 shutil.disk_usage() 获取文件系统容量信息,再根据结果进行判断。

根据服务状态决定下一步操作

假设我们通过其他方式获得了 Nginx 的运行状态:

1
2
3
4
5
6
service_status = "inactive"

if service_status == "active":
print("Nginx 正常运行")
else:
print("Nginx 未处于运行状态,请检查服务")

输出

1
Nginx 未处于运行状态,请检查服务

这个示例演示了字符串比较。

真实环境中,服务状态可以通过 systemctl is-active nginx 获取。之后,程序就可以根据检查结果记录日志、发送告警,或者触发进一步的诊断流程。

但需要特别注意:发现服务异常,并不意味着应该立即自动重启服务。 生产环境还需要考虑故障原因、重启策略、重试次数和业务影响,避免错误的自动化操作扩大故障。

为什么缩进错误在运维脚本中尤其危险?

假设你编写了一个批量检查服务器的程序,希望只有在发现磁盘空间不足时才输出告警。

如果条件判断写错,可能出现两种结果:

  • 正常服务器也不断产生告警,导致大量误报。
  • 真正发生异常时,没有执行告警逻辑,导致问题未被及时发现。

当脚本进一步涉及删除文件、重启服务或批量修改配置时,错误的条件和缩进还可能产生更严重的后果。

因此,运维脚本不能只关注能否运行,还要关注条件覆盖是否完整、边界值是否正确,以及每一条操作是否处于正确的代码块中。

if、elif、else 的几个常见误区

把多个独立的 if 当成一个多分支结构

看下面的代码:

1
2
3
4
5
6
7
number = 12

if number > 5:
print("大于 5")

if number > 10:
print("大于 10")

输出:

1
2
大于 5
大于 10

这是因为两个 if 是相互独立的判断,条件成立时都可以执行。

而使用 if 和 elif:

1
2
3
4
5
6
number = 12

if number > 5:
print("大于 5")
elif number > 10:
print("大于 10")

输出:

1
大于 5

第一个条件已经成立,所以第二个分支不会再执行。

因此,选择 if 还是 elif,取决于业务逻辑:你是希望多个条件分别判断,还是只允许其中一个分支执行?

认为 else 必须跟在每个 if 后面

这是错误的。

下面的代码完全合法:

1
2
3
4
5
6
cpu = 90

if cpu >= 80:
print("CPU 使用率过高")

print("检查完成")

输出

1
2
CPU 使用率过高
检查完成

如果条件不成立,程序就跳过 if 代码块,不需要额外的 else。

忘记缩进会影响哪些语句执行

例如:

1
2
3
4
5
6
7
cpu = 90

if cpu >= 80:
print("CPU 使用率过高")
print("需要进一步检查")

print("监控完成")

输出

1
2
3
CPU 使用率过高
需要进一步检查
监控完成

前两条打印语句受 if 控制,最后一条打印语句不受它控制。

阅读代码时,可以尝试从 if 的标头向下观察,直到遇到缩进层级回到外层的语句。这样就能判断哪些操作属于当前分支。

认为 elif 是 Python 唯一的多分支选择方式

在 Python 3.10 及更高版本中,还可以使用 match 和 case 处理某些多分支场景。

例如:

1
2
3
4
5
6
7
8
9
10
11
status = "warning"

match status:
case "normal":
print("运行正常")
case "warning":
print("需要关注")
case "critical":
print("严重告警")
case _:
print("未知状态")

输出

1
需要关注

这里的 match 更适合根据值的模式进行匹配。对于 CPU 数值阈值这种需要比较大小的场景,使用 if 和 elif 往往更直观。

对于初学者,建议先熟练掌握 if、elif 和 else,再学习 match。

总结:让程序真正具备判断能力

今天我们学习了 Python 中非常重要的决策结构。

首先,缩进决定代码块的范围。Python 使用缩进表达语句之间的层级关系,推荐每一级使用 4 个空格。

其次,if 用于判断条件是否成立,elif 用于在前面的条件不成立时继续判断其他条件,else 用于处理剩余情况。

最后,在编写实际程序时,不仅要保证语法正确,还要注意条件顺序、边界值和输入合法性。

对于 Linux 运维人员来说,这些知识可以帮助我们逐步编写自动化检查脚本,例如磁盘空间告警、服务状态检查和 CPU 使用率分级。

不过,目前我们的程序仍然需要手动输入数据,或者使用提前写好的模拟值。下一步,我们将学习如何使用 Python 获取真实的系统信息,并把条件判断与 Linux 命令、系统接口结合起来。

当程序能够主动获取数据、判断状态并采取合适的行动时,它才真正开始具备运维自动化的能力。