re 是 Python 标准库的正则实现,一套 API 覆盖匹配、搜索、替换、切分与编译复用。正则语法本体见 正则表达式,本文只谈模块用法——函数怎么选、分组怎么取、坑在哪里。

函数怎么选

模块级函数按「想要什么」选,返回值形态直接决定后续代码怎么写:

函数返回语义
re.matchMatch | None开头匹配
re.fullmatchMatch | None整串完全匹配
re.searchMatch | None全串第一处匹配
re.findalllist全部非重叠匹配
re.finditerMatch 迭代器同上,惰性求值
re.substr替换
re.subn(str, n)替换并计数
re.splitlist按模式切分
re.escapestr转义字面文本

matchsearch 的区别最容易踩: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.IGNORECASEre.I忽略大小写
re.MULTILINEre.M^ $ 按行匹配
re.DOTALLre.S. 匹配换行
re.VERBOSEre.X模式内允许空白与 # 注释
re.ASCIIre.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 验证边界行为,比盯着字符串猜快得多。