Python re 模块怎么用?正则匹配中 match、search、findall 有什么区别?
简化版
Python 的 re 模块用于正则匹配、提取和替换。match 只从字符串开头匹配,search 在任意位置找第一个匹配,findall 返回所有匹配结果,sub 用于替换。复杂正则要注意贪婪匹配、分组、转义和回溯性能问题。
详细版
常见 API 区别如下:re.match(pattern, s) 要求从开头匹配;re.search(pattern, s) 找到第一个位置即可;re.findall(pattern, s) 返回所有匹配;re.finditer(pattern, s) 返回迭代器,适合大文本;re.sub(pattern, repl, s) 做替换。
import re
s = "id=123, id=456"
re.search(r"\d+", s).group() # "123"
re.findall(r"\d+", s) # ["123", "456"]
re.sub(r"\d+", "X", s) # "id=X, id=X"
面试重点不是背 API,而是理解边界:是否从开头匹配、是否需要全部结果、分组返回形态、是否预编译、用户输入正则是否可能造成灾难性回溯。
完整版教学
一、正则适合解决什么问题
正则适合处理“有规律的文本模式”,比如提取手机号、校验简单格式、拆日志字段、替换模板片段。它不适合解析复杂嵌套语法,比如完整 HTML、SQL、编程语言源码,这些更适合专门解析器。
适合: 2026-07-31 ERROR order_id=1001
不适合: 任意嵌套 HTML / Python 源码
正则强在简洁,但可读性和性能都需要控制。一个 20 字符的清晰正则很好,一个 200 字符无人敢改的正则就是维护风险。
二、match、search、fullmatch 的边界
match 从开头尝试匹配,不要求匹配到结尾;search 在整个字符串中寻找第一个匹配;fullmatch 要求整个字符串都匹配。很多校验 bug 就来自把 match 当成完整校验。
import re
re.match(r"\d+", "123abc") # 匹配 "123"
re.fullmatch(r"\d+", "123abc") # None
re.search(r"\d+", "abc123") # 匹配 "123"
| API | 匹配位置 | 典型用途 |
|---|---|---|
match | 开头 | 前缀模式 |
search | 任意位置第一个 | 查找是否包含 |
fullmatch | 整串 | 参数校验 |
做格式校验时优先想
fullmatch,不要误把match当“全匹配”。
三、findall 和 finditer 怎么选
findall 一次性返回所有结果列表,简单直观;finditer 返回迭代器,每次给一个 match object,更适合大文本和需要位置 span 的场景。
text = "a1 b22 c333"
re.findall(r"\d+", text) # ["1", "22", "333"]
for m in re.finditer(r"\d+", text):
print(m.group(), m.span())
如果文本有 100MB,匹配结果有 100 万个,findall 会一次性把结果列表放进内存;finditer 可以边扫边处理,内存压力更小。
四、分组会影响返回结果
findall 遇到捕获分组时,返回的不是整个匹配,而是分组内容;多个分组时返回元组列表。这是非常常见的面试坑。
re.findall(r"(\w+)=(\d+)", "a=1 b=2")
# [("a", "1"), ("b", "2")]
re.findall(r"\w+=\d+", "a=1 b=2")
# ["a=1", "b=2"]
如果只是为了分组但不想捕获,可以用非捕获分组 (?:...)。这样既能表达优先级,又不改变返回结构。
re.findall(r"(?:id|uid)=(\d+)", "id=1 uid=2")
# ["1", "2"]
五、贪婪和非贪婪要怎么理解
量词 *、+、? 默认是贪婪的,会尽可能多匹配;加 ? 变成非贪婪,会尽可能少匹配,但仍要满足整体模式。
s = "<b>one</b><b>two</b>"
re.findall(r"<b>.*</b>", s)
# ["<b>one</b><b>two</b>"]
re.findall(r"<b>.*?</b>", s)
# ["<b>one</b>", "<b>two</b>"]
贪婪不是“错”,非贪婪也不是“万能”。如果格式明确,使用更精确的字符类常常比 .*? 更稳,比如 [^<]*。
六、性能和安全为什么要关注
某些正则在特定输入下会发生灾难性回溯,耗时从毫秒级变成秒级甚至更久。常见风险是嵌套量词,比如 (a+)+$ 匹配很多 a 后面跟一个不匹配字符。
pattern: (a+)+$
input: aaaaaaaaaaaaaaaaa!
随着 a 数量增加,回溯组合急剧增长。用户可控正则或用户可控输入都要小心,必要时限制长度、避免危险模式、使用超时机制或更安全的解析方案。
七、常见误区与追问
- 误区:
match是匹配整个字符串。 它只从开头匹配,完整校验应考虑fullmatch。 - 误区:
findall永远返回完整匹配。 有捕获分组时会返回分组内容或元组。 - 误区:非贪婪一定更安全。 非贪婪只是匹配策略,复杂模式仍可能回溯严重。
- 追问:什么时候预编译正则? 同一模式重复使用时用
re.compile,可减少重复编译并让代码更清晰。 - 追问:原始字符串
r""有什么用? 减少反斜杠转义混乱,正则里通常推荐使用。 - 追问:如何取匹配位置? 使用 match object 的
span()、start()、end()。 - 追问:日志解析用正则还是 split? 格式简单固定时 split 更清晰;模式复杂或字段可变时正则更合适。
八、加强记忆
re 模块记住四组边界:match/search/fullmatch 管位置,findall/finditer 管结果规模,分组会改变返回结构,贪婪和回溯会影响正确性与性能。写正则时先问“我要从哪匹配、要几个结果、要哪些分组、输入规模多大”,比盲写一个 .* 稳得多。