前言
「写条件分支」看起来是最没技术含量的活,但代码质量的分水岭恰恰在这里。同一个业务规则,有人写出五层嵌套的if,有人用三行if/elif加一个字典就清爽收尾。区别不在于聪明,而在于是否知道 Python 提供了哪些分支写法、各自适合什么场景。
初学者最常见的三个误解是:以为and/or返回的是True/False(其实返回的是参与运算的操作数本身);以为match就是 C 的switch(其实没有贯穿、还能匹配结构);以及以为x or default是安全的默认值写法(当x是0或空字符串时会被错误替换)。
本文按「从简单到复杂」的顺序讲六种条件分支写法,并给出各自该在什么时候用。文中的match语句需要 Python 3.10 及以上,其余写法在 Python 3.8 及以上都能跑。
一、基础:if / elif / else
最直接的写法。要点是命中即停,所以判断顺序就是优先级顺序。
# 适用于 Python 3 全版本
def http_status_text(code):
if code == 200:
return "OK"
elif code == 301:
return "Moved Permanently"
elif code == 404:
return "Not Found"
elif 500 <= code < 600:
return "Server Error"
else:
return "Unknown"这里用到了链式比较500 <= code < 600,它等价于500 <= code and code < 600,但更短也更不易写错。
二、条件表达式(三元运算符)
当分支只是为了「二选一取值」时,用条件表达式比写四行if/else更清晰:
# 适用于 Python 3 全版本
age = 17
label = "成年" if age >= 18 else "未成年"
print(label) # 未成年形式是「真值 if 条件 else 假值」,顺序和 C 的条件 ? 真值 : 假值相反,这是高频写错点。它适合短表达式,嵌套多层会立刻变得难读:
# 适用于 Python 3 全版本
# 不推荐:嵌套三元,可读性差
grade = "A" if s >= 90 else ("B" if s >= 80 else "C")多层判断还是回到if/elif/else。
三、布尔运算的短路与返回值
and和or有短路特性,而且返回的是操作数本身:
a or b:a为真就返回a,否则返回ba and b:a为假就返回a,否则返回b
# 适用于 Python 3 全版本
print(0 or "默认") # 默认
print("x" or "默认") # x
print(0 and 1 / 0) # 0(右侧未求值,不会除零)
print("abc" and 42) # 42利用返回值可以写出简短的默认值逻辑,但要注意假值判断的陷阱:or会把0、""、[]也当成「没有值」。如果一个合法值就是0,必须改用显式的is None判断:
# 适用于 Python 3 全版本
def with_default(value, default=100):
# 错误示范:value 为 0 时也会被替换
# return value or default
# 正确:只在确实是 None 时才用默认值
return default if value is None else value
print(with_default(0)) # 0
print(with_default(None)) # 100四、卫语句(提前返回)消除嵌套
深层嵌套的根因通常是「所有失败情况都往里缩进」。反过来写:不合格的输入提前 return,把主逻辑留在最外层。
# 适用于 Python 3 全版本
# 不推荐:嵌套三层
def price_bad(user, item):
if user is not None:
if user.is_active:
if item.stock > 0:
return item.price
else:
return -1
else:
return -2
else:
return -3
# 推荐:卫语句提前返回
def price_good(user, item):
if user is None:
return -3
if not user.is_active:
return -2
if item.stock <= 0:
return -1
return item.price五、字典分派:把「值相等」的分支换成查表
当分支条件是「某个值和一批常量逐个比较」时,用字典把「动作」或「结果」映射起来,比一长串elif更好维护。
# 适用于 Python 3 全版本
def apply_op(op, a, b):
operations = {
"+": lambda x, y: x + y,
"-": lambda x, y: x - y,
"*": lambda x, y: x * y,
}
func = operations.get(op)
if func is None:
raise ValueError(f"不支持的运算符:{op}")
return func(a, b)
print(apply_op("*", 6, 7)) # 42注意用.get()而不是operations[op]:直接下标在没有该键时会抛KeyError,除非这正是你想要的错误。字典分派的前提是分派键是可哈希且可比较的常量,不适合范围判断。
六、match 语句:结构化模式匹配(Python 3.10+)
从 Python 3.10 起,语言引入了match/case(PEP 634)。它不是switch的翻版:
- 没有贯穿(fall-through),命中一个
case就结束 - 能匹配结构(序列、映射、类实例),不只是值
_是通配符,相当于默认分支- 支持守卫子句
case ... if 条件:
# 适用于 Python 3.10+
def describe(command):
match command.split():
case ["go", direction]:
return f"向 {direction} 前进"
case ["look"]:
return "你环顾四周"
case ["take", item] if len(item) > 3:
return f"拿起一个较长的物品:{item}"
case ["take", item]:
return f"拿起 {item}"
case _:
return "无法识别的指令"
print(describe("go north")) # 向 north 前进
print(describe("take")) # 无法识别的指令需要在 Python 3.10 以下运行时,同一段逻辑只能用if/elif判断len(parts)和各位置的值来手写。
实战:把六种写法用在一个「评分系统」里
# 适用于 Python 3.10+
from dataclasses import dataclass
@dataclass
class Submission:
author: str
score: int | None = None # 3.10+ 的联合类型写法
def grade_of(score):
if score is None:
return "未评分"
if score >= 90:
return "A"
if score >= 80:
return "B"
if score >= 60:
return "C"
return "D"
def label(submission):
if submission.score is None:
return f"{submission.author}:待评分"
status = "优秀" if submission.score >= 90 else "已评分"
return f"{submission.author}:{grade_of(submission.score)}({status})"
def main():
subs = [
Submission("Ada", 95),
Submission("Bob", 72),
Submission("Cy", None),
]
for s in subs:
print(label(s))
if __name__ == "__main__":
main()输出依次是Ada:A(优秀)、Bob:C(已评分)、Cy:待评分。
注意int | None这种联合类型写法需要 Python 3.10 及以上;在 3.9 里要写typing.Optional[int],3.8 同理。
常见坑点
=与==混淆
❌if code = 200:✅if code == 200:;Python 3 里=用在条件位置是语法错误
- 以为
and/or返回布尔值
❌ 用x and y的结果去做is True判断 ✅ 它们返回操作数本身,"a" and "b"得到"b",不是True
- 用
or提供默认值,把合法假值也替换掉
❌return value or default,当value是0/""/[]时误用默认值 ✅return default if value is None else value
- 把
match当switch用
❌ 期待case会像 C 那样向下贯穿,或以为它只比常量 ✅match无贯穿、能做结构匹配、_是通配符;需要 Python 3.10+
- 字典分派时直接用下标导致
KeyError
❌operations[op](a, b)✅ 先func = operations.get(op),判空后再调用,或明确用 try/except 捕获
- 深层嵌套而不使用卫语句
❌ 把失败分支一层层缩进到看不见主逻辑 ✅ 不合格输入提前return,主逻辑留在最外层
- 把范围判断写进字典分派
❌ 试图用{range(0, 60): "D", range(60, 80): "C"}直接查表 ✅range对象相等比较不是区间匹配;范围判断仍用if/elif或专门的区间表
- 条件表达式顺序写反
❌label = "未成年" if age >= 18 else "成年"✅ 真值在前:label = "成年" if age >= 18 else "未成年"
总结
| 写法 | 适用场景 | 注意事项 |
|---|
if/elif/else | 通用分支、范围判断 | 命中即停,顺序即优先级 |
| 条件表达式 | 二选一取值 | 真值在前,勿嵌套多层 |
and/or短路 | 简单默认值、守卫 | 返回操作数;假值会误触发 |
| 卫语句 | 消除深层嵌套 | 失败提前返回 |
| 字典分派 | 常量到结果的映射 | 用.get()防KeyError |
match | 结构匹配 | 3.10+,无贯穿,_为通配符 |
判断该用哪种写法,只需要问自己两个问题:分支条件是「值相等」还是「范围/结构」?值是常量就考虑字典分派,是结构就用match,是范围就用if/elif。至于嵌套过深,先想到卫语句,而不是继续往里缩进。