# 替换与模式匹配 · 符号操作的基本动作

> 求导、化简、解方程，底层都是同一个动作：在表达式树里找到一个模式，把它替换掉。这一章讲清 SymPy 的三件基本兵器——subs（替换）、match（模式匹配）、replace（按模式批量替换），它们是下一章「计算即重写」的砖石。

- 作者：David（道雾轩）
- 专栏：符号计算的艺术（https://daiw.org/manual/symbolic-computing.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/symbolic-computing/substitution-matching
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 替换与模式匹配 · 符号操作的基本动作

[地基](https://daiw.org/manual/symbolic-computing/expression-tree)搭好了，现在看符号计算最核心的**动作**。你会发现，无论多花哨的操作，拆到底都是两个字：**替换**——在[表达式树](https://daiw.org/manual/symbolic-computing/expression-tree)里找到某个部分，换成别的。这一章讲三件实现「替换」的基本兵器。

## subs：把符号换成别的

最常用的是 `.subs()`——把表达式里的某个符号，换成一个值或另一个表达式：

```python
from sympy import *
x, y, z = symbols('x y z')
e = x**2 + 2*x + 1

print(e.subs(x, 3))          # 16              —— 把 x 换成数 3(即求值)
print(e.subs(x, y + 1))      # (y+1)**2 + 2*(y+1) + 1   —— 把 x 换成表达式
print((x + y).subs({x: 1, y: 2}))    # 3       —— 一次换多个
```

`subs` 就是「代入」。注意它的本质是**树操作**：在 $e$ 的树里，找到所有 $x$ 叶子，把每个都换成新的子树（数 `3`、或子树 `y+1`）。换成表达式时，新子树整个**嫁接**进去。

更妙的是，`subs` 不止能换符号，还能换**整个子表达式**——只要那棵子树在原树里出现：

```python
print((x*y + z).subs(x*y, z))    # 2*z    —— 把子式 x*y 整个换成 z
```

它在树里找到 `x*y` 这棵子树，替换成 `z`，得到 `z + z`，自动[化简](https://daiw.org/manual/symbolic-computing/auto-simplification)成 `2*z`。**这正是化简、变量代换、方程回代的底层机制。**

## match：在树里认出一个「模式」

`subs` 要求你**精确**说出要换什么。但很多时候我们想表达的是一类模式：「任何东西的平方」「$\sin$ 括号里任意倍的 $x$」。这就需要**通配符** `Wild` 和**模式匹配** `.match()`：

```python
a, b = Wild('a'), Wild('b')

print((x**2).match(a**b))          # {a_: x, b_: 2}    —— 认出「a 的 b 次方」
print(sin(2*x).match(sin(a*x)))    # {a_: 2}           —— 认出「sin(a 乘 x)」
```

`Wild('a')` 是个**通配符**，能匹配任意子表达式。`.match(pattern)` 拿模式去套目标树：套得上，就返回一个**字典**，告诉你每个通配符**匹配到了什么**（$a\to x$，$b\to2$）；套不上返回 `None`。

<Callout type="info">
  **模式匹配是符号计算的「眼睛」。** 一条数学规则，比如乘积法则 $(uv)' = u'v + uv'$，左边的 $uv$ 就是一个**模式**（两个任意表达式相乘）。CAS 要应用这条规则，第一步就是用模式匹配**在树里认出**「这是一个乘积」，并抓出 $u$ 和 $v$ 分别是谁。**先「认出」，才能「改写」**——这就是下一章的主题。
</Callout>

## replace：找出所有匹配，一次换掉

`match` 只看整棵树是否匹配。实战中我们常要**在树的各个角落**找出所有符合模式的地方，统统替换——这是 `.replace()`：

```python
# 把表达式里所有的 sin 换成 cos
print((sin(x) + sin(y)**2).replace(sin, cos))    # cos(x) + cos(y)**2

# 配合 Wild:把任意「平方」换成「立方」
print((x**2 + y**2).replace(a**2, a**3))         # x**3 + y**3
```

`replace` 遍历整棵树，每遇到一处匹配 `sin(任意)` 或 `(任意)**2`，就地替换。**一条「模式 → 替换」的规则，加上 `replace` 的遍历，就能把一个变换施加到整个表达式**。

## 三件兵器，一个内核

回头看这三件工具，它们其实是**同一件事**的三个层次：

| 工具 | 干什么 | 一句话 |
| --- | --- | --- |
| `subs` | 把**指定的**东西换掉 | 精确替换 |
| `match` | **认出**符合模式的树，抓出通配符 | 模式识别 |
| `replace` | 找出**所有**匹配的地方，批量替换 | 模式识别 + 替换 |

它们共同指向一个内核：**一条规则 =「模式（match 的对象）+ 替换（subs 的动作）」；应用规则 = 在树里找到模式，把它换成替换的样子。** 求导规则、三角恒等式、化简规则……**全都是这个形状**。

## 小结与下一章

符号操作的基本动作是**替换**：`subs`（把指定符号 / 子式换成别的，本质是树的嫁接）、`match`（用 `Wild` 通配符在树里**认出**模式，返回匹配字典）、`replace`（找出**所有**匹配处批量替换）。三者合起来定义了「**规则 = 模式 + 替换**」这个符号计算的原子。

现在，最深刻的一章来了。既然求导、化简、解方程都是「按规则替换」，那我们不妨把这个想法推到底：**符号计算的全部，是不是就是「反复应用重写规则」？更进一步——「计算」本身，是不是就是这件事？** 答案是响亮的「是」，它把符号计算、λ 演算、Lisp 和数理逻辑连成了一体。👉 [计算即重写：逻辑计算的本质](https://daiw.org/manual/symbolic-computing/term-rewriting)。

## 参考文献

- [1] SymPy 文档.「Manipulation: subs, replace, match, Wild.」https://docs.sympy.org/latest/tutorials/intro-tutorial/manipulation.html
- [2] Joel S. Cohen.《Computer Algebra and Symbolic Computation: Elementary Algorithms》, ch. 3 —— 结构上的替换与匹配。
- [3] Bruno Buchberger et al.《Symbolic Computation》—— 模式匹配在 CAS 中的角色。
