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 的缩进规范,以及 if、elif、else 决策结构,已经能够让程序根据不同条件执行不同操作。

但在实际工作中,一个判断条件往往没有那么简单。

比如,我们希望只有当 CPU 使用率超过 80%,并且内存使用率也超过 90% 时,才触发告警;又比如,我们希望日志文件存在且内容满足特定条件时,才继续处理日志。

这时候,仅仅掌握 if 还不够,我们还需要学习关系运算符和逻辑运算符。

更有意思的是,Python 还具备一种叫作“短路求值”的特性:某些情况下,表达式的后半部分甚至不会执行。

今天我们就从关系运算符开始,一步步搞懂 and、or、not、is,再结合 Linux 运维场景,看看这些知识究竟有什么用。

关系运算符:判断两个值之间的关系

在编写程序时,我们经常需要比较两个值。

例如:

  • CPU 使用率是否超过 80%?
  • 磁盘使用率是否达到 95%?
  • 当前内存使用量是否大于某个阈值?
  • 服务状态是否等于 active?

这些判断都可以使用关系运算符或相等性比较运算符完成。

Python 常见的比较运算符

运算符含义示例结果
<小于10 < 20True
<=小于或等于10 <= 10True
>大于20 > 10True
>=大于或等于20 >= 20True
==等于10 == 20False
!=不等于10 != 20True

先看一个简单的例子:

1
2
3
4
5
6
x = 10
y = 20

print(x < y)
print(x == y)
print(x != y)

运行结果:

1
2
3
True
False
True

这是因为:

  • x < y 判断 10 是否小于 20,结果为真。
  • x == y 判断两个值是否相等,结果为假。
  • x != y 判断两个值是否不相等,结果为真。

这些比较表达式的结果都是布尔值,也就是 True 或 False。

注意:= 和 == 不是一回事

这是初学者最容易犯的错误之一。

1
cpu = 85

这里的 = 表示赋值,将 85 赋给变量 cpu。

而:

1
cpu == 85

这里的 == 才表示比较,判断变量 cpu 的值是否等于 85。

如果你在 if 条件中误把 == 写成 =,Python 会报出语法错误。

1
2
3
4
cpu = 85

if cpu = 85:
print("CPU 使用率为 85%")

输出

1
2
3
4
  File "<python-input-38>", line 3
if cpu = 85:
^^^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

这段代码无法正常执行,因为 if 后面需要的是一个表达式,而不是这种赋值语句。

正确写法:

1
2
3
4
cpu = 85

if cpu == 85:
print("CPU 使用率为 85%")

输出

1
CPU 使用率为 85%

在编写监控脚本时,一定要区分赋值与比较。否则,程序可能无法运行,或者判断逻辑与预期不一致。

关系运算符可以用于字符串比较吗?

可以。

例如,检查 Linux 服务的状态:

1
2
3
4
5
6
status = "active"

if status == "active":
print("服务正在运行")
else:
print("服务状态异常")

输出

1
服务正在运行

这里比较的是两个字符串是否相等。

需要注意,字符串比较区分大小写:

1
print("active" == "Active")

结果为:

1
False

如果数据来源不统一,例如某些接口返回大写状态、另一些接口返回小写状态,就需要先明确数据格式,再决定是否进行大小写转换。

不要为了让判断通过而随意忽略大小写,因为某些业务场景中的大小写本身就有意义。

Python 还支持连续比较

例如:

1
2
3
4
cpu = 65

if 50 <= cpu < 80:
print("CPU 使用率处于警告区间")

输出

1
CPU 使用率处于警告区间

这段代码可以理解为:

1
2
if cpu >= 50 and cpu < 80:
print("CPU 使用率处于警告区间")

输出

1
CPU 使用率处于警告区间

两种写法都可以。

Python 支持这种连续比较,因此在表达数值范围时,50 <= cpu < 80 往往更简洁,也更容易阅读。

注意,比较的是数值范围,不是字符串范围。如果 cpu 是从 input() 直接获取的字符串,就应该先将其转换为适当的数值类型。

== 与 is:值相等不代表是同一个对象

除了数值大小和相等性比较,Python 还有两个经常让初学者混淆的运算符:

  • ==:比较两个对象的值是否相等。
  • is:判断两个引用是否指向同一个对象,也就是对象身份是否相同。

先看一个例子:

1
2
3
4
5
a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)
print(a is b)

运行结果:

1
2
True
False

