贪婪是怎么实现的
贪婪量词先按最大可能的次数去试,把长度吃满;如果后面接不下去,就退回一步再试,如此往复。所谓贪婪并不是「必然取到最长结果」,而是「在当前尝试路径上从最长的方案开始试」,最终长度取决于后续部分能否成立。
为什么影响结果
当模式里存在成对的分隔符或多处可匹配位置时,贪婪会让引擎选到最靠后的那个终点,于是提取结果包含多个片段。这不是引擎出错,而是模式没把边界写死。
基本信息
| 默认行为 | 所有量词(星号、加号、问号、花括号)都贪婪 |
|---|---|
| 尝试顺序 | 先最多,后逐步减少 |
| 结果特征 | 在有多个可匹配终点时倾向取最后一个 |
| 改成非贪婪 | 量词后加问号 |
| 改成占有量词 | 量词后加加号(PCRE、Java 等支持)或改写为原子组 |
典型使用场景
- 提取整行内容时希望连同后续内容一起取到行尾,贪婪正好符合预期
- 校验收尾分隔符位置,贪婪配合行尾锚点可直接定位到最后一个分隔符之后
- 拆分结构化文本时,往往需要非贪婪或精确字符类来避免跨段
- 排查提取结果过长时,先检查是不是贪婪量词吃过了头
- 在需要严格边界的场景里用非贪婪加断言替代纯贪婪
贪婪与非贪婪对比
贪婪与非贪婪对比
import re
s = '<b>1</b><b>2</b>'
re.search(r'<b>.*</b>', s).group(0) # 贪婪:一直吃到最后一个闭合标签
re.search(r'<b>.*?</b>', s).group(0) # 非贪婪:只取第一段
re.search(r'[^<]*', '<b>1') # 用精确字符类替代点号,边界更可靠
re.search(r'^.*$', 'a\nb', re.M).group(0) # 行尾锚点前的贪婪只在本行内生效
re.search(r'(?s).*', 'a\nb').group(0) # 打开跨行后贪婪会吃满全文三种尝试策略
| 策略 | 写法 | 失败时的行为 |
|---|---|---|
| 贪婪 | 星号或加号或问号 | 从最长开始逐步回退 |
| 非贪婪 | 量词后加问号 | 从最短开始逐步加长 |
| 占有 | 量词后加加号(部分引擎) | 不回退,失败即整体失败 |
非贪婪并不能替代精确字符类。存在嵌套结构时,非贪婪取到的仍可能不是目标片段;能确定内容边界的,优先写成明确的字符类或断言。
贪婪带来的真正代价是回退次数。模式里同时出现多点号加星号、嵌套重复与不定长度前缀时,回退量会随输入长度急剧增长,极端情况下表现为正则引擎长时间不返回。