一、概述
正则表达式(Regular Expression,简称regex/re)是处理字符串的强大工具,它可以用非常简洁的语法实现复杂的字符串匹配、查找、替换、分割等操作,在文本处理、数据清洗、表单验证等场景中应用非常广泛。Python通过标准库re模块提供了完整的正则表达式支持,本文将总结Python中正则表达式的常用技巧以及常见的坑点,帮助大家避免踩坑,写出高效正确的正则表达式。
二、核心知识点
1. 基础语法快速回顾
常用元字符:
– .:匹配除换行符外的任意字符
– ^:匹配字符串开头
– $:匹配字符串结尾
– *:匹配前面的字符0次或多次
– +:匹配前面的字符1次或多次
– ?:匹配前面的字符0次或1次
– {n}:匹配前面的字符n次
– {n,}:匹配前面的字符至少n次
– {n,m}:匹配前面的字符n到m次
– []:字符集,匹配括号内的任意一个字符
– |:或运算符,匹配左右任意一个表达式
– ():分组,将括号内的内容作为一个整体,匹配结果可以单独提取
常用转义序列:
– \d:匹配数字,等价于[0-9]
– \w:匹配字母、数字、下划线,等价于[a-zA-Z0-9_]
– \s:匹配空白字符(空格、制表符、换行符等)
– \D:匹配非数字
– \W:匹配非字母数字下划线
– \S:匹配非空白字符
2. 常用操作示例
(1)匹配与查找
import re
# 判断整个字符串是否匹配
pattern = r'^1[3-9]\d{9}$' # 手机号正则
print(re.match(pattern, '13812345678')) # 匹配成功返回Match对象,否则返回None
# 查找第一个匹配项
text = "我的电话是13812345678,邮箱是test@example.com"
phone = re.search(r'1[3-9]\d{9}', text)
print(phone.group()) # 输出:13812345678
# 查找所有匹配项
emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', text)
print(emails) # 输出:['test@example.com']
# 迭代查找所有匹配项
for match in re.finditer(r'\d+', "abc123def456ghi789"):
print(match.group()) # 依次输出123、456、789
(2)替换与分割
# 替换匹配项
text = "Hello 123 World 456"
result = re.sub(r'\d+', 'NUM', text)
print(result) # 输出:Hello NUM World NUM
# 分割字符串
text = "a,b;c d\te"
result = re.split(r'[,; \t]', text)
print(result) # 输出:['a', 'b', 'c', 'd', 'e']
3. 高级技巧
(1)分组与捕获
使用括号可以对正则进行分组,捕获的内容可以单独提取:
text = "2026-03-24"
pattern = r'(\d{4})-(\d{2})-(\d{2})'
match = re.match(pattern, text)
print(match.group(0)) # 完整匹配:2026-03-24
print(match.group(1)) # 第1组:2026
print(match.group(2)) # 第2组:03
print(match.group(3)) # 第3组:24
print(match.groups()) # 所有组:('2026', '03', '24')
# 命名分组
pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
match = re.match(pattern, text)
print(match.group('year')) # 输出:2026
(2)贪婪与非贪婪匹配
默认情况下*,+,{n,m}都是贪婪匹配,会尽可能匹配最长的字符串;在后面加?就变成非贪婪匹配,尽可能匹配最短的字符串:
text = "<div>hello</div><div>world</div>"
# 贪婪匹配
pattern_greedy = r'<div>.*</div>'
print(re.search(pattern_greedy, text).group()) # 输出:<div>hello</div><div>world</div>
# 非贪婪匹配
pattern_non_greedy = r'<div>.*?</div>'
print(re.search(pattern_non_greedy, text).group()) # 输出:<div>hello</div>
(3)标志位使用
re模块提供了多个标志位来修改匹配行为:
– re.IGNORECASE / re.I:忽略大小写
– re.MULTILINE / re.M:多行模式,^和$匹配每行的开头和结尾
– re.DOTALL / re.S:让.可以匹配换行符
– re.VERBOSE / re.X:允许正则表达式中添加注释和空白,提高可读性
# 忽略大小写匹配
print(re.search(r'hello', 'Hello World', re.I).group()) # 输出:Hello
# 多行模式
text = "line1\nline2\nline3"
print(re.findall(r'^line\d', text, re.M)) # 输出:['line1', 'line2', 'line3']
# 允许.匹配换行符
text = "hello\nworld"
print(re.search(r'hello.*world', text, re.S).group()) # 匹配成功
# verbose模式写复杂正则
pattern = re.compile(r'''
^ # 开头
1[3-9] # 前两位
\d{9} # 后面9位数字
$ # 结尾
''', re.X)
print(pattern.match('13812345678')) # 匹配成功
三、常见坑点与避免方法
- 元字符转义问题:如果要匹配元字符本身(比如. * + ?等),需要用反斜杠转义,或者使用原始字符串r”避免Python字符串转义和正则转义的冲突。比如匹配点号要用
\.或者r’\.’。 - 默认不匹配换行符:默认情况下.不会匹配换行符,如果需要跨行匹配,记得加re.S标志位。
- 贪婪匹配陷阱:默认的贪婪匹配很容易匹配到多余的内容,在需要匹配最短内容的时候一定要加?改成非贪婪模式。
- 字符集的^含义不同:^在字符集[]里面表示“非”,在外面表示字符串开头,比如
[^abc]表示匹配不是a、b、c的任意字符。 - 零宽断言的使用限制:Python的re模块只支持固定长度的后视断言(lookbehind),不支持可变长度的,比如
(?<=a+)会报错,因为a+长度不固定。 - 正则效率问题:复杂的正则表达式尤其是含有多个嵌套量词的正则,很容易出现回溯性能问题,处理大文本的时候会非常慢,甚至卡住。建议:
- 避免使用过于复杂的正则,复杂逻辑可以拆分成多个简单正则处理
- 尽量精确匹配,减少.*这样的模糊匹配
- 对于多次使用的正则,提前用re.compile编译,提高匹配速度
- 中文匹配问题:在Python3中,\w可以匹配中文,如果要只匹配英文字母,需要用[a-zA-Z];如果要匹配指定范围的中文,可以用
[\u4e00-\u9fa5]。
四、总结
正则表达式是非常强大的文本处理工具,但也很容易写出有bug的正则。掌握常用技巧、避开常见坑点,就能写出高效、正确的正则表达式。对于非常复杂的文本处理场景,也可以考虑用专门的解析库来处理,避免过度依赖正则表达式,降低代码维护成本。
本文为原创技术总结,发布于 2026-03-24,如需转载请注明出处:https://qzdd.net