为什么会这样?

因为 a 和 b 是分别创建的两个列表。它们包含相同的元素,因此 a == b 为 True。

但它们是两个不同的列表对象,所以 a is b 为 False。

可以用下面的图理解:

flowchart TD
    A["变量 a"] --> C["列表对象 A<br/>[1, 2, 3]"]
    B["变量 b"] --> D["列表对象 B<br/>[1, 2, 3]"]
    C -.-> E["值相等:a == b 为 True"]
    D -.-> E
    C -.-> F["对象不同:a is b 为 False"]
    D -.-> F

如果我们让两个变量引用同一个列表:

1
2
3
4
5
a = [1, 2, 3]
b = a

print(a == b)
print(a is b)

结果就是:

1
2
True
True

此时,b = a 并没有创建一个新的列表,而是让 b 指向与 a 相同的对象。

运维代码中什么时候使用 is?

最常见的场景之一,就是检查一个值是不是 None。

1
2
3
4
result = None

if result is None:
print("没有获取到结果")

输出

1
没有获取到结果

这里推荐使用:

1
result is None

输出

1
True

而不是:

1
result == None

因为 is None 明确检查对象身份,表达的意思也更清晰。

相应地,检查不是 None 时,可以写成:

1
2
if result is not None:
print("已经获取到结果")

在 Python 中,None 是表示缺失值或无结果的特殊对象,通常用于表达“没有值”。

需要牢记:比较两个数字或字符串的值时,使用 ==;检查是否为 None 时,推荐使用 is None。

不要使用 is 来判断两个字符串或数字是否相等。某些简单例子中,is 可能碰巧返回 True,但那不是应该依赖的行为。

逻辑运算符:把多个条件组合起来

前面我们已经知道,比较表达式可以得到 True 或 False。

但实际工作中的判断经常需要组合多个条件。

例如:

  • CPU 使用率超过 80%,并且内存使用率超过 90%。
  • 磁盘使用率超过 95%,或者文件系统已经只读。
  • 服务没有运行,或者检查结果没有获取成功。

Python 提供了三个逻辑运算符:

运算符含义典型用途
and逻辑与所有条件都需要满足
or逻辑或至少一个条件满足
not逻辑非反转真值判断

and:多个条件必须同时满足

例如:

1
2
3
4
5
cpu = 85
memory = 95

if cpu > 80 and memory > 90:
print("CPU 和内存使用率都过高")

运行结果:

1
CPU 和内存使用率都过高

这里有两个条件:

  • cpu > 80,结果为 True。
  • memory > 90,结果也为 True。

由于两个条件都成立,整个判断成立。

如果把内存使用率修改为 70:

1
2
3
4
5
cpu = 85
memory = 70

if cpu > 80 and memory > 90:
print("CPU 和内存使用率都过高")

程序不会输出任何内容,因为第二个条件不成立。

and 的核心规则是:只有当两侧的操作数都为真时,整个逻辑与表达式才为真。

or:只要一个条件满足即可

再看另一个例子:

1
2
3
4
5
cpu = 95
memory = 60

if cpu > 90 or memory > 90:
print("至少有一项资源使用率过高")

运行结果:

1
至少有一项资源使用率过高

这里 CPU 使用率已经超过 90%,即使内存使用率没有超过 90%,整个条件仍然成立。

or 适用于多个异常条件中任意一个出现就需要采取行动的场景。

例如:

1
2
if cpu > 90 or disk_usage > 95:
print("触发资源告警")

运行结果:

1
触发资源告警

只要 CPU 或磁盘使用率达到对应的阈值,就会触发告警。

not:反转条件的真值

not 用于对一个表达式的真值取反。

例如:

1
2
3
4
service_running = False

if not service_running:
print("服务未运行")

因为 service_running 为假,not service_running 就为真,因此程序输出:

1
服务未运行

not 也可以用于判断一个条件是否不成立:

1
2
3
4
cpu = 30

if not cpu > 80:
print("CPU 使用率没有超过 80%")

运行结果:

1
CPU 使用率没有超过 80%

这段代码可以理解为:如果 cpu > 80 不成立,就输出提示。

不过,编写简单的范围判断时,直接写成下面这样通常更清晰:

1
2
if cpu <= 80:
print("CPU 使用率没有超过 80%")

阅读代码时,尽量使用直观、明确的表达式,避免连续叠加多个 not,让判断变得难以理解。

