37.模式匹配
37. 模式匹配
模式匹配是Python的结构匹配系统。它是由match声明和case条款。```python
match value:
case 0:
result = "zero"
case [x, y]:
result = x + y
case {"name": name}:
result = name
case _:
result = None
在 CPython 级别,模式匹配被编译为普通字节码加上专门的匹配指令。解释器评估主题,按顺序尝试每种情况,绑定成功匹配的名称,然后跳转到选定的正文。
## 37.1 的`match`声明
一个`match`语句具有一个主题表达式和一个或多个案例。```python
match subject:
case pattern:
body
case pattern if guard:
body
case _:
body
```主题表达式被评估一次。```python
match compute():
case 1:
...
case 2:
...
compute()运行一次。然后将结果与案例从上到下进行测试。
从概念上讲:```text subject = compute()
try case 1 if match succeeds: execute case 1 body else: try case 2
## 37.2 模式与表达式
模式看起来像表达式,但它们遵循不同的规则。```python
match value:
case x:
...
```这不比较`value`与现有变量`x`。它将主题捕捉到一个新名称中`x`。
捕获模式总是成功的。```python
case x:
```方法:```text
bind x = subject
match succeeds
```要与命名常量进行比较,请使用点名称:```python
case Color.RED:
```或使用守卫:```python
case x if x == expected:
```这种区别很重要,因为模式匹配有自己的语法和绑定规则。
## 37.3 案例顺序
案件按顺序审理。```python
match value:
case int():
result = "integer"
case bool():
result = "boolean"
```这是一个错误的订单,因为`bool`是一个子类`int`。```python
isinstance(True, int)
```是真的。
所以`True`比赛`int()`在到达之前`bool()`。
更好的:```python
match value:
case bool():
result = "boolean"
case int():
result = "integer"
```模式匹配使用运行时类型和结构。更具体的案例通常应该出现在更一般的案例之前。
## 37.4 通配符模式
通配符模式`_`匹配任何东西但不绑定任何东西。```python
match value:
case 0:
result = "zero"
case _:
result = "other"
```这`_`模式通常用作最终的后备。
它与普通的捕获名称不同:```python
case name:
```这绑定了`name`。```python
case _:
```这个不绑定`_`作为匹配结果的新本地。
## 37.5 文字模式
文字模式匹配数字、字符串、字节、布尔值等值`None`。```python
match value:
case 0:
result = "zero"
case "ok":
result = "success"
case None:
result = "missing"
```对于大多数文字,匹配使用相等语义。```text
subject == literal
```对于单例常量,例如`None`, `True`, 和`False`,匹配使用身份风格的单例语义。
文字模式对于标签、小状态和协议标记很有用。
## 37.6 捕获模式
捕获模式绑定一个名称。```python
match value:
case x:
result = x
```这总是匹配的。
捕获模式在较大的模式中很有用:```python
match point:
case [x, y]:
result = x + y
```这里,`[x, y]`是一个序列模式。名字`x`和`y`捕获元素。
成功匹配后,绑定名称将在案例正文中可用。```python
match value:
case [x, y]:
print(x, y)
```如果匹配失败,则来自该失败模式的绑定不得以用户代码可以依赖的方式泄漏。
## 37.7 价值模式
值模式将主题与点名称引用的值进行比较。```python
match color:
case Color.RED:
handle_red()
case Color.BLUE:
handle_blue()
```对带点的名称进行评估并与主题进行比较。
这不同于:```python
case RED:
```它捕获到一个名为`RED`。
对于常量,请使用点名称、枚举或防护。```python
class Status:
OK = "ok"
ERROR = "error"
match status:
case Status.OK:
...
```## 37.8 OR 模式
如果任何替代项匹配,则 OR 模式匹配。```python
match value:
case 0 | 1 | 2:
result = "small"
```所有替代方案必须绑定同一组名称。
有效的:```python
match value:
case [x] | (x,):
result = x
```原则上无效:```python
case [x] | [x, y]:
```因为第一个选择绑定`x`,而第二个绑定`x`和`y`。
编译器检查 OR 模式的名称绑定一致性。
## 37.9 AS 模式
AS 模式绑定整个匹配值,同时还匹配子模式。```python
match value:
case [x, y] as pair:
print(x, y, pair)
```如果主题匹配`[x, y]`, 然后:```text
x = first element
y = second element
pair = whole subject
```当您同时需要解构字段和原始值时,这非常有用。
## 37.10 守卫
守卫是一个`if`案件附带的条件。```python
match value:
case [x, y] if x < y:
result = "ascending"
```首先匹配模式。如果成功,将对守卫进行评估。
从概念上讲:```text
if subject matches [x, y]:
if x < y:
execute body
else:
try next case
```Guards 可以运行任意 Python 代码。它们可以引发异常、调用函数、改变状态或依赖于捕获的名称。
如果守卫评估为假,则该案例被视为未选择,并且继续匹配下一个案例。
## 37.11 序列模式
序列模式匹配类似序列的对象。```python
match value:
case [x, y]:
result = x + y
```这匹配长度为二的序列。
可能匹配的示例:```python
[1, 2]
(1, 2)
```为此目的,字符串和字节不被视为序列,即使它们在其他上下文中是类似序列的。
序列匹配需要检查:```text
is the subject a sequence pattern candidate
does it have the required length
extract elements
match nested subpatterns
bind names
```## 37.12 星号序列模式
带星号的图案捕获可变长度的中间部分。```python
match value:
case [first, *middle, last]:
...
```为了:```python
value = [1, 2, 3, 4]
```绑定是:```text
first = 1
middle = [2, 3]
last = 4
```加星标的捕获会收到一个列表。
该模式至少需要足够的元素用于非星号位置。```python
case [first, *middle, last]:
```至少需要两个元素。
## 37.13 映射模式
映射模式匹配类似映射的对象。```python
match value:
case {"name": name, "age": age}:
...
```这将检查主题是否具有所需的密钥。
为了:```python
value = {"name": "Ada", "age": 37, "city": "London"}
```模式匹配并绑定:```text
name = "Ada"
age = 37
```允许使用额外的键,除非模式在模式之外使用更严格的逻辑,例如防护。
映射模式还可以捕获剩余的项目:```python
match value:
case {"name": name, **rest}:
...
```然后`rest`接收不匹配键的字典。
## 37.14 类模式
类模式按类型和属性匹配对象。```python
match value:
case Point(x, y):
...
```这检查是否`value`是一个实例`Point`,然后提取字段。
位置字段的含义由控制`Point.__match_args__`。
例子:```python
class Point:
__match_args__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
```然后:```python
match p:
case Point(x, y):
...
```在概念上类似于:```text
isinstance(p, Point)
x = p.x
y = p.y
```具有模式匹配语义和失败行为。
## 37.15 关键字类模式
类模式可以明确地命名属性。```python
match value:
case Point(x=0, y=y):
...
```这匹配一个`Point`谁`x`属性匹配`0`,并绑定`y`属性。
关键字类模式不依赖于`__match_args__`对于那些属性。
从概念上讲:```text
check isinstance(value, Point)
get value.x and match against 0
get value.y and bind y
```属性查找可以执行代码,因此如果涉及属性或自定义属性访问,类模式可能会产生副作用。
## 37.16`__match_args__`
`__match_args__`将位置类模式字段映射到属性名称。```python
class Point:
__match_args__ = ("x", "y")
```这允许:```python
case Point(a, b):
```意思是:```python
case Point(x=a, y=b):
```对于数据类和命名元组,Python 通常提供有用的`__match_args__`自动地。
如果`__match_args__`如果缺失、为空或不兼容,位置类模式可能会失败或引发错误,具体取决于模式形式。
## 37.17 嵌套模式
图案可以嵌套。```python
match value:
case {"point": Point(x, y), "label": label}:
...
```这会检查:```text
subject is a mapping
subject has key "point"
subject has key "label"
subject["point"] is a Point
extract Point.x and Point.y
bind label
```嵌套模式被编译成一系列测试和提取操作。
如果任何嵌套部分失败,则整个案例失败并继续匹配下一个案例。
## 37.18 名称绑定
成功案例中绑定的名称将成为周围范围内的局部变量。```python
def f(value):
match value:
case [x, y]:
return x + y
case _:
return 0
```编译器处理`x`和`y`作为本地名称`f`。
这可能会影响范围分析。模式捕获的名称是一个绑定事件,类似于赋值。
例子:```python
def f(value):
match value:
case x:
return x
```这里,`x`是本地的`f`。
## 37.19 失败的匹配绑定
失败的模式不应用作稳定绑定的来源。
例子:```python
match value:
case [x, 0]:
...
case _:
...
```如果第一种情况在绑定部分结构后失败,CPython 必须避免将部分绑定暴露为成功结果。
语言语义不允许您依赖失败匹配期间绑定的名称。
编译器和解释器代码必须保留此规则。
## 37.20 模式匹配字节码
模式匹配编译成字节码。
一个简单的例子:```python
def f(value):
match value:
case [x, y]:
return x + y
case _:
return 0
```概念字节码结构:```text
load subject
try sequence case:
check sequence
check length 2
unpack into x, y
execute body
return
fallback:
return 0
```CPython 包含用于常见检查的特定于模式的操作码,例如序列匹配、映射匹配、密钥提取和类匹配。确切的操作码名称和布局可能会因版本而异。
## 37.21 主题重复
在尝试多种情况时,受试者必须经常保持可用状态。```python
match value:
case [x]:
...
case {"x": x}:
...
case _:
...
```如果第一种情况失败,第二种情况仍然需要原来的主体。
编译器根据需要发出堆栈操作来复制、保留或丢弃主题。
从概念上讲:```text
subject on stack
try case 1
if fail, restore subject
try case 2
if fail, restore subject
fallback
```正确的堆栈规则很重要,因为模式匹配有许多失败路径。
## 37.22 失败路径
一个案例可能会在很多方面失败:```text
wrong type
wrong sequence length
missing mapping key
class check fails
attribute extraction fails with AttributeError
nested subpattern fails
OR alternative fails
guard is false
```有些失败是正常的不匹配。其他人则是真正的例外。
模式匹配必须区分:```text
normal pattern failure
real runtime exception
```例如,缺少映射键意味着映射模式不匹配。
但一个能筹集资金的财产获取者`RuntimeError`在类模式期间应该传播异常。
## 37.23 守卫和例外
守卫是普通的 Python 代码。```python
match value:
case x if check(x):
...
```如果`check(x)`引发,异常传播。解释器不会将其视为失败的匹配。
从概念上讲:```text
pattern succeeds
evaluate guard
returns true: select case
returns false: try next case
raises: propagate exception
```这使得守卫强大而有效。
## 37.24 映射键查找
映射模式使用键查找。一个模式,例如:```python
case {"name": name}:
```检查主题是否包含密钥`"name"`并提取其价值。
与映射模式规则相比,实现必须避免意外调用改变语义的行为。
重要的源级行为是:```text
required keys must exist
their values must match subpatterns
extra keys are allowed
```如果`**rest`如果存在,不匹配的键值对将被复制到新字典中。
## 37.25 类模式属性查找
类模式可以执行属性查找。```python
case Point(x=x, y=y):
```这可能会调用:```text
Point-related descriptors
properties
__getattribute__
__getattr__
```如果属性访问引发`AttributeError`,类模式可能会因与该属性不匹配而失败。其他异常会传播。
例子:```python
class C:
@property
def x(self):
raise RuntimeError("bad")
match C():
case C(x=x):
...
```这传播`RuntimeError`。
## 37.26 模式匹配和描述符
因为类模式使用属性访问,所以描述符可以参与。```python
class C:
@property
def x(self):
return 10
match C():
case C(x=10):
result = "matched"
```属性 getter 在匹配期间运行。
这意味着模式匹配可以触发用户代码。它并不总是被动的结构检查。
## 37.27 模式匹配和数据类
数据类可以自然地与类模式配合使用。```python
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def f(p):
match p:
case Point(0, y):
return y
case Point(x, y):
return x + y
```数据类通常提供`__match_args__`基于字段,因此位置类模式有效。
从概念上讲:```text
Point(0, y)
matches Point instance
checks x == 0
binds y from p.y
```## 37.28 模式匹配和枚举
枚举通常与值模式一起使用。```python
from enum import Enum
class TokenKind(Enum):
NAME = 1
NUMBER = 2
match kind:
case TokenKind.NAME:
...
case TokenKind.NUMBER:
...
```点名称是值模式,因此它们与现有枚举成员进行比较。
不要写:```python
case NAME:
```除非你打算捕获一个新变量。
## 37.29 模式匹配和 AST
模式匹配对于树状结构很有用。```python
match node:
case BinOp(left, "+", right):
...
case Literal(value):
...
```编译器或解释器可以使用类模式来检查节点类型。
例子:```python
@dataclass
class Literal:
value: object
@dataclass
class BinOp:
left: object
op: str
right: object
```然后:```python
match node:
case BinOp(Literal(a), "+", Literal(b)):
return Literal(a + b)
```这直接表达了结构分解。
## 37.30 模式匹配和字节码堆栈
模式匹配像其他字节码一样使用帧值堆栈。
主题、中间提取值、比较结果和保留的回退值可以存在于堆栈中。
序列模式:```python
case [x, y]:
```从概念上讲:```text
subject
check sequence
check length
unpack two elements
store x
store y
execute body
```如果失败,则弹出临时值,下一个情况从干净的堆栈形状开始。
## 37.31 编译器职责
编译器必须生成保留几个不变量的字节码:```text
subject evaluated once
cases tried in order
successful pattern bindings visible in body
failed pattern bindings not relied on
guards evaluated after binding
guards can access bound names
stack shape valid at every jump
exceptions propagate correctly
only first successful case runs
```因此,模式匹配既是语言功能也是编译器功能。
## 37.32 运行时职责
在运行时,CPython 必须执行:```text
type checks
sequence checks
mapping checks
class checks
attribute extraction
key extraction
equality checks
binding
guard evaluation
failure jumps
exception propagation
```其中一些操作是通用的 Python 操作,可以调用用户代码。
例如:```python
case SomeClass(x=x):
```可以执行描述符逻辑`x`。```python
case 10:
```可以使用相等比较。
在 Python 的动态对象模型下,模式匹配必须保持正确。
## 37.33 模式匹配和相等
文字和值模式可以使用相等性。```python
case 10:
```如果主语等于则匹配`10`。
对于用户定义的对象,相等可以调用`__eq__`。```python
class C:
def __eq__(self, other):
print("compare")
return True
match C():
case 10:
print("matched")
```This can execute user code.
如果相等引发异常,则异常会传播。
## 37.34 Pattern Matching and Performance
模式匹配可以很清晰,但它并不神奇。 It still performs runtime checks.
费用可能包括:```text
type checks
length checks
mapping lookups
attribute lookups
equality comparisons
temporary allocations
guard execution
bytecode branches
```一个简单的`if`对于非常小的标量情况,链可能会更快。
当结构很重要时,模式匹配最有用:```text
nested data
AST nodes
protocol messages
token streams
configuration shapes
command parsing
event dispatch
```在可以提高清晰度的地方使用它。
## 37.35 模式匹配与`if/elif`这:```python
match value:
case 0:
...
case 1:
...
```可以类似于:```python
if value == 0:
...
elif value == 1:
...
```但模式匹配更进一步:```python
match value:
case {"type": "user", "name": name}:
...
case ["move", x, y]:
...
case Point(x=0, y=y):
...
```它结合了型式试验、形状试验、拆包和装订。
## 37.36 模式匹配与解构赋值
解构赋值需要形状匹配或提升。```python
x, y = value
```如果`value`不正好有两个项目,它会引发。
模式匹配测试形状和是否会失败。```python
match value:
case [x, y]:
...
case _:
...
```匹配失败`[x, y]`只需尝试下一个案例。
所以模式匹配是条件解构。
## 37.37 模式匹配和范围
模式中捕获的名称是本地绑定。```python
def f(value):
match value:
case x:
return x
```编译器处理`x`作为本地`f`。
这可能会让期望与外部变量进行比较的用户感到惊讶。```python
expected = 10
def f(value):
match value:
case expected:
return True
```这会捕获到本地名称`expected`。和外面的没法比`expected`。
使用:```python
def f(value):
match value:
case x if x == expected:
return True
```或使用点分常量。
## 37.38 模式匹配和不可达的情况
捕获模式捕获一切。```python
match value:
case x:
...
case 0:
...
```第二种情况是无法访问的。
相似地:```python
case _:
```因为非最终案例使得后面的案例无法访问。
编译器在无效位置检测到一些无可辩驳的模式。
无可辩驳的模式是始终匹配的模式,例如:```text
_
x
object()
```在许多情况下。
## 37.39 模式匹配和`object()`不带参数的类模式可以匹配该类的任何实例。```python
case object():
```由于几乎所有普通对象都是`object`,这是广泛匹配。
它不同于:```python
case object:
```这是一个名为的捕获模式`object`,除非语法通过上下文以不同的方式解析。避免模式中出现歧义的名称。
## 37.40 模式匹配和`None`匹配`None`, 使用:```python
case None:
```这是字面上的单例模式。
不要使用:```python
case x:
```并期望它与外部进行比较`x`。
单例模式`None`, `True`, 和`False`是常见且明确的。
## 37.41 模式匹配和安全
模式匹配可以执行用户代码。
潜在的执行点包括:```text
equality comparison
attribute access
descriptor access
mapping methods
sequence methods
guard expressions
```对于受信任的内存中对象,这是正常的。
对于具有恶意方法的不受信任对象,模式匹配应被视为普通的 Python 执行,而不是安全的声明性查询。
## 37.42 检查模式匹配
使用`dis`检查匹配字节码。```python
import dis
def f(value):
match value:
case [x, y]:
return x + y
case {"x": x}:
return x
case _:
return 0
dis.dis(f)
```寻找:```text
subject handling
pattern-specific checks
unpack operations
mapping operations
jumps between cases
stores for captured names
guard evaluation
return paths
```确切的字节码因 Python 版本而异。
## 37.43 最小模式匹配器
一个小型的列表结构匹配器可以展示这个想法。```python
def match_pair(value):
if isinstance(value, (list, tuple)) and len(value) == 2:
x, y = value
return True, {"x": x, "y": y}
return False, {}
```使用:```python
ok, binds = match_pair([1, 2])
if ok:
print(binds["x"] + binds["y"])
```这反映了一小部分:```python
match value:
case [x, y]:
print(x + y)
```CPython 通过编译的字节码和运行时助手而不是绑定字典来实现这一点。
## 37.44 常见误解
|误会 |正确型号 |
|---|---|
|`match`只是一个 switch 语句 |它执行结构匹配和绑定 |
|`case x`与变量比较`x`|它将主体捕捉到`x` |
| `_`是正常捕获 |`_`是模式中的通配符 |
|针对每个案例对主题进行评估 |评估一次 |
|模式匹配没有副作用 |它可以调用相等性、描述符、守卫和属性钩子 |
|失败的比赛属于例外 |正常模式失败会进入下一种情况 |
|类模式直接检查字段 |他们使用属性访问和`__match_args__`|
|模式匹配总是比`if`|它仍然执行运行时检查 |
## 37.45 阅读策略
从标量情况开始:```python
def f(x):
match x:
case 0:
return "zero"
case _:
return "other"
```然后添加结构:```python
def f(x):
match x:
case [a, b]:
return a + b
case {"name": name}:
return name
```然后添加类:```python
class Point:
__match_args__ = ("x", "y")
def f(p):
match p:
case Point(0, y):
return y
case Point(x, y):
return x + y
```对于每个版本:```python
import dis
dis.dis(f)
```追踪:```text
where the subject is stored
where each case begins
which checks can fail normally
which operations can raise
where names are bound
where guards run
where fallback jumps go
```## 37.46 章节总结
模式匹配是 CPython 的结构条件调度系统。它评估一个主题一次,按顺序尝试案例,检查模式,绑定名称,评估守卫,并执行第一个匹配的主体。
核心模型是:
````文本
评估受试者一次
↓
尝试案例模式
↓
如果模式失败:清理并尝试下一种情况
↓
如果模式成功:绑定名称
↓
如果守卫存在:评估守卫
↓
如果防护为真或不存在:执行主体