← 返回题目列表

Python re 模块怎么用?正则匹配中 match、search、findall 有什么区别?

高频 中等 第 14 / 27 题 更新于 2026/07/31
re正则表达式matchsearch

简化版

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 管结果规模,分组会改变返回结构,贪婪和回溯会影响正确性与性能。写正则时先问“我要从哪匹配、要几个结果、要哪些分组、输入规模多大”,比盲写一个 .* 稳得多。