9. 使用布尔逻辑组合条件
在第 7 章中,我们学习了布尔值以及使用比较运算符的简单条件。在第 8 章中,我们用这些条件配合 if 语句做出决策。但现实世界的程序往往需要一次检查多个条件。如果用户有正确的密码并且已登录,我们是否应该授予访问权限?如果温度太热或者太冷,我们是否应该显示警告?如果文件不为空,我们是否应该继续?
Python 提供了三个逻辑运算符 (logical operators),让我们可以组合并修改布尔值:and、or 和 not。这些运算符是你在程序中表达复杂决策逻辑的基础构件。
9.1) 逻辑运算符 and、or 与 not
这三个逻辑运算符会对布尔值(或可以被当作布尔值处理的值)进行运算,并产生新的布尔结果。
9.1.1) and 运算符
and 运算符只有在两个操作数都为真时才返回 True。如果任意一个操作数为假,则整个表达式为假。
# 两个条件都必须为 True
age = 25
has_license = True
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: True
# 如果任意一个条件为 False,结果就是 False
age = 18
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: False你可以把 and 想成一个严格的门卫:必须所有条件都通过,整体检查才会成功。
and 的真值表:
| 左操作数 | 右操作数 | 结果 |
|---|---|---|
True | True | True |
True | False | False |
False | True | False |
False | False | False |
9.1.2) or 运算符
or 运算符在至少有一个操作数为真时返回 True。只有当两个操作数都为假时,它才返回 False。
# 至少一个条件必须为 True
is_weekend = True
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: True
# 两个条件都为 False
is_weekend = False
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: False你可以把 or 想成一个宽松的门卫:你只需要满足一个条件就能通过。
or 的真值表:
| 左操作数 | 右操作数 | 结果 |
|---|---|---|
True | True | True |
True | False | True |
False | True | True |
False | False | False |
下面是一个关于折扣资格系统的实用示例:
# 如果顾客是学生 OR 老年人,则可享受折扣
age = 68
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: True
# 另一位顾客
age = 30
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: False第一位顾客符合条件,因为他们满足其中一个标准(老年人),即使他们不是学生。
9.1.3) not 运算符
not 运算符是一个一元运算符 (unary operator)(它只作用于单个操作数),用于反转布尔值。它会把 True 变成 False,把 False 变成 True。
is_raining = False
is_sunny = not is_raining
print(is_sunny) # Output: True
is_raining = True
is_sunny = not is_raining
print(is_sunny) # Output: Falsenot 的真值表:
| 操作数 | 结果 |
|---|---|
True | False |
False | True |
当你想检查某个条件的相反情况时,not 运算符特别有用:
# 检查文件是否 NOT 为空
file_size = 0
is_empty = file_size == 0
is_not_empty = not is_empty
print(f"File has content: {is_not_empty}") # Output: File has content: False
# 检查用户是否 NOT 已登录
is_logged_in = False
needs_login_prompt = not is_logged_in
print(f"Show login prompt: {needs_login_prompt}") # Output: Show login prompt: True9.1.4) 组合多个逻辑运算符
你可以在一个表达式中组合多个逻辑运算符,以构建更复杂的条件:
# 在线商店:如果订单超过 $50 或者客户是高级会员,则免运费
# 并且商品有库存
order_total = 45.00
is_premium = True
in_stock = True
gets_free_shipping = (order_total >= 50 or is_premium) and in_stock
print(f"Free shipping: {gets_free_shipping}") # Output: Free shipping: True让我们跟踪一下它的求值过程:
order_total >= 50的结果是False(45.00 不 >= 50)is_premium是TrueFalse or True的结果是Truein_stock是TrueTrue and True的结果是True
下面是另一个访问控制的示例:
# 如果用户是管理员
# 并且(在内网 OR 使用 VPN),则可访问管理面板
is_admin = True
on_internal_network = False
using_vpn = True
can_access_admin = is_admin and (on_internal_network or using_vpn)
print(f"Can access admin panel: {can_access_admin}") # Output: Can access admin panel: True注意 (on_internal_network or using_vpn) 外面的括号。这些括号很重要,因为它们会控制求值顺序,就像算术表达式中的括号一样。
9.2) 布尔表达式中的运算符优先级(Not、And、Or 的顺序)
当你在不加括号的情况下组合多个逻辑运算符时,Python 会遵循特定的优先级规则来决定求值顺序。理解这些规则能帮助你写出正确的条件,并避免一些隐蔽的 bug。
9.2.1) 优先级层级
Python 按以下顺序(从高到低优先级)对逻辑运算符求值:
not(最高优先级)and(中等优先级)or(最低优先级)
这意味着会先计算 not,然后是 and,最后是 or。
# 不加括号时,优先级决定求值顺序
result = True or False and False
print(result) # Output: True
# Python 如何求值:
# 第 1 步:False and False → False(and 的优先级高于 or)
# 第 2 步:True or False → True我们用一个更详细的示例一步步看看:
is_weekend = False
is_holiday = True
has_work = True
# 表达式:not has_work or is_weekend and is_holiday
free_time = not has_work or is_weekend and is_holiday
# 求值顺序:
# 第 1 步:not has_work → not True → False
# 第 2 步:is_weekend and is_holiday → False and True → False
# 第 3 步:False or False → False
print(f"Has free time: {free_time}") # Output: Has free time: False9.2.2) 使用括号提升清晰度
即使你理解优先级规则,使用括号也能让代码更清晰,并防止出错。括号会覆盖默认优先级,让你的意图更明确。
# 不加括号时含义不够明确
result = True or False and False
print(result) # Output: True
# 加括号后更清晰——我们真正想表达什么?
result = (True or False) and False
print(result) # Output: False
result = True or (False and False)
print(result) # Output: True这两个表达式会产生不同的结果!括号会彻底改变含义。
9.2.3) 比较运算符与逻辑运算符一起使用
比较运算符(如 <、>、==、!=)的优先级高于逻辑运算符。这意味着比较会在逻辑运算之前被求值。
age = 25
income = 50000
# 比较表达式周围不需要括号
eligible = age >= 18 and income >= 30000
print(f"Eligible for loan: {eligible}") # Output: Eligible for loan: True
# Python 的求值方式:
# 第 1 步:age >= 18 → True
# 第 2 步:income >= 30000 → True
# 第 3 步:True and True → True9.3) 短路求值 (short-circuit evaluation)
Python 在使用 and 与 or 计算布尔表达式时,会使用短路求值 (short-circuit evaluation)。这意味着 Python 一旦知道最终结果,就会停止继续求值,从而可能跳过后续操作数的计算。这种行为既是一种性能优化,也是一种实用的编程技巧。
9.3.1) and 如何短路
对于 and 运算符,如果左操作数是 False,Python 就知道整个表达式必然是 False(因为 and 要返回 True,两个操作数都必须为真)。因此,Python 完全不会计算右操作数。
# 简单演示
x = 5
result = x < 3 and x > 10
print(result) # Output: False
# Python 的求值过程:
# 第 1 步:x < 3 → 5 < 3 → False
# 第 2 步:由于左侧为 False,不计算 x > 10
# 第 3 步:返回 False下面是一个实际示例,展示短路求值为什么重要:
# 检查一个数是否可整除——避免除以零
numerator = 100
denominator = 0
# 由于短路求值,这样写是安全的
# 如果 denominator 为 0,就不会发生除法
is_divisible = denominator != 0 and numerator % denominator == 0
print(f"Is divisible: {is_divisible}") # Output: Is divisible: False
# 如果没有短路求值,这将导致错误:
# denominator = 0
# result = numerator % denominator # ZeroDivisionError!表达式 denominator != 0 的结果为 False,因此 Python 永远不会去计算 numerator % denominator,否则会触发除以零错误。
我们再看一个关于字符串操作的示例:
# 安全地检查字符串属性
text = ""
# 检查 text 不为空 AND 第一个字符是大写
# 安全,因为如果 text 为空,我们不会尝试访问 text[0]
has_uppercase_start = len(text) > 0 and text[0].isupper()
print(f"Starts with uppercase: {has_uppercase_start}") # Output: Starts with uppercase: False
# 如果不做长度检查就这样写:
# text = ""
# result = text[0].isupper() # IndexError: string index out of range9.3.2) or 如何短路
对于 or 运算符,如果左操作数是 True,Python 就知道整个表达式必然是 True(因为只要至少一个操作数为真就足够了)。因此,Python 不会计算右操作数。
# 简单演示
x = 15
result = x > 10 or x < 5
print(result) # Output: True
# Python 的求值过程:
# 第 1 步:x > 10 → 15 > 10 → True
# 第 2 步:由于左侧为 True,不计算 x < 5
# 第 3 步:返回 True9.3.3) 短路求值的实际应用
避免错误:
# 安全地访问列表元素
numbers = [1, 2, 3]
index = 5
# 访问前先检查 index 是否有效
is_valid = index < len(numbers) and numbers[index] > 0
print(f"Valid and positive: {is_valid}") # Output: Valid and positive: False
# 如果没有短路求值,这会崩溃:
# is_valid = numbers[index] > 0 # IndexError!高效地检查多个条件:
# 表单校验——在第一个错误处停止
email = "user@example.com"
password = "pass"
age = 25
# 按照最可能失败的顺序依次检查每个要求
valid_form = (
len(email) > 0 and # 快速检查
"@" in email and # 快速检查
len(password) >= 8 and # 快速检查
age >= 18 # 快速检查
)
print(f"Form valid: {valid_form}") # Output: Form valid: False
# 在密码长度检查处停止,不会计算 age9.4) 当操作数不是布尔值时 and 与 or 会返回什么,以及常见布尔表达式陷阱
到目前为止,我们看到 and、or 和 not 都在布尔值上工作。但 Python 的逻辑运算符有一个有趣的行为:它们可以对任何值进行运算,而不仅仅是 True 和 False。理解这种行为能帮助你写出更简洁的代码,并避免常见错误。
9.4.1) 理解真值性与假值性(回顾)
正如我们在第 7 章所学,Python 会在布尔上下文中把许多非布尔值当作“真值 (truthy)”或“假值 (falsy)”:
假值(会被当作 False):
FalseNone0(任何数值类型的零)""(空字符串)[](空列表){}(空字典)()(空元组)
真值(会被当作 True):
True- 任何非零数字
- 任何非空字符串
- 任何非空集合
# 演示真值性
if "hello":
print("Non-empty strings are truthy") # Output: Non-empty strings are truthy
if 0:
print("This won't print") # 0 是假值
else:
print("Zero is falsy") # Output: Zero is falsy
if [1, 2, 3]:
print("Non-empty lists are truthy") # Output: Non-empty lists are truthy9.4.2) and 实际返回什么
and 运算符并不总是返回 True 或 False。相反,它会返回其中一个操作数:
- 如果左操作数为假值,
and返回左操作数(不会计算右侧) - 如果左操作数为真值,
and返回右操作数
# and 会返回第一个假值;如果全部都是真值,则返回最后一个值
result = 5 and 10
print(result) # Output: 10
result = 0 and 10
print(result) # Output: 0
result = "hello" and "world"
print(result) # Output: world
result = "" and "world"
print(result) # Output: (empty string)
result = None and "world"
print(result) # Output: None让我们跟踪这些示例:
# 示例 1:两边都是真值
result = 5 and 10
# 第 1 步:5 是真值,所以计算右侧
# 第 2 步:返回右侧的值:10
print(result) # Output: 10
# 示例 2:左侧是假值
result = 0 and 10
# 第 1 步:0 是假值,所以立刻返回它
# 第 2 步:不计算右侧
print(result) # Output: 0
# 示例 3:两个都是真值字符串
result = "hello" and "world"
# 第 1 步:"hello" 是真值,所以计算右侧
# 第 2 步:返回右侧的值:"world"
print(result) # Output: world9.4.3) or 实际返回什么
类似地,or 运算符也会返回其中一个操作数:
- 如果左操作数为真值,
or返回左操作数(不会计算右侧) - 如果左操作数为假值,
or返回右操作数
# or 会返回第一个真值;如果全部都是假值,则返回最后一个值
result = 5 or 10
print(result) # Output: 5
result = 0 or 10
print(result) # Output: 10
result = "" or "default"
print(result) # Output: default
result = "hello" or "world"
print(result) # Output: hello
result = None or 0
print(result) # Output: 0让我们跟踪这些示例:
# 示例 1:左侧是真值
result = 5 or 10
# 第 1 步:5 是真值,所以立刻返回它
# 第 2 步:不计算右侧
print(result) # Output: 5
# 示例 2:左侧是假值
result = 0 or 10
# 第 1 步:0 是假值,所以计算右侧
# 第 2 步:返回右侧的值:10
print(result) # Output: 10
# 示例 3:两边都是假值
result = None or 0
# 第 1 步:None 是假值,所以计算右侧
# 第 2 步:返回右侧的值:0(尽管它同样是假值)
print(result) # Output: 09.4.4) 使用 or 提供默认值的实际用途
一个常见模式是用 or 来提供默认值:
# 带默认值的用户偏好设置
user_theme = "" # 用户没有设置主题
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: light
user_theme = "dark"
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: dark
# 配置值
max_retries = None # 未配置
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 3
max_retries = 5
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 5这个模式之所以可行,是因为如果左侧是假值(空字符串、None、0 等),or 就会返回右侧(默认值)。
9.4.12) 运算符返回值总结
下面是每个逻辑运算符返回内容的完整总结:
and 运算符:
- 返回第一个假值操作数
- 如果所有操作数都是真值,则返回最后一个操作数
- 使用短路求值(在第一个假值处停止)
or 运算符:
- 返回第一个真值操作数
- 如果所有操作数都是假值,则返回最后一个操作数
- 使用短路求值(在第一个真值处停止)
not 运算符:
- 总是返回布尔值(
True或False) not会先把操作数转换为布尔值,然后再取反
# 演示三个运算符
print(5 and 10) # Output: 10 (both truthy, return last)
print(0 and 10) # Output: 0 (first falsy, return it)
print(5 or 10) # Output: 5 (first truthy, return it)
print(0 or 10) # Output: 10 (first falsy, evaluate second)
print(not 5) # Output: False (5 is truthy, not returns boolean)
print(not 0) # Output: True (0 is falsy, not returns boolean)
print(not "") # Output: True (empty string is falsy)
print(not "hello") # Output: False (non-empty string is truthy)理解这些行为能帮助你写出更简洁、更 Pythonic 的代码,但始终要优先考虑清晰性。如果使用这些特性让你的代码更难理解,那么显式写法会更好。
在本章中,我们探讨了如何使用 Python 的 and、or 与 not 运算符把简单条件组合成复杂的布尔逻辑。我们学习了运算符优先级、短路求值,以及逻辑运算符在非布尔值上的“意外”行为。我们还考察了常见陷阱与编写清晰、正确布尔表达式的最佳实践。
这些工具让你能够在程序中表达更复杂的决策逻辑。结合第 8 章的 if 语句,你现在几乎可以处理程序所需的任何条件逻辑。在下一章中,我们将探索条件表达式,它提供了一种更紧凑的方式,可基于条件在两个值之间进行选择。