我们来详细讲解一下Python的`match`表达式,它是在 **Python 3.10** 版本中正式引入的一个新特性,被称为“结构化模式匹配”(Structural Pattern Matching)。
在Python 3.10之前,如果需要根据变量的值或结构执行不同的操作,通常依赖一长串的`if/elif/else`语句。`match`语句提供了一种更具声明性、可读性更高且功能更强大的方式来处理这类逻辑,尤其是在处理复杂的数据结构时。
### 核心概念
Python的`match`语句与MoonBit或Rust中的`match`非常相似,但也有其自身的特点。其核心思想是:
1. **结构匹配**:它不仅仅是检查值的相等性,更重要的是检查被匹配对象的**结构**。例如,你可以检查一个列表是否包含特定数量的元素,或者一个字典是否包含特定的键。
2. **解构与绑定**:在匹配成功的同时,可以将对象中的部分或全部数据提取出来,并赋值给新的变量。
3. **可读性**:对于复杂的条件分支,`match`语句通常比`if/elif/else`链条更清晰、更易于理解。
### 基本语法
```python
match subject:
case <pattern_1>:
# 当 subject 匹配 pattern_1 时执行的代码
case <pattern_2>:
# 当 subject 匹配 pattern_2 时执行的代码
case _: # 通配符,可选
# 当以上所有 case 都不匹配时执行的代码
```
- `subject`:你想要进行匹配的变量或值。
- `case <pattern>`:一个`case`块包含一个模式。Python会自上而下地逐一尝试匹配`subject`与每个`case`的模式。
- **一旦找到第一个成功匹配的`case`,就会执行其对应的代码块,然后`match`语句立即结束**。这一点与C语言的`switch`不同,不需要`break`。
- `_` (下划线):作为通配符(Wildcard Pattern),可以匹配任何东西,通常放在最后一个`case`,用于处理所有其他未匹配的情况。如果所有`case`都不匹配且没有通配符`case`,则`match`语句不执行任何操作。
---
### `match` 的常用模式详解
#### 1. 字面量模式 (Literal Pattern)
这是最基础的模式,用于匹配精确的值,如数字、字符串、`True`、`False`和`None`。
```python
def http_status(status):
match status:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return "Unknown Status"
print(http_status(404)) # 输出: Not Found
```
#### 2. 捕获模式 (Capture Pattern)
如果一个模式是一个未被`_`占用的变量名,它就会匹配成功,并将`subject`的值**捕获**(或绑定)到这个变量上。
```python
command = "send user@example.com"
match command.split():
case ["send", email]: # 捕获第二个元素到变量 email
print(f"Sending email to: {email}")
case ["quit"]:
print("Quitting...")
case _:
print("Unknown command")
# 输出: Sending email to: user@example.com
```
#### 3. 通配符模式 (Wildcard Pattern)
如前所述,`_`可以匹配任何值,但**不会**将值绑定到任何变量。它用于表示“我们不关心这个位置的值”。
#### 4. OR 模式 (`|`)
可以在一个`case`中使用`|`来组合多个字面量模式,表示“或”的关系。
```python
def process_code(code):
match code:
case 200 | 201 | 202:
return "Success"
case 400 | 401 | 403 | 404:
return "Client Error"
case _:
return "Other"
print(process_code(403)) # 输出: Client Error
```
#### 5. 序列模式 (Sequence Pattern)
用于匹配序列类型的数据,如列表(list)和元组(tuple)。它非常强大,可以检查序列的长度和内容。
```python
data = [1, "hello", 3.14]
match data:
# 精确匹配
case [1, "hello", 3.14]:
print("Exact match")
# 使用捕获模式
case [x, y, z]:
print(f"Matched three items: {x}, {y}, {z}")
# 结合字面量和捕获模式
case [1, "hello", z]:
print(f"The third item is {z}")
# 使用 * 来匹配序列中的零个或多个项 (类似JS的...rest)
case [first, *rest]:
print(f"First item: {first}, rest: {rest}") # rest 会是一个列表
# 也可以放在中间或结尾
case [first, *middle, last]:
print(f"First: {first}, Middle: {middle}, Last: {last}")
```
如果`data`是`[1, "hello", 3.14, "world"]`,最后一个`case`会输出:
`First: 1, Middle: ['hello', 3.14], Last: 'world'`
#### 6. 映射模式 (Mapping Pattern)
用于匹配字典(dict)等映射类型的数据。可以检查特定键是否存在,并捕获对应的值。
```python
action = {"command": "draw", "shape": "circle", "x": 10, "y": 20}
match action:
# 匹配包含特定键的字典,并捕获值
case {"command": "draw", "shape": s}:
print(f"Drawing a {s}")
# 捕获所有剩余的键值对到 a **rest** 变量中
case {"command": "move", **rest}:
print(f"Moving with parameters: {rest}")
# 只检查键是否存在,不关心值
case {"error": _}:
print("An error occurred")
```
如果`action`是`{"command": "move", "dx": 5, "dy": -5}`,第二个`case`会输出:
`Moving with parameters: {'dx': 5, 'dy': -5}`
#### 7. 类模式 (Class Pattern)
可以根据对象的类(类型)以及其属性来进行匹配。
```python
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def locate(p):
match p:
# 匹配一个 Point 实例,并捕获其属性
case Point(x=0, y=0):
print("At the origin")
case Point(x=x, y=0):
print(f"On the x-axis at {x}")
case Point(x=0, y=y):
print(f"On the y-axis at {y}")
case Point(x=x, y=y):
print(f"At ({x}, {y})")
case _:
print("Not a point")
locate(Point(3, 0)) # 输出: On the x-axis at 3
```
#### 8. 守卫 (Guards)
在`case`模式的末尾,可以添加一个`if`子句,称为“守卫”。只有当模式匹配成功 **并且** `if`条件为`True`时,这个`case`才算最终匹配成功。
```python
def process_point(p):
match p:
# 守卫条件可以使用模式中捕获的变量
case Point(x=x, y=y) if x == y:
print(f"On the y=x line at ({x}, {y})")
case Point(x=x, y=y):
print(f"Just a regular point at ({x}, {y})")
process_point(Point(5, 5)) # 输出: On the y=x line at (5, 5)
process_point(Point(5, 6)) # 输出: Just a regular point at (5, 6)
```
### 总结
Python的`match`语句是一个功能非常丰富的工具,它极大地增强了Python处理复杂数据结构和条件逻辑的能力。
- **优点**:代码更具可读性、更安全(虽然不像MoonBit那样有编译时穷尽性检查)、表达力强。
- **适用场景**:解析器、状态机、处理API响应、树形数据遍历、根据数据结构执行不同操作等。
- **与`if/elif`的区别**:`if/elif`主要基于布尔表达式求值,而`match`主要基于**结构**匹配和解构。对于简单的值判断,`if/elif`可能更直接;但对于需要检查数据结构并提取其中内容的情况,`match`无疑是更优的选择。