Python 正则表达式贪婪与非贪婪匹配
量词决定匹配的贪婪程度,影响匹配结果的边界和长度。
贪婪量词
默认贪婪,尽可能多地匹配字符。
Python
import re
text = "<div>content</div><span>text</span>"
# 贪婪匹配:匹配到最后一个 </>
match = re.search(r"<.*>", text)
print(match.group()) # <div>content</div><span>text</span>
贪婪量词列表:
| 量词 | 含义 | 贪婪行为 |
|---|---|---|
| * | 0 次或多次 | 尽可能多 |
| + | 1 次或多次 | 尝试多次 |
| ? | 0 欧姆或 1 次 | 尝试 1 次 |
| {n,m} | n 到 m 次 | 尝试 m 次 |
| {n,} | 至少 n 次 | 尽可能多 |
非贪婪量词
在量词后加 ?,尽可能少地匹配。
Python
import re
text = "<div>content</div><span>text</span>"
# 非贪婪匹配:匹配到第一个 >
match = re.search(r"<.*?>", text)
print(match.group()) # <div>
# 匹配所有标签
matches = re.findall(r"<.*?>", text)
print(matches) # ['<div>', '</div>', '<span>', '</span>']
非贪婪量词列表:
| 量词 | 含义 | 非贪婪行为 |
|---|---|---|
| *? | 0 次或多次 | 尽可能少 |
| +? | 1 次或多次 | 尽可能少 |
| ?? | 0 次或 1 次 | 尝试 0 次 |
| {n,m}? | n 到 m 次 | 尝试 n 次 |
| {n,}? | 至少 n 次 | 尝试 n 次 |
匹配行为对比
Python
import re
text = "aaaa"
# 贪婪:尽可能多
match = re.search(r"a+", text)
print(match.group()) # aaaa
# 非贪婪:尽可能少
match = re.search(r"a+?", text)
print(match.group()) # a
实际应用场景
提取标签内容
Python
import re
text = "<div>Hello</div><div>World</div>"
# 贪婪:错误提取
match = re.search(r"<div>.*</div>", text)
print(match.group()) # <div>Hello</div><div>World</div>
# 非贪婪:正确提取
matches = re.findall(r"<div>(.*?)</div>", text)
print(matches) # ['Hello', 'World']
提取引号内容
Python
import re
text = "'first' and 'second'"
# 贪婪:匹配到最后一个引号
match = re.search(r"'(.*)'", text)
print(match.group(1)) # first' and 'second
# 非贪婪:匹配到第一个引号结束
matches = re.findall(r"'(.*?)'", text)
print(matches) # ['first', 'second']
提取 URL 参数
Python
import re
text = "url?a=1&b=2&c=3"
# 贪婪:匹配过多
match = re.search(r"url\?(.*)", text)
print(match.group(1)) # a=1&b=2&c=3
# 非贪婪:单独提取每个参数
params = re.findall(r"(\w+)=(\w+)", text)
print(params) # [('a', '1'), ('b', '2'), ('c', '3')]
匹配回溯机制
Python
import re
text = "123abc456"
# 贪婪匹配流程
# 1. \d+ 匹配 "123abc456"(尝试全部数字)
# 2. 回溯找 abc,失败
# 3. 回溯到 "123abc45",失败
# 4. 最终匹配 "123" 前的部分数字
match = re.search(r"\d+abc\d+", text)
print(match.group()) # 123abc456
非贪婪陷阱
Python
import re
text = "abc123def456"
# 非贪婪可能不是最小匹配
match = re.search(r"a.*?(\d+)", text)
print(match.group(1)) # 123(而非期望的全部数字)
# 需要配合更精确的模式
matches = re.findall(r"\d+", text)
print(matches) # ['123', '456']
固定宽度避免贪婪问题
Python
import re
text = "abc12345def"
# 指定宽度避免贪婪和非贪婪的歧义
match = re.search(r"\d{5}", text)
print(match.group()) # 12345
# 精确宽度比非贪婪更可控
性能对比
Python
import re
import time
text = "a" * 10000 + "b"
# 贪婪匹配可能回溯多次
start = time.time()
re.search(r"a.*b", text)
print(f"贪婪: {time.time() - start:.4f}s")
# 非贪婪匹配更快找到边界
start = time.time()
re.search(r"a.*?b", text)
print(f"非贪婪: {time.time() - start:.4f}s")
要点总结
- 贪婪量词尽可能多匹配,可能回溯
- 非贪婪量词加
?,尽可能少匹配 *?、+?、??、{n,m}?是非贪婪形式- 提取边界内容时优先使用非贪婪
- 非贪婪配合精确模式效果更好
- 固定宽度
{n}避免贪婪问题 - 理解回溯机制有助于调试复杂正则
- 合理选择贪婪/非贪婪影响匹配结果
📝 发现内容有误?点击此处直接编辑