Python 第四课:条件语句与结构化模式匹配
课程概述
本节课我们将学习 Python 中最重要的控制流工具之一——条件语句(Conditional Statements)。条件语句允许程序根据特定条件的真假来选择执行不同的代码路径。我们将详细学习 if、elif(else if 的缩写)和 else 语句的用法,以及条件表达式(三元运算符)的使用。此外,本课还将重点介绍 Python 3.10 引入的革命性新特性——match-case 结构化模式匹配(Structural Pattern Matching)。match-case 是 Python 历史上最重要的语法特性之一,它借鉴自函数式编程语言如 Haskell 和 OCaml,允许开发者以声明式的方式对值、结构和类型进行匹配和解析。本课程所有内容基于 Python 3.11 编写,涵盖了 Python 3.10 和 Python 3.11 中 match-case 的最新改进。
条件语句是编程的基础构建块,几乎所有程序都需要根据不同的条件做出不同的决策。在 Python 中,条件语句不仅支持简单的布尔判断,还支持复杂的表达式、多重条件判断,以及与循环、异常处理等控制流工具的组合使用。Python 的条件语句设计得非常优雅,使用缩进而不是大括号来划分代码块,这使得代码既简洁又具有良好的可读性。
if 语句基础
最基本的 if 语句
if 语句是最简单的条件语句,其基本语法如下:
# 基本语法
if 条件:
条件为真时执行的代码
# 示例
age = 20
if age >= 18:
print("你已经成年了!")
print("可以观看这部电影")
print("程序继续执行...")
在 Python 中,if 语句后的条件表达式不需要使用括号(虽然技术上可以使用括号),条件的真假由 Python 的布尔规则决定。如果条件为真(True),则执行缩进后的代码块;如果为假(False),则跳过该代码块。
条件表达式与布尔值
在 Python 中,以下值在布尔上下文中被视为假(False):
- 布尔值
False - 整数
0(包括0、0L、0.0、0j) - 空字符串
''和空字节串b'' - 空列表
[]、空元组()、空字典{}、空集合set() None
所有其他值都被视为真(True)。
# 各种条件测试
print("测试各种值的布尔值:")
print(f"bool(0) = {bool(0)}")
print(f"bool(1) = {bool(1)}")
print(f"bool('') = {bool('')}")
print(f"bool('hello') = {bool('hello')}")
print(f"bool([]) = {bool([])}")
print(f"bool([1,2]) = {bool([1,2])}")
print(f"bool(None) = {bool(None)}")
print(f"bool({}) = {bool({})}")
# 字符串非空判断
username = "张三"
if username:
print(f"用户名:{username}")
if-else 语句
else 子句在 if 条件为假时执行:
age = 15
if age >= 18:
print("你已经成年了")
else:
print("你还未成年")
print("程序结束")
if-elif-else 链
当需要判断多个条件时,可以使用 elif(else if 的缩写):
score = 85
if score >= 90:
grade = "A"
print("优秀!")
elif score >= 80:
grade = "B"
print("良好!")
elif score >= 70:
grade = "C"
print("中等!")
elif score >= 60:
grade = "D"
print("及格")
else:
grade = "F"
print("需要继续努力")
print(f"你的等级是:{grade}")
多重条件判断
一个 if 语句可以包含多个 elif 子句:
month = 6
if month == 1:
print("一月")
elif month == 2:
print("二月")
elif month == 3:
print("三月")
elif month == 4:
print("四月")
elif month == 5:
print("五月")
elif month == 6:
print("六月")
elif month == 7:
print("七月")
elif month == 8:
print("八月")
elif month == 9:
print("九月")
elif month == 10:
print("十月")
elif month == 11:
print("十一月")
elif month == 12:
print("十二月")
else:
print("无效的月份")
条件表达式(三元运算符)
Python 提供了一个简洁的单行条件表达式,语法为:value_if_true if condition else value_if_false
# 基本语法
age = 20
status = "成年" if age >= 18 else "未成年"
print(f"status = {status}")
# 在表达式中使用
x, y = 10, 20
max_val = x if x > y else y
print(f"max_val = {max_val}")
# 嵌套条件表达式(不推荐过于嵌套)
score = 85
grade = "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 70 else "D"
print(f"grade = {grade}")
# 条件表达式的实际应用
def get_tax_rate(income):
"""根据收入计算税率"""
return (
0.45 if income > 1000000 else
0.35 if income > 500000 else
0.25 if income > 200000 else
0.15 if income > 100000 else
0.05
)
for income in [50000, 150000, 300000, 700000, 2000000]:
rate = get_tax_rate(income)
print(f"收入 {income:,} 元,税率 {rate:.0%},税额 {income * rate:,.0f} 元")
逻辑运算符在条件中的应用
and 和 or 的组合使用
# and 组合多个条件
age = 25
income = 50000
if age >= 18 and income > 30000:
print("符合申请条件")
# or 组合多个条件
day = "周六"
is_holiday = True
if day == "周六" or day == "周日" or is_holiday:
print("今天休息")
# 复杂的条件组合
age = 30
has_job = True
has_savings = False
credit_score = 750
if (age >= 21 and age <= 65) and (has_job or has_savings) and credit_score >= 700:
print("贷款申请通过")
else:
print("贷款申请被拒绝")
使用括号明确优先级
# 没有括号时的优先级
if age > 18 and not has_license or is_supervised:
pass
# 使用括号明确优先级
if (age > 18 and not has_license) or is_supervised:
pass
pass 语句
pass 是一个空操作语句,当语法上需要一条语句但程序不需要任何操作时使用:
# 使用 pass 占位
if condition:
pass # 稍后实现
else:
print("条件为假")
# pass 常用于定义空函数或空类
class EmptyClass:
pass
def TODO():
pass
match-case 结构化模式匹配
match-case 是 Python 3.10 引入的最重要的新语法特性。它的设计理念来自函数式编程语言中的模式匹配(Pattern Matching),允许你将一个值与一系列模式(patterns)进行比较,并在匹配成功时执行相应的代码。match-case 不仅可以匹配字面值,还可以匹配复杂的数据结构、序列、字典,甚至可以绑定变量。
match-case 的基本语法
# 基本语法
match subject:
case pattern1:
# 匹配 pattern1 时执行的代码
case pattern2:
# 匹配 pattern2 时执行的代码
case _:
# 默认情况(相当于 else)
# 示例:简单的 HTTP 方法路由
def handle_request(method):
match method:
case "GET":
return "获取资源"
case "POST":
return "创建资源"
case "PUT":
return "更新资源"
case "DELETE":
return "删除资源"
case _:
return "未知方法"
print(handle_request("GET")) # 获取资源
print(handle_request("POST")) # 创建资源
print(handle_request("PATCH")) # 未知方法
匹配字面值
# 匹配字符串字面值
def get_day_type(day):
match day:
case "Saturday" | "Sunday": # 使用 | 连接多个字面值(OR 模式)
return "周末"
case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday":
return "工作日"
case _:
return "无效日期"
print(get_day_type("Saturday")) # 周末
print(get_day_type("Monday")) # 工作日
print(get_day_type("随便输入")) # 无效日期
# 匹配数字
def get_grade_message(score):
match score:
case 100:
return "满分!太棒了!"
case 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99:
return "优秀!"
case 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89:
return "良好!"
case _ if 60 <= score < 80:
return "及格"
case _ if 0 <= score < 60:
return "需要努力"
case _:
return "无效分数"
print(get_grade_message(100)) # 满分!太棒了!
print(get_grade_message(85)) # 良好!
print(get_grade_message(65)) # 及格
捕获变量(Capture Patterns)
在 match-case 中,你可以使用变量名捕获匹配的值:
# 捕获变量
def describe_point(point):
match point:
case (0, 0):
return "原点"
case (x, 0):
return f"X 轴上的点,x = {x}"
case (0, y):
return f"Y 轴上的点,y = {y}"
case (x, y):
return f"平面上的点,x = {x}, y = {y}"
case _:
return "无效坐标"
print(describe_point((0, 0))) # 原点
print(describe_point((5, 0))) # X 轴上的点,x = 5
print(describe_point((0, -3))) # Y 轴上的点,y = -3
print(describe_point((3, 4))) # 平面上的点,x = 3, y = 4
通配符模式(Wildcard Pattern)
使用下划线 _ 作为通配符,匹配任意值但不绑定到变量:
def process_message(msg_type, data):
match (msg_type, data):
case ("text", _):
return "文本消息"
case ("image", _):
return "图片消息"
case ("file", _):
return "文件消息"
case _:
return "未知消息类型"
print(process_message(("text", "Hello"))) # 文本消息
print(process_message(("image", "photo.jpg"))) # 图片消息
序列模式(Sequence Patterns)
# 匹配列表或元组
def describe_list(items):
match items:
case []:
return "空列表"
case [x]:
return f"只有一个元素:{x}"
case [x, y]:
return f"两个元素:{x} 和 {y}"
case [x, y, z]:
return f"三个元素:{x}、{y} 和 {z}"
case [x, *rest]: # *pattern 捕获剩余元素
return f"第一个元素是 {x},还有 {len(rest)} 个其他元素"
case _:
return "更多元素"
print(describe_list([])) # 空列表
print(describe_list([1])) # 只有一个元素:1
print(describe_list([1, 2])) # 两个元素:1 和 2
print(describe_list([1, 2, 3, 4, 5])) # 第一个元素是 1,还有 4 个其他元素
# 匹配固定长度的序列
def parse_command(cmd):
match cmd.split():
case ["ls"]:
return "列出文件"
case ["ls", *files]:
return f"列出指定文件:{files}"
case ["cd", dir_name]:
return f"切换到目录:{dir_name}"
case ["rm", *files]:
return f"删除文件:{files}"
case _:
return "未知命令"
print(parse_command("ls"))
print(parse_command("ls file1.txt file2.txt"))
print(parse_command("cd /home"))
print(parse_command("rm a.txt b.txt c.txt"))
字典模式(Mapping Patterns)
# 匹配字典(映射)
def describe_config(config):
match config:
case {"host": host, "port": port}:
return f"主机:{host},端口:{port}"
case {"host": host}:
return f"主机:{host},使用默认端口"
case {"port": port}:
return f"使用默认主机,端口:{port}"
case {}:
return "空配置"
case _:
return "无效配置"
print(describe_config({"host": "localhost", "port": 8080}))
print(describe_config({"host": "example.com"}))
print(describe_config({"port": 3000}))
print(describe_config({}))
# Python 3.11 中字典模式支持 **rest 捕获
def parse_http_headers(headers):
match headers:
case {"Content-Type": ct, "Content-Length": cl, **rest}:
return f"内容类型:{ct},长度:{cl},其他头:{list(rest.keys())}"
case {"Content-Type": ct, **rest}:
return f"内容类型:{ct},其他头:{list(rest.keys())}"
case _:
return "无内容相关信息"
headers1 = {"Content-Type": "text/html", "Content-Length": 1234, "X-Custom": "value"}
headers2 = {"Content-Type": "application/json"}
print(parse_http_headers(headers1))
print(parse_http_headers(headers2))
类模式(Class Patterns)
# 匹配类实例的属性
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
@dataclass
class Circle:
center: Point
radius: int
def describe_shape(shape):
match shape:
case Point(0, 0):
return "原点"
case Point(x, 0):
return f"X轴上的点 (x={x})"
case Point(0, y):
return f"Y轴上的点 (y={y})"
case Point(x, y):
return f"平面点 (x={x}, y={y})"
case Circle(Point(x, y), r):
return f"圆心在({x},{y}),半径为{r}的圆"
case _:
return "未知形状"
p1 = Point(0, 0)
p2 = Point(5, 0)
c1 = Circle(Point(1, 2), 10)
print(describe_shape(p1)) # 原点
print(describe_shape(p2)) # X轴上的点 (x=5)
print(describe_shape(c1)) # 圆心在(1,2),半径为10的圆
带有守卫条件(Guard Clauses)
在 match-case 中,可以使用 if 语句添加额外的条件(称为守卫):
# 守卫条件
def classify_number(n):
match n:
case x if x < 0:
return "负数"
case 0:
return "零"
case x if x % 2 == 0:
return f"正偶数 ({x})"
case x if x % 2 != 0:
return f"正奇数 ({x})"
print(classify_number(-5)) # 负数
print(classify_number(0)) # 零
print(classify_number(4)) # 正偶数 (4)
print(classify_number(7)) # 正奇数 (7)
# 复杂的多条件匹配
def process_api_response(response):
match response:
case {"status": 200, "data": data} if len(data) > 0:
return f"成功返回 {len(data)} 条数据"
case {"status": 200, "data": []}:
return "成功但无数据"
case {"status": 400, "error": error_msg}:
return f"客户端错误:{error_msg}"
case {"status": 401 | 403}:
return "认证/授权失败"
case {"status": 500, "error": err}:
return f"服务器错误:{err}"
case {"status": s} if 400 <= s < 600:
return f"HTTP错误:{s}"
case _:
return "无效响应"
print(process_api_response({"status": 200, "data": [1, 2, 3]}))
print(process_api_response({"status": 400, "error": "Bad Request"}))
match-case 的实际应用场景
应用一:JSON 解析
def parse_json_value(value):
"""简化的 JSON 值解析"""
match value:
case None:
return "null"
case bool() as b:
return f"布尔值: {b}"
case int() | float() as n:
return f"数字: {n}"
case str() as s:
return f"字符串: {s}"
case list() as items:
return f"数组[{len(items)}项]"
case dict() as obj:
return f"对象[{len(obj)}键]"
case _:
return "未知类型"
data = [
None, True, 42, 3.14, "hello",
[1, 2, 3], {"name": "张三", "age": 25}
]
for item in data:
print(f"{str(item):<20} => {parse_json_value(item)}")
应用二:命令处理器
def execute_command(command, args):
"""简化的命令处理器"""
match (command, args):
case ("quit",):
return "退出程序"
case ("help",):
return "显示帮助信息"
case ("echo", [*msgs]):
return " ".join(msgs)
case ("repeat", [n, msg]) if n.isdigit():
return msg * int(n)
case ("print", {"text": text}):
return text
case ("print", {"text": text, "count": c}) if c.isdigit():
return text * int(c)
case (cmd, _) if cmd.startswith(":"):
return f"元命令: {cmd}"
case _:
return f"未知命令: {command}"
print(execute_command("quit", []))
print(execute_command("echo", ["Hello", "World"]))
print(execute_command("repeat", ["3", "Hi"]))
print(execute_command("print", {"text": "Hi", "count": "2"}))
应用三:状态机
from enum import Enum
from typing import Optional
class State(Enum):
IDLE = "idle"
RUNNING = "running"
PAUSED = "paused"
STOPPED = "stopped"
class Event(Enum):
START = "start"
PAUSE = "pause"
RESUME = "resume"
STOP = "stop"
def transition(state: State, event: Event) -> Optional[State]:
"""有限状态机的状态转换"""
match (state, event):
case (State.IDLE, Event.START):
return State.RUNNING
case (State.RUNNING, Event.PAUSE):
return State.PAUSED
case (State.PAUSED, Event.RESUME):
return State.RUNNING
case (State.RUNNING | State.PAUSED, Event.STOP):
return State.STOPPED
case (State.STOPPED, Event.START):
return State.RUNNING
case _:
return None # 无效转换
# 测试状态机
current = State.IDLE
print(f"初始状态:{current.value}")
transitions = [
(Event.START, State.RUNNING),
(Event.PAUSE, State.PAUSED),
(Event.RESUME, State.RUNNING),
(Event.STOP, State.STOPPED),
]
for event, expected in transitions:
current = transition(current, event)
print(f"事件 {event.value} => {current.value if current else '无效转换'}")
match-case vs if-elif-else
什么时候应该使用 match-case 而不是 if-elif-else?
# if-elif-else 适合简单的值比较
def route_http_method_if(method):
if method == "GET":
return "获取"
elif method == "POST":
return "创建"
elif method == "PUT":
return "更新"
elif method == "DELETE":
return "删除"
else:
return "未知"
# match-case 适合:
# 1. 需要解构复杂数据结构时
def route_http_method_match(method):
match method:
case "GET": return "获取"
case "POST": return "创建"
case "PUT": return "更新"
case "DELETE": return "删除"
case _: return "未知"
# 2. 需要匹配多种值的联合时
def validate_input_if(value):
if value in (0, 1, 2):
return "小数字"
elif value in range(3, 10):
return "中等数字"
else:
return "大数字"
def validate_input_match(value):
match value:
case 0 | 1 | 2:
return "小数字"
case 3 | 4 | 5 | 6 | 7 | 8 | 9:
return "中等数字"
case _:
return "大数字"
# 3. 需要解构元组或序列时
def process_point_if(point):
if len(point) >= 2:
x, y = point[0], point[1]
return f"x={x}, y={y}"
return "无效点"
def process_point_match(point):
match point:
case [x, y, *rest]:
return f"x={x}, y={y}, extra={rest}"
case [x, y]:
return f"x={x}, y={y}"
case _:
return "无效点"
Python 3.11 中 match-case 的改进
Python 3.11 对 match-case 做了一些改进,包括更好的错误信息和性能优化:
# Python 3.11 中 match-case 的特性
# 1. 更清晰的错误信息
# 当 case 模式有语法错误时,Python 3.11 会给出更精确的错误位置
# 2. 性能优化
# Python 3.11 的字节码编译器对 match-case 进行了优化
# 3. 支持 AS 模式(用于给模式绑定别名)
def process(data):
match data:
case [x, y] as pair:
print(f"pair = {pair}, x = {x}, y = {y}")
case _:
print("不是长度为2的序列")
process([1, 2])
综合练习题
练习一:计算器程序
"""
计算器程序
练习 if-elif-else 和 match-case 的结合使用
"""
def calculator_if(operation, a, b):
"""使用 if-elif-else 的计算器"""
if operation == "add":
return a + b
elif operation == "subtract":
return a - b
elif operation == "multiply":
return a * b
elif operation == "divide":
if b == 0:
return "错误:除数不能为零"
return a / b
elif operation == "power":
return a ** b
elif operation == "mod":
if b == 0:
return "错误:除数不能为零"
return a % b
else:
return f"未知操作:{operation}"
def calculator_match(operation, a, b):
"""使用 match-case 的计算器"""
match operation:
case "add":
return a + b
case "subtract":
return a - b
case "multiply":
return a * b
case "divide" if b != 0:
return a / b
case "divide":
return "错误:除数不能为零"
case "power":
return a ** b
case "mod" if b != 0:
return a % b
case "mod":
return "错误:除数不能为零"
case _:
return f"未知操作:{operation}"
# 测试两种实现
operations = ["add", "subtract", "multiply", "divide", "power", "mod"]
a, b = 10, 3
print("=" * 60)
print(" 计算器测试")
print(f" {a} 和 {b}")
print("=" * 60)
print(f"{'操作':<12} {'if-elif':<15} {'match-case':<15}")
print("-" * 60)
for op in operations:
result_if = calculator_if(op, a, b)
result_match = calculator_match(op, a, b)
print(f"{op:<12} {str(result_if):<15} {str(result_match):<15}")
练习二:学生成绩管理系统
"""
学生成绩管理系统
练习 match-case 处理复杂数据结构
"""
from dataclasses import dataclass
from typing import Optional
@dataclass
class Student:
name: str
student_id: str
scores: dict[str, int]
def get_student_report(student: Student, course: Optional[str] = None):
"""生成学生成绩报告"""
match (course, student.scores):
case (None, {}):
return f"{student.name}(学号:{student.id}):暂无成绩记录"
case (None, scores):
total = sum(scores.values())
count = len(scores)
avg = total / count if count > 0 else 0
return (
f"{student.name}(学号:{student.student_id})\n"
f" 课程数:{count}\n"
f" 总分:{total}\n"
f" 平均分:{avg:.1f}"
)
case (c, {c: score, **rest}):
grade = (
"A+" if score >= 95 else
"A" if score >= 90 else
"B+" if score >= 85 else
"B" if score >= 80 else
"C+" if score >= 75 else
"C" if score >= 70 else
"D" if score >= 60 else
"F"
)
return f"{student.name} - {c}:{score}分({grade})"
case (c, _):
return f"{student.name} 未选修 {c} 课程"
# 测试数据
student1 = Student("张三", "2023001", {"Python": 92, "数学": 88, "英语": 85})
student2 = Student("李四", "2023002", {})
print(get_student_report(student1))
print()
print(get_student_report(student1, "Python"))
print()
print(get_student_report(student2))
练习三:URL 路由解析
"""
URL 路由解析器
练习 match-case 解析复杂字符串模式
"""
def parse_url(url):
"""解析 URL 并返回路由信息"""
# 移除协议前缀
match url.removeprefix("https://").removeprefix("http://").split("/", 2):
case [host]:
return {"type": "domain", "host": host}
case [host, path] if "?" in path:
path, query = path.split("?", 1)
return {"type": "page", "host": host, "path": path, "query": query}
case [host, path]:
return {"type": "page", "host": host, "path": path}
case _:
return {"type": "unknown"}
urls = [
"https://example.com",
"https://example.com/api/users",
"https://example.com/search?q=python",
"ftp://invalid.com/file",
]
print("URL 路由解析测试")
print("=" * 60)
for url in urls:
result = parse_url(url)
print(f"URL: {url}")
print(f"解析结果: {result}")
print()
Python 3.10 match-case 的限制
需要注意的是,Python 的 match-case 与真正的函数式语言模式匹配有一些区别:
# 1. match-case 不能直接用于赋值
# 这是无效的:
# match value:
# case x = 10: # 错误
# pass
# 正确做法:
match value:
case 10 as x: # 使用 as 捕获
print(f"x = {x}")
# 2. match-case 是语句,不是表达式
# 它不能直接返回值(但可以用作函数体)
# 这是无效的:
# result = match x:
# case 1: 1
# case 2: 2
# 正确做法:
def get_value(x):
match x:
case 1:
return 1
case 2:
return 2
常见错误与调试
错误一:缺少冒号
# 错误:if 条件后缺少冒号
# if x > 0
# print("正数")
# 正确:
if x > 0:
print("正数")
错误二:Match-case 字面值重复
# 在 Python 3.10 中,同一个值不能出现多次
# 这是无效的:
# match x:
# case 1 | 1: # 重复的字面值
# pass
# 正确:
match x:
case 1:
pass
错误三:守卫条件中的变量作用域
# 守卫条件中的变量绑定问题
def check(x):
match x:
case y if y > 0: # y 是在守卫中绑定的
return f"正数 y={y}"
case y: # 这里的 y 不会被守卫中的绑定影响
return f"非正数 y={y}"
# 注意:每个 case 都有自己的作用域
错误四:类型注解与 match-case 的混淆
from typing import Union
def process_value(value: Union[int, str, list]):
# 这不是类型注解,而是 match-case 模式
match value:
case int() as n:
return f"整数: {n}"
case str() as s:
return f"字符串: {s}"
case list() as items:
return f"列表: {items}"
本章小结
今天我们学习了以下核心内容:
- if 语句基础:条件判断、布尔值、缩进规则。
- if-elif-else 链:多重条件判断、嵌套条件。
- 条件表达式:三元运算符、嵌套条件表达式。
- 逻辑运算符组合:and、or、not 的组合使用。
- pass 语句:空操作占位符的使用。
- match-case 结构化模式匹配:
- 基本语法和字面值匹配
- 捕获变量(Capture Patterns)
- 序列模式(List/Tuple Patterns)
- 字典模式(Mapping Patterns)
- 类模式(Class Patterns)
- 守卫条件(Guard Clauses)
- match-case vs if-elif-else:何时使用哪种方式。
- Python 3.11 中的改进:更好的错误信息和性能优化。
下一节课我们将学习 Python 的逻辑运算符(and、or、not)、短路求值机制以及链式比较的深入理解,敬请期待!