短路求值:为什么后面的表达式可能不会执行?

现在来到本文最重要的部分:短路求值。

我们已经知道:

  • and 要求两个操作数都为真。
  • or 只要求至少一个操作数为真。

但 Python 并不总是计算两个操作数。

为了确定整个表达式的结果,Python 会根据已经计算出的结果,决定是否有必要继续计算后面的操作数。这种行为称为短路求值(short-circuit evaluation)。

and 的短路特性

先看下面的代码:

1
2
3
4
5
6
x = 0

if x != 0 and 100 / x > 10:
print("条件成立")
else:
print("条件不成立")

运行结果:

1
条件不成立

注意,这段代码并没有报出除零错误。

为什么?

程序先计算:

1
x != 0

由于 x 为 0,结果是 False。

对于 and 来说,只要左侧已经为假,整个表达式就不可能为真,因此没有必要继续计算右侧的:

1
100 / x > 10

Python 直接确定整个表达式为假,跳过右侧的计算。

这就是 and 的短路特性:

当左侧操作数为假时,右侧操作数不会被计算。

这不仅可以减少不必要的计算,还可以避免某些表达式在特定条件下产生错误。

不过,如果将条件顺序调换:

1
2
3
4
x = 0

if 100 / x > 10 and x != 0:
print("条件成立")

程序就会报出 ZeroDivisionError,因为除法先执行了。

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-70>", line 3, in <module>
if 100 / x > 10 and x != 0:
~~~~^~~
ZeroDivisionError: division by zero

所以,在使用 and 保护可能产生错误的操作时,必须将前置检查放在左侧。

or 的短路特性

or 的行为恰好对应另一种情况。

1
2
3
4
x = 10

if x > 0 or 100 / 0 > 10:
print("条件成立")

运行结果:

1
条件成立

这里同样没有发生除零错误。

因为 x > 0 已经为真,整个 or 表达式必然为真,Python 没有必要再计算右侧的表达式。

or 的短路规则是:

当左侧操作数为真时,右侧操作数不会被计算。

我们可以用一张表来总结:

表达式左侧结果是否计算右侧
False and ...假不计算
True and ...真需要计算
True or ...真不计算
False or ...假需要计算

这里的“需要计算”是指 Python 为了确定表达式结果,需要继续计算右侧操作数;如果左侧已经决定了整个表达式的结果,就会短路。

为什么日志文件检查经常使用 and?

我们来看一个 Linux 运维场景。

假设变量 log_file 保存的是某个日志文件路径,但在某些情况下,程序可能没有获取到路径,变量的值是 None。

如果直接调用:

1
2
3
4
log_file = None

if log_file.startswith("ERROR"):
print("发现错误日志")

程序会报出 AttributeError,因为 None 对象没有 startswith() 这个字符串方法。

输出

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-73>", line 3, in <module>
if log_file.startswith("ERROR"):
^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'startswith'

可以先判断变量是否存在有效值:

1
2
3
4
log_file = None

if log_file and log_file.startswith("ERROR"):
print("发现错误日志")

此时,左侧的 log_file 为假值,Python 会直接停止计算,不会调用 startswith(),因此避免了异常。

不过,这里需要明确一个细节:这个例子中的 log_file 实际上被当作字符串内容使用。如果它保存的是文件路径,那么 startswith("ERROR") 检查的是路径字符串的开头,并不能检查日志文件的内容。

如果我们想检查一行日志是否以 ERROR 开头,应该写成:

1
2
3
4
log_line = "ERROR: disk space is low"

if log_line and log_line.startswith("ERROR"):
print("发现错误日志")

运行结果:

1
发现错误日志

这时,短路求值能够保证:只有当 log_line 为真值时,才继续调用字符串方法。

需要注意,log_line and log_line.startswith("ERROR") 的结果不一定是布尔值。当 log_line 为 None 或空字符串时,整个表达式可能直接返回 None 或空字符串;在 if 条件中,它们都会被视为假。

短路求值不等于异常处理

虽然短路求值可以避免某些错误,但它不能代替完整的异常处理。

例如:

1
2
3
4
log_file = "/var/log/messages"

if log_file and log_file.startswith("ERROR"):
print("发现错误日志")

这段代码只检查路径字符串是否以 ERROR 开头,并没有真正读取文件。

即使变量非空,也不代表文件一定存在、可读,或者内容符合预期。

