3. Python re 模块入门:re.match、re.search、re.findall
正则表达式的精髓在于「匹配」。你写好了模式,接下来就得用 Python 的 re 模块去执行它。这个模块提供了三个最常用的函数:re.match、re.search 和 re.findall。说白了,它们干的是同一件事——在字符串里找东西,但找的方式和结果都不一样。
我个人习惯把这组函数叫做「匹配三剑客」。当年刚学的时候也混淆过,后来踩了坑才真正记住。今天我们就把它彻底讲清楚。
3.1 先看全局:三个函数的定位
用一句话总结它们的区别:
- re.match —— 从字符串开头匹配,只要开头不匹配就返回
None。 - re.search —— 扫描整个字符串,找到第一个匹配的位置就停。
- re.findall —— 扫描整个字符串,找到所有匹配的结果,返回列表。
你想想看,这三个函数拼在一起,几乎覆盖了所有常见的正则匹配场景。下面我逐个拆开讲,配上代码,再补几个我踩过的坑。
3.2 re.match:严格的「开头控」
re.match(pattern, string) 会从字符串的起始位置开始匹配。如果开头字符不满足模式,直接返回 None。嗯,这里要注意:它不会跳过开头去别处找。
我在项目中遇到过这样一个坑:用 re.match(r'\d+', 'abc123') 检查字符串是否包含数字,结果返回 None。当时我百思不得其解,后来才反应过来——数字没出现在开头。
/var/log 开头。
import re
text = "2024-12-25 日志文件.log"
# 匹配开头的日期
match = re.match(r'\d{4}-\d{2}-\d{2}', text)
if match:
print(match.group()) # 输出:2024-12-25
else:
print("开头没匹配上")
# 这个字符串开头没有数字,所以 match 会返回 None
text2 = "日志2024.log"
print(re.match(r'\d+', text2)) # None
3.3 re.search:找第一个就停
re.search(pattern, string) 不要求开头匹配。它会扫描整个字符串,找到第一个匹配的位置就停下来,返回一个 Match 对象。如果全串都找不到,才返回 None。
说白了,search 比 match 更“宽容”。我平时检测字符串里有没有某种格式时,90% 的情况都用 search。
re.search。它最接近日常的「查找」直觉。
text = "我的邮箱是 hello@example.com,请发送到那里。"
search = re.search(r'[\w.+-]+@[\w-]+\.[\w.-]+', text)
if search:
print(search.group()) # hello@example.com
else:
print("没找到邮箱")
# 注意:search 只返回第一个匹配,后面的不管
text2 = "邮箱1: a@b.com, 邮箱2: c@d.com"
search2 = re.search(r'[\w.+-]+@[\w-]+\.[\w.-]+', text2)
print(search2.group()) # 只输出 a@b.com
3.4 re.findall:全部给我出来
当需要提取字符串中所有符合模式的内容时,re.findall(pattern, string) 就是最佳选择。它返回一个列表,里面装着所有匹配到的字符串(没有 Match 对象,直接是文本)。如果找不到,返回空列表 []。
我记得有一次要解析几十万行的服务器日志,提取所有 IP 地址。用 re.findall 一行搞定,配合 set 去重,几秒钟就跑完了。要是用 search 循环,代码得复杂好几倍。
findall 的行为会变化——只返回分组捕获的内容,而不是整个匹配。
text = "用户A: 13800138000 用户B: 15912345678"
phones = re.findall(r'1[3-9]\d{9}', text)
print(phones) # ['13800138000', '15912345678']
# 如果模式有分组,findall 只返回分组内容
text2 = "温度: 25°C, 湿度: 60%"
# 注意:有两个分组,findall 返回元组列表
pairs = re.findall(r'(\w+):\s*(\d+)', text2)
print(pairs) # [('温度', '25'), ('湿度', '60')]
3.5 核心对比
下面这张表,我建议你收藏。遇到选择困难症时翻一翻就清楚了。
| 函数 | 匹配策略 | 返回类型 | 适用场景 |
|---|---|---|---|
re.match |
从开头匹配 | Match 对象 或 None |
校验前缀、固定格式开头 |
re.search |
扫描第一个匹配 | Match 对象 或 None |
判断字符串是否包含某个模式 |
re.findall |
扫描所有匹配 | 字符串列表(无分组时)或元组列表(有分组时) | 批量提取所有符合条件的子串 |
3.6 避坑指南 & 个人经验
我曾经犯过一个特别低级的错误:在 re.match 里加了 ^ 锚点,又在 search 里用了 $ 结尾,结果写了个模式到处乱配。后来我学乖了——用之前先想清楚逻辑:你是要「开头强制」、「任意位置第一个」还是「全部搜集」?想清楚再选函数。
还有一个常见的坑:re.findall 遇到嵌套分组时返回的是元组列表,很多新手直接把它当成字符串列表用,导致 TypeError。我记得之前有个同事在线上环境踩了这个雷,日志全打花了。所以说,如果模式写了很多括号,先用 re.finditer 代替 findall,它返回迭代器且每个元素都是 Match 对象,便于取 .group() 和 .groups()。
re.findall 中用 group() 方法——它返回的是列表,没有 group()。
嗯,学到这里,你已经掌握了 Python 正则最核心的三个函数。结合前面的模式语法,现在你可以做很多事了:提取手机号、解析邮箱、校验日志前缀……后面我们会把这些技巧组合成完整的敏感数据识别实战。