Python3 模式匹配:从基础到高级的匹配技巧
Python 模式匹配:从 in 到正则再到 match-case,一招对一招
处理文本和数据,绕不开匹配这件事——判断一段文字里有没有某个词、提取一个手机号、解析一段 JSON 结构。
Python 提供了三个层级的工具:字符串方法(最基础)、正则表达式(最强大)、match-case 结构化匹配(Python 3.10+,专门对付数据结构)。遇到什么场景用什么工具,比学会所有工具的语法更重要。
最简单的:字符串自带的方法
能用内置方法解决的问题,就别上正则。代码短、速度快、不用记那些奇怪的符号。
1. 有没有:in 和 not in
text = "hello world, hello python"
if "hello" in text:
print("找到了")
这是最直观的写法,没有之一。find() 也可以,但 in 更符合直觉。
2. 在哪:find() 和 rfind()
text.find("hello") # 0,第一次出现的位置
text.rfind("hello") # 13,从右边找
text.find("java") # -1,找不到返回 -1
如果你还需要知道位置,用 find()。注意它返回 -1 表示没找到,不是抛异常。
3. 开头结尾:startswith() 和 endswith()
url.startswith("https") # True
filename.endswith(".txt") # True
# 可以传元组,匹配任意一个
url.startswith(("http://", "https://")) # True
filename.endswith((".txt", ".md")) # True
批量处理文件的时候非常好用:
files = ["data.txt", "image.png", "notes.md"]
txt_files = [f for f in files if f.endswith(".txt")]
文件名通配符:fnmatch
如果你需要 * 和 ? 这种简单的通配符,又不想写正则,fnmatch 是折中方案。
from fnmatch import fnmatch
fnmatch("document.txt", "*.txt") # True
fnmatch("config.json", "config.*") # True
# 批量过滤
files = ["main.py", "utils.py", "test.py"]
py_files = [f for f in files if fnmatch(f, "*.py")]
fnmatch 比正则简单,比字符串方法灵活,适合文件名匹配这类场景。但仅限于 * 和 ?,复杂的就别用它了。
复杂文本:正则表达式,一劳永逸
遇到手机号、邮箱、URL、日志解析这种复杂模式,别用字符串方法硬凑,直接用正则。
常用写法速查:
| 需求 | 正则 |
|---|---|
| 手机号(11位) | 1[3-9]\d{9} |
| 邮箱 | \w+@\w+\.\w+ |
| 数字 | \d+ |
| 中文 | [\u4e00-\u9fa5]+ |
| URL | https?://[^\s]+ |
| 日期(YYYY-MM-DD) | \d{4}-\d{2}-\d{2} |
提取手机号:
import re
text = "联系我:13812345678 或 13987654321"
phones = re.findall(r"1[3-9]\d{9}", text)
# ['13812345678', '13987654321']
提取邮箱 + 去重:
text = "test@example.com, admin@test.com, test@example.com"
emails = set(re.findall(r"\w+@\w+\.\w+", text))
# {'admin@test.com', 'test@example.com'}
提取捕获组:
log = "[2026-07-02 10:30:45] ERROR: 数据库连接超时"
pattern = r"\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+): (.*)"
match = re.search(pattern, log)
if match:
time = match.group(1) # "2026-07-02 10:30:45"
level = match.group(2) # "ERROR"
msg = match.group(3) # "数据库连接超时"
替换敏感词:
sensitive = ["垃圾", "废物", "骗子"]
pattern = re.compile("|".join(sensitive))
text = "这个产品太垃圾了,简直是废物"
pattern.sub("***", text) # "这个产品太***了,简直是***"
预编译提升性能: 如果你用同一个正则在大量文本上反复匹配,用 re.compile() 预编译能快 30%-50%。
# 先编译一次
phone_pattern = re.compile(r"1[3-9]\d{9}")
# 反复使用
for line in log_lines:
phone_pattern.findall(line)
结构化数据:Python 3.10 的 match-case
正则对付的是文本。如果你的数据已经是列表、字典、对象了,再用正则去解析就是绕远路。match-case 是专门对付数据结构的。
基础语法:
match 变量:
case 模式1:
# 匹配模式1
case 模式2:
# 匹配模式2
case _:
# 默认(类似 else)
匹配列表结构:
def handle(data):
match data:
case [1, 2, x]: # 列表前两个是1和2
print(f"第三个是:{x}")
case [x, y, z]: # 任意三个元素
print(f"三个元素:{x},{y},{z}")
case [*rest]: # 任意长度
print(f"其他:{rest}")
case _:
print("不匹配")
handle([1, 2, 3]) # 第三个是:3
handle([1, 2, 4]) # 第三个是:4
handle([5, 6, 7]) # 三个元素:5,6,7
匹配字典结构:
def handle_response(resp):
match resp:
case {"status": 200, "body": body}:
print(f"成功:{body[:50]}...")
case {"status": 404}:
print("资源不存在")
case {"status": s} if 500 <= s < 600:
print(f"服务器错误:{s}")
case _:
print("未知响应")
handle_response({"status": 200, "body": "OK"})
handle_response({"status": 404})
匹配类对象:
from dataclasses import dataclass
class Point:
x: int
y: int
def describe(p):
match p:
case Point(0, 0):
print("原点")
case Point(x, 0):
print(f"x轴上:({x}, 0)")
case Point(0, y):
print(f"y轴上:(0, {y})")
case Point(x, y):
print(f"普通点:({x}, {y})")
describe(Point(0, 0)) # 原点
describe(Point(5, 0)) # x轴上:(5, 0)
带守卫条件(if 附加判断):
case {"name": name, "age": age} if age >= 18:
print(f"成年:{name}")
case {"name": name, "age": age}:
print(f"未成年:{name}")
match-case 最擅长的是”根据数据结构的不同形状走不同分支”,而不是”判断文本内容”。数据结构越复杂,它越有用。