正则表达式的应用
1. 正则表达式概述
处理文本时,常需要按某种"规则"检查、提取或改写片段,例如从日志里找出订单号、校验表单字段格式、把敏感数字中间位打码。正则表达式(Regular Expression)就是描述这类规则的专用记法:它定义一种模式,用来判断字符串是否匹配,或从字符串中取出/替换匹配部分。
可以把它理解成比文件名通配符(*、?)更精细的匹配语言:表达力更强,书写成本也更高。今天绝大多数语言都内置了正则支持;在 Python 中,标准库模块 re 负责编译与匹配。
更多语法可参考通用入门材料,例如 正则表达式 30 分钟入门。读完后,下表中的常用符号会更容易对照使用。
| 符号 | 含义 | 示例 | 说明 |
|---|---|---|---|
. |
任意单个字符 | c.t |
可匹配 cat、c9t 等 |
\w |
字母、数字、下划线 | \w+ |
一段"词"字符 |
\s |
空白(空格、制表、换行等) | id\s*= |
id 与 = 之间允许空白 |
\d |
数字 | \d{4} |
恰好 4 位数字 |
\b |
单词边界 | \bEND\b |
独立单词 END |
^ / $ |
串首 / 串尾 | ^ERR、\.log$ |
以 ERR 开头、以 .log 结尾 |
\W \S \D \B |
与上面几类相反 | \D+ |
连续非数字 |
[...] / [^...] |
字符集 / 取反 | [A-Z]、[^0-9] |
大写字母;非数字 |
* / + / ? |
0 次及以上 / 1 次及以上 / 0 或 1 次 | \d+ |
至少一位数字 |
{n} {m,} {m,n} |
固定或区间次数 | \d{2,4} |
2~4 位数字 |
| ` | ` | 分支(或) | `GET |
(exp) |
捕获分组 | (\d+)-(\d+) |
可取各组内容 |
(?:exp) |
非捕获分组 | (?:ab)+ |
只分组不捕获 |
(?=exp) / (?<=exp) |
正向 / 反向先行断言 | \d+(?=元) |
数字后紧跟"元" |
(?!exp) / (?<!exp) |
负向断言 | (?<!\d)\d{6}(?!\d) |
恰好 6 位且两侧非数字 |
*? +? ?? {m,n}? |
懒惰量词 | a.*?b |
尽可能短地匹配 |
说明: 若要匹配正则元字符本身,需转义,例如字面小数点写作
\.,字面括号写作\(\)。
2. Python 中的 re 模块
| 函数 / 标记 | 作用 |
|---|---|
compile(pattern, flags=0) |
编译模式,得到可复用的正则对象 |
match(pattern, string, flags=0) |
从字符串开头 尝试匹配,成功返回匹配对象,否则 None |
fullmatch(pattern, string, flags=0) |
要求整串完全符合模式 |
search(pattern, string, flags=0) |
在串中查找第一次出现 |
findall(pattern, string, flags=0) |
返回所有匹配的字符串列表(有分组时结构会变化) |
finditer(pattern, string, flags=0) |
返回匹配对象的迭代器 |
split(pattern, string, maxsplit=0, flags=0) |
按模式切分,返回列表 |
sub(pattern, repl, string, count=0, flags=0) |
用替换内容替换匹配;repl 可为字符串或函数 |
purge() |
清理隐式编译缓存 |
re.I / re.IGNORECASE |
忽略大小写 |
re.M / re.MULTILINE |
多行模式下 ^ $ 作用于每行 |
关键概念:
- 同一模式若会多次使用,应先
compile,再调用对象的match/search/findall等方法。 - 书写模式时推荐原始字符串
r'...',避免\被 Python 字符串先转义一层(否则\d常要写成\\d)。
多个 flags 可用按位或组合,例如 flags=re.I | re.M。
3. 应用示例
3.1 校验工号与邮箱(match / fullmatch)
规则约定:
- 工号:2 位大写字母 + 4~6 位数字,整串合法(如
HR1024、IT90001)。 - 邮箱:本地部分允许字母数字、点、下划线、百分号、加号、减号;
@后为域名片段。
python
import re
staff_id = input('工号: ').strip()
email = input('邮箱: ').strip()
# match:从开头匹配;配合 ^...$ 等价于"整串符合"
m_id = re.match(r'^[A-Z]{2}\d{4,6}$', staff_id)
if not m_id:
print('工号格式无效')
# fullmatch:整串必须完全匹配,模式里可不写 ^ $
m_mail = re.fullmatch(
r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}',
email,
)
if not m_mail:
print('邮箱格式无效')
if m_id and m_mail:
print('校验通过')
提示:
match只锚定开头;若未写$,AB1234xyz在宽松模式下可能被误判为"从开头已匹配成功"。校验整字段时优先fullmatch,或显式使用^...$。
3.2 从日志中提取订单号(compile / findall / finditer / search)
订单号形如 ORD- + 8 位数字。用前后断言避免与更长数字串粘连,并演示三种取结果的方式。
python
import re
# 左侧非数字、右侧非数字,降低误匹配
pattern = re.compile(r'(?<!\d)ORD-\d{8}(?!\d)')
log_text = """
[INFO] create ORD-20250101 ok
retry ORD-20250102; noise ORD-20250102111 ignored
client sent ORD-20250315 and ORD-20250316
"""
# 方法一:全部匹配成列表
print(re.findall(pattern, log_text))
# 方法二:迭代匹配对象
for m in pattern.finditer(log_text):
print(m.group(), 'at', m.start())
# 方法三:search 从上次结束位置继续扫
m = pattern.search(log_text)
while m:
print(m.group())
m = pattern.search(log_text, m.end())
关键点:
findall适合只要文本;需要位置或分组细节时用finditer。search(..., pos)可从指定下标继续搜,适合手写扫描循环。
若号段规则更复杂,可把主体改成分支,例如 ORD-(?:20\d{6}|9\d{7}),再保留两侧断言。
3.3 脱敏证件号与手机号(sub)
将 11 位手机号中间 4 位、18 位证件号中间若干位替换为 *。repl 使用函数,按分组拼接。
python
import re
def mask_sensitive(text: str) -> str:
def repl_phone(m: re.Match) -> str:
s = m.group()
return s[:3] + '****' + s[7:]
def repl_id(m: re.Match) -> str:
s = m.group()
return s[:4] + '*' * 10 + s[-4:]
text = re.sub(r'(?<!\d)1\d{10}(?!\d)', repl_phone, text)
text = re.sub(
r'(?<!\d)\d{17}[\dXx](?!\d)',
repl_id,
text,
flags=re.IGNORECASE,
)
return text
sample = '联系人 13812345678,证件 110101199001011234,备用 13900001111'
print(mask_sensitive(sample))
# 输出类似:联系人 138****5678,证件 1101**********1234,备用 139****1111
说明:
flags=re.I(或re.IGNORECASE)让证件末位x/X都能匹配。多个标志用|叠加。真实业务号段、校验位规则更严,此处仅演示替换接口。
3.4 按多种分隔符拆分配置行(split)
配置项可能用逗号、分号或竖线分隔,分隔符后允许空格。
python
import re
line = 'host=10.0.0.8; port=8080 | timeout=3, retries=2'
parts = re.split(r'[,;|]\s*', line)
fields = [p for p in parts if p]
for item in fields:
print(item)
# host=10.0.0.8
# port=8080
# timeout=3
# retries=2
split 得到的列表可能含空串(首尾或连续分隔符),用列表推导过滤即可。需要限制段数时传入 maxsplit。
总结
| 场景 | 常用接口 |
|---|---|
| 整字段是否合法 | fullmatch,或 match + ^...$ |
| 取出全部片段 | findall / finditer |
| 从某位置继续找 | search + 结束下标 |
| 改写匹配内容 | sub(字符串或回调) |
| 按模式切开 | split |
| 模式复用 | 先 compile |
正则擅长结构化文本的校验、抽取与改写;模式越复杂,越建议用在线调试工具边写边测。业务规则变更时,优先收紧字符类与断言,并补上边界用例(空串、粘连数字、大小写),避免"能跑但误伤"。
`)