Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
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 的缩进规范,以及
if、elif、else决策结构,已经能够让程序根据不同条件执行不同操作。但在实际工作中,一个判断条件往往没有那么简单。
比如,我们希望只有当 CPU 使用率超过 80%,并且内存使用率也超过 90% 时,才触发告警;又比如,我们希望日志文件存在且内容满足特定条件时,才继续处理日志。
这时候,仅仅掌握
if还不够,我们还需要学习关系运算符和逻辑运算符。更有意思的是,Python 还具备一种叫作“短路求值”的特性:某些情况下,表达式的后半部分甚至不会执行。
今天我们就从关系运算符开始,一步步搞懂
and、or、not、is,再结合 Linux 运维场景,看看这些知识究竟有什么用。
关系运算符:判断两个值之间的关系
在编写程序时,我们经常需要比较两个值。
例如:
- CPU 使用率是否超过 80%?
- 磁盘使用率是否达到 95%?
- 当前内存使用量是否大于某个阈值?
- 服务状态是否等于
active?
这些判断都可以使用关系运算符或相等性比较运算符完成。
Python 常见的比较运算符
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
< | 小于 | 10 < 20 | True |
<= | 小于或等于 | 10 <= 10 | True |
> | 大于 | 20 > 10 | True |
>= | 大于或等于 | 20 >= 20 | True |
== | 等于 | 10 == 20 | False |
!= | 不等于 | 10 != 20 | True |
先看一个简单的例子:
1 | x = 10 |
运行结果:
1 | True |
这是因为:
x < y判断 10 是否小于 20,结果为真。x == y判断两个值是否相等,结果为假。x != y判断两个值是否不相等,结果为真。
这些比较表达式的结果都是布尔值,也就是 True 或 False。
注意:= 和 == 不是一回事
这是初学者最容易犯的错误之一。
1 | cpu = 85 |
这里的 = 表示赋值,将 85 赋给变量 cpu。
而:
1 | cpu == 85 |
这里的 == 才表示比较,判断变量 cpu 的值是否等于 85。
如果你在 if 条件中误把 == 写成 =,Python 会报出语法错误。
1 | cpu = 85 |
输出
1 | File "<python-input-38>", line 3 |
这段代码无法正常执行,因为 if 后面需要的是一个表达式,而不是这种赋值语句。
正确写法:
1 | cpu = 85 |
输出
1 | CPU 使用率为 85% |
在编写监控脚本时,一定要区分赋值与比较。否则,程序可能无法运行,或者判断逻辑与预期不一致。
关系运算符可以用于字符串比较吗?
可以。
例如,检查 Linux 服务的状态:
1 | status = "active" |
输出
1 | 服务正在运行 |
这里比较的是两个字符串是否相等。
需要注意,字符串比较区分大小写:
1 | print("active" == "Active") |
结果为:
1 | False |
如果数据来源不统一,例如某些接口返回大写状态、另一些接口返回小写状态,就需要先明确数据格式,再决定是否进行大小写转换。
不要为了让判断通过而随意忽略大小写,因为某些业务场景中的大小写本身就有意义。
Python 还支持连续比较
例如:
1 | cpu = 65 |
输出
1 | CPU 使用率处于警告区间 |
这段代码可以理解为:
1 | if cpu >= 50 and cpu < 80: |
输出
1 | CPU 使用率处于警告区间 |
两种写法都可以。
Python 支持这种连续比较,因此在表达数值范围时,50 <= cpu < 80 往往更简洁,也更容易阅读。
注意,比较的是数值范围,不是字符串范围。如果 cpu 是从 input() 直接获取的字符串,就应该先将其转换为适当的数值类型。
== 与 is:值相等不代表是同一个对象
除了数值大小和相等性比较,Python 还有两个经常让初学者混淆的运算符:
==:比较两个对象的值是否相等。is:判断两个引用是否指向同一个对象,也就是对象身份是否相同。
先看一个例子:
1 | a = [1, 2, 3] |
运行结果:
1 | True |
为什么会这样?
因为 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 | a = [1, 2, 3] |
结果就是:
1 | True |
此时,b = a 并没有创建一个新的列表,而是让 b 指向与 a 相同的对象。
运维代码中什么时候使用 is?
最常见的场景之一,就是检查一个值是不是 None。
1 | result = None |
输出
1 | 没有获取到结果 |
这里推荐使用:
1 | result is None |
输出
1 | True |
而不是:
1 | result == None |
因为 is None 明确检查对象身份,表达的意思也更清晰。
相应地,检查不是 None 时,可以写成:
1 | if result is not None: |
在 Python 中,None 是表示缺失值或无结果的特殊对象,通常用于表达“没有值”。
需要牢记:比较两个数字或字符串的值时,使用 ==;检查是否为 None 时,推荐使用 is None。
不要使用 is 来判断两个字符串或数字是否相等。某些简单例子中,is 可能碰巧返回 True,但那不是应该依赖的行为。
逻辑运算符:把多个条件组合起来
前面我们已经知道,比较表达式可以得到 True 或 False。
但实际工作中的判断经常需要组合多个条件。
例如:
- CPU 使用率超过 80%,并且内存使用率超过 90%。
- 磁盘使用率超过 95%,或者文件系统已经只读。
- 服务没有运行,或者检查结果没有获取成功。
Python 提供了三个逻辑运算符:
| 运算符 | 含义 | 典型用途 |
|---|---|---|
and | 逻辑与 | 所有条件都需要满足 |
or | 逻辑或 | 至少一个条件满足 |
not | 逻辑非 | 反转真值判断 |
and:多个条件必须同时满足
例如:
1 | cpu = 85 |
运行结果:
1 | CPU 和内存使用率都过高 |
这里有两个条件:
cpu > 80,结果为True。memory > 90,结果也为True。
由于两个条件都成立,整个判断成立。
如果把内存使用率修改为 70:
1 | cpu = 85 |
程序不会输出任何内容,因为第二个条件不成立。
and 的核心规则是:只有当两侧的操作数都为真时,整个逻辑与表达式才为真。
or:只要一个条件满足即可
再看另一个例子:
1 | cpu = 95 |
运行结果:
1 | 至少有一项资源使用率过高 |
这里 CPU 使用率已经超过 90%,即使内存使用率没有超过 90%,整个条件仍然成立。
or 适用于多个异常条件中任意一个出现就需要采取行动的场景。
例如:
1 | if cpu > 90 or disk_usage > 95: |
运行结果:
1 | 触发资源告警 |
只要 CPU 或磁盘使用率达到对应的阈值,就会触发告警。
not:反转条件的真值
not 用于对一个表达式的真值取反。
例如:
1 | service_running = False |
因为 service_running 为假,not service_running 就为真,因此程序输出:
1 | 服务未运行 |
not 也可以用于判断一个条件是否不成立:
1 | cpu = 30 |
运行结果:
1 | CPU 使用率没有超过 80% |
这段代码可以理解为:如果 cpu > 80 不成立,就输出提示。
不过,编写简单的范围判断时,直接写成下面这样通常更清晰:
1 | if cpu <= 80: |
阅读代码时,尽量使用直观、明确的表达式,避免连续叠加多个 not,让判断变得难以理解。
短路求值:为什么后面的表达式可能不会执行?
现在来到本文最重要的部分:短路求值。
我们已经知道:
and要求两个操作数都为真。or只要求至少一个操作数为真。
但 Python 并不总是计算两个操作数。
为了确定整个表达式的结果,Python 会根据已经计算出的结果,决定是否有必要继续计算后面的操作数。这种行为称为短路求值(short-circuit evaluation)。
and 的短路特性
先看下面的代码:
1 | x = 0 |
运行结果:
1 | 条件不成立 |
注意,这段代码并没有报出除零错误。
为什么?
程序先计算:
1 | x != 0 |
由于 x 为 0,结果是 False。
对于 and 来说,只要左侧已经为假,整个表达式就不可能为真,因此没有必要继续计算右侧的:
1 | 100 / x > 10 |
Python 直接确定整个表达式为假,跳过右侧的计算。
这就是 and 的短路特性:
当左侧操作数为假时,右侧操作数不会被计算。
这不仅可以减少不必要的计算,还可以避免某些表达式在特定条件下产生错误。
不过,如果将条件顺序调换:
1 | x = 0 |
程序就会报出 ZeroDivisionError,因为除法先执行了。
1 | Traceback (most recent call last): |
所以,在使用 and 保护可能产生错误的操作时,必须将前置检查放在左侧。
or 的短路特性
or 的行为恰好对应另一种情况。
1 | x = 10 |
运行结果:
1 | 条件成立 |
这里同样没有发生除零错误。
因为 x > 0 已经为真,整个 or 表达式必然为真,Python 没有必要再计算右侧的表达式。
or 的短路规则是:
当左侧操作数为真时,右侧操作数不会被计算。
我们可以用一张表来总结:
| 表达式 | 左侧结果 | 是否计算右侧 |
|---|---|---|
False and ... | 假 | 不计算 |
True and ... | 真 | 需要计算 |
True or ... | 真 | 不计算 |
False or ... | 假 | 需要计算 |
这里的“需要计算”是指 Python 为了确定表达式结果,需要继续计算右侧操作数;如果左侧已经决定了整个表达式的结果,就会短路。
为什么日志文件检查经常使用 and?
我们来看一个 Linux 运维场景。
假设变量 log_file 保存的是某个日志文件路径,但在某些情况下,程序可能没有获取到路径,变量的值是 None。
如果直接调用:
1 | log_file = None |
程序会报出 AttributeError,因为 None 对象没有 startswith() 这个字符串方法。
输出
1 | Traceback (most recent call last): |
可以先判断变量是否存在有效值:
1 | log_file = None |
此时,左侧的 log_file 为假值,Python 会直接停止计算,不会调用 startswith(),因此避免了异常。
不过,这里需要明确一个细节:这个例子中的 log_file 实际上被当作字符串内容使用。如果它保存的是文件路径,那么 startswith("ERROR") 检查的是路径字符串的开头,并不能检查日志文件的内容。
如果我们想检查一行日志是否以 ERROR 开头,应该写成:
1 | log_line = "ERROR: disk space is low" |
运行结果:
1 | 发现错误日志 |
这时,短路求值能够保证:只有当 log_line 为真值时,才继续调用字符串方法。
需要注意,log_line and log_line.startswith("ERROR") 的结果不一定是布尔值。当 log_line 为 None 或空字符串时,整个表达式可能直接返回 None 或空字符串;在 if 条件中,它们都会被视为假。
短路求值不等于异常处理
虽然短路求值可以避免某些错误,但它不能代替完整的异常处理。
例如:
1 | log_file = "/var/log/messages" |
这段代码只检查路径字符串是否以 ERROR 开头,并没有真正读取文件。
即使变量非空,也不代表文件一定存在、可读,或者内容符合预期。
因此,在实际运维脚本中,需要根据任务分别考虑:
- 使用条件判断排除不应该执行的操作。
- 使用短路求值避免不必要或不安全的表达式。
- 对文件读取、网络请求和系统命令等可能失败的操作,采用适当的异常处理或错误码检查。
这三者解决的问题不同,不应该互相替代。
真值判断:为什么空字符串也能用于 if?
在前面的代码中,我们写过:
1 | if log_line: |
输出
1 | 日志内容不为空 |
但 log_line 明明是一个字符串,为什么可以直接放在 if 后面?
这是因为 Python 支持真值判断(truth value testing)。
在条件语句中,Python 不要求表达式必须直接返回布尔值。许多对象都可以根据其真值被判断为真或假。
哪些值是假值?
Python 中常见的假值包括:
| 值 | 说明 |
|---|---|
False | 布尔假值 |
None | 表示没有值 |
0 | 整数零 |
0.0 | 浮点数零 |
"" | 空字符串 |
[] | 空列表 |
() | 空元组 |
{} | 空字典 |
set() | 空集合 |
例如:
1 | print(bool(0)) |
运行结果:
1 | False |
这里的 bool() 用于将一个值转换为布尔值,以便观察它的真值。
需要注意:不是所有数值都是假值。非零数值通常为真,包括负数;非空字符串、非空列表等通常也为真。
例如:
1 | print(bool(-1)) |
运行结果:
1 | True |
为什么字符串 "0" 是真值?
因为它是一个非空字符串。Python 不会自动把它转换为数字零。
同理,[0] 是一个包含一个元素的列表,因此也不是空列表。
真值判断在运维脚本中有什么用?
假设我们获取了一组异常服务器列表:
1 | servers = ["server01", "server02"] |
因为列表中有两个元素,servers 为真值,因此输出:
1 | 发现需要处理的服务器 |
如果将其修改为空列表:
1 | servers = [] |
程序就会输出:
1 | 没有需要处理的服务器 |
这种写法在处理服务器列表、日志行、API 返回的数据以及待执行任务列表时都很实用。
但也要注意:空值判断不等于业务有效性判断。
例如,列表 [None] 并不是空列表,因此它的真值为真;但列表中的元素可能并不符合业务要求。
因此,如果程序要求列表中的每一项都必须是有效服务器名称,就需要进一步检查元素,而不能只判断列表是否为空。
逻辑运算符不一定返回 True 或 False
这是 Python 中一个非常值得掌握的细节。
很多初学者认为:
1 | a and b |
和:
1 | a or b |
一定会返回 True 或 False。
实际上并不是这样。
在 Python 中,and 和 or 会根据短路规则返回其中一个操作数本身,而不是强制把结果转换为布尔值。
and 返回什么?
1 | print("hello" and "world") |
运行结果:
1 | world |
第一行中,"hello" 是真值,因此 Python 继续计算右侧,并返回 "world"。
第二行中,空字符串是假值,因此 Python 直接返回左侧的空字符串,不再计算右侧。
or 返回什么?
1 | print("hello" or "world") |
运行结果:
1 | hello |
第一行中,"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 | cpu = 85 |
运行结果:
1 | 告警:CPU 和内存使用率都过高 |
这里有三个独立的 if,因为它们代表三个可以同时成立的告警规则。
这与 if、elif 结构不同:多个独立的 if 可以分别执行,而 if、elif、else 结构最多只会执行其中一个分支。
这也是运维告警中非常重要的区别。
如果我们把第二个和第三个 if 改为 elif,当第一个条件成立后,后续的告警规则就可能不再执行,从而导致漏报。
当然,真实监控系统还需要考虑指标采集失败、阈值配置、告警去重和恢复通知等问题。这里的代码只是用来说明逻辑运算符与多条独立规则的组合方式。
几个常见错误与注意事项
用 is 判断数字是否相等
错误示例:
1 | cpu = 80 |
输出
1 | <python-input-93>:3: SyntaxWarning: "is" with 'int' literal. Did you mean "=="? |
这里应该使用:
1 | if cpu == 80: |
输出
1 | CPU 使用率为 80% |
is 用于判断对象身份,而不是判断数值是否相等。
把两个独立的 if 误认为 and
例如:
1 | cpu = 90 |
输出
1 | CPU 使用率过高 |
这两个条件分别判断,互不依赖。
如果需求是 CPU 和内存必须同时超过阈值才告警,就应该写成:
1 | if cpu > 80 and memory > 90: |
注意,这不是说 and 一定比两个 if 更安全,而是两种写法表达的业务逻辑不同。应根据需求选择。
忘记 and 和 or 的优先级
例如:
1 | if cpu > 80 or memory > 90 and disk > 95: |
输出
1 | 触发告警 |
Python 中,not 的优先级高于 and,而 and 的优先级高于 or。
因此,上面的条件等价于:
1 | if cpu > 80 or (memory > 90 and disk > 95): |
输出
1 | 触发告警 |
也就是说,CPU 超过 80% 就会告警;或者内存和磁盘同时超过阈值时也会告警。
如果实际需求是 CPU 和内存任意一个超过阈值,同时磁盘必须超过 95%,就需要明确写出:
1 | if (cpu > 80 or memory > 90) and disk > 95: |
输出
1 | 触发告警 |
建议在包含多个 and、or 的复杂表达式中,主动使用括号。这样不仅能减少理解错误,也能让后续维护代码的人更容易确认业务规则。
把空字符串判断当成完整的输入校验
例如:
1 | hostname = "" |
这段代码能够判断字符串是否为空,但不能验证主机名是否符合合法格式。
如果要校验用户输入的主机名,还需要进一步检查字符、长度和业务规则。
总结
本篇我们从关系运算符开始,学习了 Python 中的 ==、!=、<、<=、>、>=,以及用于判断对象身份的 is 和 is not。
接着,我们学习了三个逻辑运算符:
and:两个条件都满足时,整体条件才为真。or:至少一个条件满足时,整体条件就为真。not:对表达式的真值取反。
随后,我们重点理解了短路求值。and 在左侧为假时会跳过右侧,or 在左侧为真时也会跳过右侧。这一特性既能减少不必要的计算,也可以用来避免某些不安全的操作。
最后,我们学习了 Python 的真值判断,以及为什么空字符串、空列表、None 和数值零在条件语句中通常会被视为假值。
对于 Linux 运维人员来说,这些知识可以直接应用于资源监控、服务状态检查、日志筛选和自动化告警。
当你能够熟练地组合多个条件,并理解短路求值的执行顺序时,就可以开始编写更复杂、更可靠的运维判断逻辑了。
下一步,我们可以继续学习循环结构,让 Python 不仅能够判断一次,还能够批量检查多台服务器、遍历日志记录,并自动处理大量重复任务。