因此,在实际运维脚本中,需要根据任务分别考虑:

  • 使用条件判断排除不应该执行的操作。
  • 使用短路求值避免不必要或不安全的表达式。
  • 对文件读取、网络请求和系统命令等可能失败的操作,采用适当的异常处理或错误码检查。

这三者解决的问题不同,不应该互相替代。

真值判断:为什么空字符串也能用于 if?

在前面的代码中,我们写过:

1
2
if log_line:
print("日志内容不为空")

输出

1
日志内容不为空

但 log_line 明明是一个字符串,为什么可以直接放在 if 后面?

这是因为 Python 支持真值判断(truth value testing)。

在条件语句中,Python 不要求表达式必须直接返回布尔值。许多对象都可以根据其真值被判断为真或假。

哪些值是假值?

Python 中常见的假值包括:

值说明
False布尔假值
None表示没有值
0整数零
0.0浮点数零
""空字符串
[]空列表
()空元组
{}空字典
set()空集合

例如:

1
2
3
4
print(bool(0))
print(bool(""))
print(bool([]))
print(bool(None))

运行结果:

1
2
3
4
False
False
False
False

这里的 bool() 用于将一个值转换为布尔值,以便观察它的真值。

需要注意:不是所有数值都是假值。非零数值通常为真,包括负数;非空字符串、非空列表等通常也为真。

例如:

1
2
3
print(bool(-1))
print(bool("0"))
print(bool([0]))

运行结果:

1
2
3
True
True
True

为什么字符串 "0" 是真值?

因为它是一个非空字符串。Python 不会自动把它转换为数字零。

同理,[0] 是一个包含一个元素的列表,因此也不是空列表。

真值判断在运维脚本中有什么用?

假设我们获取了一组异常服务器列表:

1
2
3
4
5
6
servers = ["server01", "server02"]

if servers:
print("发现需要处理的服务器")
else:
print("没有需要处理的服务器")

因为列表中有两个元素,servers 为真值,因此输出:

1
发现需要处理的服务器

如果将其修改为空列表:

1
2
3
4
5
6
servers = []

if servers:
print("发现需要处理的服务器")
else:
print("没有需要处理的服务器")

程序就会输出:

1
没有需要处理的服务器

这种写法在处理服务器列表、日志行、API 返回的数据以及待执行任务列表时都很实用。

但也要注意:空值判断不等于业务有效性判断。

例如,列表 [None] 并不是空列表,因此它的真值为真;但列表中的元素可能并不符合业务要求。

因此,如果程序要求列表中的每一项都必须是有效服务器名称,就需要进一步检查元素,而不能只判断列表是否为空。

逻辑运算符不一定返回 True 或 False

这是 Python 中一个非常值得掌握的细节。

很多初学者认为:

1
a and b

和:

1
a or b

一定会返回 True 或 False。

实际上并不是这样。

在 Python 中,and 和 or 会根据短路规则返回其中一个操作数本身,而不是强制把结果转换为布尔值。

and 返回什么?

1
2
print("hello" and "world")
print("" and "world")

运行结果:

1
2
world

第一行中,"hello" 是真值,因此 Python 继续计算右侧,并返回 "world"。

第二行中,空字符串是假值,因此 Python 直接返回左侧的空字符串,不再计算右侧。

or 返回什么?

1
2
print("hello" or "world")
print("" or "world")

运行结果:

1
2
hello
world

第一行中,"hello" 已经为真,因此 Python 直接返回左侧的 "hello"。

第二行中,左侧为空字符串,为假值,因此 Python 继续计算右侧并返回 "world"。

可以记住下面两条规则:

  • A and B:如果 A 为假,返回 A;否则返回 B。
  • A or B:如果 A 为真,返回 A;否则返回 B。

这里的规则针对 Python 的 and、or 表达式。它们既能用于布尔值,也能用于其他具有真值的对象。

理解这一点后,你就能更好地理解为什么 log_line and log_line.startswith("ERROR") 在 log_line 为空时,可能返回空字符串,而不是明确的 False。

如果希望得到严格的布尔值,可以使用 bool(),或者直接在条件语句中使用表达式。

运维实战:组合条件判断 CPU、内存和磁盘状态

前面学习了各种运算符,现在我们将它们组合起来,编写一个简单的资源告警程序。

假设我们已经从监控系统中获得了 CPU、内存和磁盘使用率:

1
2
3
4
5
6
7
8
9
10
11
12
cpu = 85
memory = 92
disk = 96

