re 是 Python 标准库的正则实现,一套 API 覆盖匹配、搜索、替换、切分与编译复用。正则语法本体见 正则表达式,本文只谈模块用法——函数怎么选、分组怎么取、坑在哪里。
函数怎么选
模块级函数按「想要什么」选,返回值形态直接决定后续代码怎么写:
| 函数 | 返回 | 语义 |
|---|---|---|
re.match | Match | None | 从开头匹配 |
re.fullmatch | Match | None | 整串完全匹配 |
re.search | Match | None | 全串第一处匹配 |
re.findall | list | 全部非重叠匹配 |
re.finditer | Match 迭代器 | 同上,惰性求值 |
re.sub | str | 替换 |
re.subn | (str, n) | 替换并计数 |
re.split | list | 按模式切分 |
re.escape | str | 转义字面文本 |
match 与 search 的区别最容易踩:match 只锚定字符串开头,等价于 pattern 自带 ^;search 扫全串找第一处。验证整串格式(手机号、邮箱)用 fullmatch,它自带 ^...$ 语义且没有 $ 在尾部换行处的歧义,比手动加锚点更稳。
import re
re.match(r'\d+', 'abc123') # None:开头不是数字
re.search(r'\d+', 'abc123') # <re.Match object; span=(3, 6), match='123'>
re.fullmatch(r'\d+', '123abc') # None:未覆盖整串findall 的返回形态随分组数变化:无分组返回整串匹配的列表;恰好一个分组返回该分组内容的列表;多个分组返回 tuple 列表。
想统一拿整串匹配
把分组写成非捕获
(?:...),或直接用finditer——返回 Match 对象,字段自己取,语义最明确:
re.findall(r'(\d+)-(\d+)', 'a 1-2 b 3-4') # [('1', '2'), ('3', '4')]
re.findall(r'(?:\d+)-(?:\d+)', 'a 1-2 b 3-4') # ['1-2', '3-4']
for m in re.finditer(r'(\d+)-(\d+)', 'a 1-2 b 3-4'):
print(m.span(), m.groups()) # (2, 5) ('1', '2') ...Match 对象与命名分组
match/search/fullmatch/finditer 返回 Match 对象,位置与分组信息都在上面:group(0) 整串匹配,group(n) 第 n 个分组,groups() 全部分组 tuple,groupdict() 命名分组字典,start()/end()/span() 位置信息。命名分组 (?P<name>...) 写法之后可按名取值,m['year'] 与 m.group('year') 等价(3.6+ 支持下标写法)。
解析日志这类多字段场景,命名分组的可读性远超数字下标:
log = '2026-09-09 14:32:01 ERROR disk full'
m = re.match(r'(?P<date>\S+) (?P<time>\S+) (?P<level>\w+) (?P<msg>.*)', log)
m['level'] # 'ERROR'
m.groupdict() # {'date': '2026-09-09', 'time': '14:32:01', 'level': 'ERROR', 'msg': 'disk full'}
m.span('msg') # (26, 35)分组还能在 pattern 内部复用:(?P<char>\w)(?P=char) 匹配叠字,(\w)\1 是其匿名写法;在替换文本里则通过 \1 或 \g<name> 引用。
替换:re.sub 的三种形态
re.sub(r'\bcat\b', 'dog', 'cat catalog cat') # 'dog catalog dog'
re.sub(r'(\d+)', r'<\1>', 'a1b22') # 'a<1>b<22>'
re.sub(r'(?P<kw>def)\s+\w+', r'\g<kw> ???', 'def foo') # 'def ???'count 参数限制替换次数,count=1 只换第一处。repl 传函数时,每个匹配调用一次、返回值作为替换文本,大小写转换与查表替换都靠它:
re.sub(r'[a-z]+', lambda m: m.group(0).upper(), 'abc def') # 'ABC DEF'repl 必须 raw string
不加
r时'\1'是八进制转义(chr(1) 控制字符),替换结果悄悄变成乱码而不报错。pattern 与 repl 一律r''开头。
flags
| flag | 简写 | 作用 |
|---|---|---|
re.IGNORECASE | re.I | 忽略大小写 |
re.MULTILINE | re.M | ^ $ 按行匹配 |
re.DOTALL | re.S | . 匹配换行 |
re.VERBOSE | re.X | 模式内允许空白与 # 注释 |
re.ASCII | re.A | \d \w \s \b 仅匹配 ASCII |
多个 flag 用 | 组合,如 re.I | re.M;行内写法 (?i)、(?m)、(?s) 只作用于所在位置之后,适合只让半段 pattern 大小写不敏感。
Python 3 起 \d/\w/\s 默认 Unicode 语义,\w 能匹配中文。爬虫解析 URL 或协议报文时混入非 ASCII 字符常源于此,显式加 re.A 收紧。
re.X 让复杂模式可以排版,长正则从此可维护:
pattern = re.compile(r'''
(\d{4})-(\d{2})-(\d{2}) # 日期
\s+ # 分隔
(\d{2}):(\d{2}) # 时间
''', re.X)编译复用
模块级函数内部有编译缓存(约 512 条 LRU),偶尔调用无需手动 compile。但同一 pattern 高频调用——逐行解析日志、循环内匹配——预编译成 Pattern 对象更清晰也更稳,方法签名与模块级函数一致:
NUM = re.compile(r'\d+')
NUM.findall('a1b22') # ['1', '22']
NUM.sub('', 'a1b22') # 'ab'常见坑
字面文本必须 re.escape。把用户输入或变量拼进 pattern 时,re.escape(s) 转义全部元字符——既防 a.b 里的 . 偷换语义,也防 ( 未闭合直接抛 re.error。
split 遇到捕获分组会把分组带进结果:
re.split(r'(-)', 'a-b-c') # ['a', '-', 'b', '-', 'c']
re.split(r'(?:-)', 'a-b-c') # ['a', 'b', 'c']嵌套量词引发灾难性回溯。(a+)+b 这类模式在长的不匹配串上耗时指数级增长,是正则 DoS 的根源。Python 3.11 加入原子分组 (?>...) 与占有优先量词 *+,匹配失败时不回溯;低版本只能改写 pattern、收敛可回溯范围。
空匹配。可匹配空串的模式(\b、\d*)在 findall 里会产出空串结果、在 sub 里于每个字符间插入替换文本,调试时先检查模式是否可空匹配。
落到日常,90% 的 re 代码就是 search/findall/sub 加命名分组。写 pattern 前先在交互环境用 finditer 打一遍 span 验证边界行为,比盯着字符串猜快得多。