if cpu >= 80 and memory >= 90:
print("告警:CPU 和内存使用率都过高")

if disk >= 95:
print("告警:磁盘使用率过高")

if cpu >= 95 or memory >= 95 or disk >= 95:
print("严重告警:至少一项资源达到严重阈值")

运行结果:

1
2
3
告警:CPU 和内存使用率都过高
告警:磁盘使用率过高
严重告警:至少一项资源达到严重阈值

这里有三个独立的 if,因为它们代表三个可以同时成立的告警规则。

这与 if、elif 结构不同:多个独立的 if 可以分别执行,而 if、elif、else 结构最多只会执行其中一个分支。

这也是运维告警中非常重要的区别。

如果我们把第二个和第三个 if 改为 elif,当第一个条件成立后,后续的告警规则就可能不再执行,从而导致漏报。

当然,真实监控系统还需要考虑指标采集失败、阈值配置、告警去重和恢复通知等问题。这里的代码只是用来说明逻辑运算符与多条独立规则的组合方式。

几个常见错误与注意事项

用 is 判断数字是否相等

错误示例:

1
2
3
4
cpu = 80

if cpu is 80:
print("CPU 使用率为 80%")

输出

1
2
<python-input-93>:3: SyntaxWarning: "is" with 'int' literal. Did you mean "=="?
CPU 使用率为 80%

这里应该使用:

1
2
if cpu == 80:
print("CPU 使用率为 80%")

输出

1
CPU 使用率为 80%

is 用于判断对象身份,而不是判断数值是否相等。

把两个独立的 if 误认为 and

例如:

1
2
3
4
5
6
7
8
cpu = 90
memory = 60

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

if memory > 90:
print("内存使用率过高")

输出

1
CPU 使用率过高

这两个条件分别判断,互不依赖。

如果需求是 CPU 和内存必须同时超过阈值才告警,就应该写成:

1
2
if cpu > 80 and memory > 90:
print("CPU 和内存使用率都过高")

注意,这不是说 and 一定比两个 if 更安全,而是两种写法表达的业务逻辑不同。应根据需求选择。

忘记 and 和 or 的优先级

例如:

1
2
if cpu > 80 or memory > 90 and disk > 95:
print("触发告警")

输出

1
触发告警

Python 中,not 的优先级高于 and,而 and 的优先级高于 or。

因此,上面的条件等价于:

1
2
if cpu > 80 or (memory > 90 and disk > 95):
print("触发告警")

输出

1
触发告警

也就是说,CPU 超过 80% 就会告警;或者内存和磁盘同时超过阈值时也会告警。

如果实际需求是 CPU 和内存任意一个超过阈值,同时磁盘必须超过 95%,就需要明确写出:

1
2
if (cpu > 80 or memory > 90) and disk > 95:
print("触发告警")

输出

1
触发告警

建议在包含多个 and、or 的复杂表达式中,主动使用括号。这样不仅能减少理解错误,也能让后续维护代码的人更容易确认业务规则。

把空字符串判断当成完整的输入校验

例如:

1
2
3
4
hostname = ""

if hostname:
print("主机名不为空")

这段代码能够判断字符串是否为空,但不能验证主机名是否符合合法格式。

如果要校验用户输入的主机名,还需要进一步检查字符、长度和业务规则。

总结

本篇我们从关系运算符开始,学习了 Python 中的 ==、!=、<、<=、>、>=,以及用于判断对象身份的 is 和 is not。

接着,我们学习了三个逻辑运算符:

  • and:两个条件都满足时,整体条件才为真。
  • or:至少一个条件满足时,整体条件就为真。
  • not:对表达式的真值取反。

随后,我们重点理解了短路求值。and 在左侧为假时会跳过右侧,or 在左侧为真时也会跳过右侧。这一特性既能减少不必要的计算,也可以用来避免某些不安全的操作。

最后,我们学习了 Python 的真值判断,以及为什么空字符串、空列表、None 和数值零在条件语句中通常会被视为假值。

对于 Linux 运维人员来说,这些知识可以直接应用于资源监控、服务状态检查、日志筛选和自动化告警。

当你能够熟练地组合多个条件,并理解短路求值的执行顺序时,就可以开始编写更复杂、更可靠的运维判断逻辑了。

下一步,我们可以继续学习循环结构,让 Python 不仅能够判断一次,还能够批量检查多台服务器、遍历日志记录,并自动处理大量重复任务。