Python正则表达式实用技巧:re模块元字符、分组与性能优化
正则表达式(Regular Expression)是编程中处理文本模式的利器,在Python中通过内置的re模块实现。无论是数据清洗、日志解析还是表单验证,掌握正则都能大幅提升效率。本文将系统梳理Python正则表达式的核心用法,从基础元字符到高级预查与命名分组,并给出性能优化建议和典型应用场景的代码示例。import re
基础元字符与匹配
元字符是正则表达式的基石。常用元字符包括:
. 匹配任意单个字符(除换行符)
^ 匹配字符串开头
$ 匹配字符串结尾
* 匹配前一个字符0次或多次
+ 匹配前一个字符1次或多次
? 匹配前一个字符0次或1次
{n} 匹配恰好n次
{n,} 匹配至少n次
{n,m} 匹配n到m次
[] 匹配括号内任意字符
| 表示或
示例:匹配a与b中间任意字符的模式
pattern = r"a.b"
text = "aab abb acb"
matches = re.findall(pattern, text)
print(matches)# 输出: ['aab', 'abb', 'acb']
字符类
预定义字符集能快速匹配数字、字母、空白等:
\d 数字
\D 非数字
\w 字母数字下划线
\W 非字母数字下划线
\s 空白字符(空格、制表符、换行)
\S 非空白字符
样例:匹配美国社会安全号码格式
pattern = r"\d{3}-\d{2}-\d{4}"
text = "My SSN is 123-45-6789."
match = re.search(pattern, text)
if match:
print("Found SSN:", match.group())# 输出Found SSN: 123-45-6789
分组与捕获
圆括号()用于分组并捕获匹配内容,通过group(n)获取子组。
pattern = r"(\d{3})-(\d{2})-(\d{4})"
text = "My SSN is 123-45-6789."
match = re.search(pattern, text)
if match:
print("Full match:", match.group(0))# 123-45-6789
print("Group 1:", match.group(1)) # 123
print("Group 2:", match.group(2)) # 45
print("Group 3:", match.group(3)) # 6789
非捕获分组(?:...)用于分组但不捕获,减少内存占用。
pattern = r"(?:\d{3})-(\d{2})-(\d{4})"
match = re.search(pattern, text)
print(match.group(0))# 123-45-6789
print(match.group(1))# 45 (原第二组)
print(match.group(2))# 6789
贪婪与非贪婪匹配
默认量词是贪婪的,尽量多匹配;在量词后加?变为非贪婪。
pattern_greedy = r"<.*>"
pattern_non_greedy = r"<.*?>"
text = "<html><head><title>Page Title</title></head></html>"
match_greedy = re.search(pattern_greedy, text)
match_non_greedy = re.search(pattern_non_greedy, text)
print("Greedy match:", match_greedy.group())
# 输出整个字符串
print("Non-greedy match:", match_non_greedy.group())
# 输出第一个标签<html>
查找与替换
re.sub()用于批量替换匹配文本。
pattern = r"\d+"
text = "There are 3 apples and 5 oranges."
result = re.sub(pattern, "X", text)
print(result)# There are X apples and X oranges.
编译正则表达式
对于多次使用的正则,提前编译成Pattern对象可提升性能。
pattern = re.compile(r"\d{3}-\d{2}-\d{4}")
match = pattern.search(text)
多行匹配与忽略大小写
使用re.MULTILINE让^和$匹配每行开头结尾。
pattern = r"^\d+"
text = "1 apple\n2 oranges\n3 bananas"
matches = re.findall(pattern, text, re.MULTILINE)
print(matches)# ['1', '2', '3']
re.IGNORECASE使匹配忽略大小写。
re.search(r"apple", "Apple", re.IGNORECASE).group()# 'Apple'
高级技巧:预查与命名分组
正向预查(?=...)匹配后面紧跟指定模式的内容,但不消耗该模式。
pattern = r"\d+(?= dollars)"
text = "I have 100 dollars."
print(re.search(pattern, text).group())# 100
负向预查(?!...)匹配后而不紧跟指定模式的内容。
pattern = r"\d+(?! dollars)"
text = "I have 100 euros."
print(re.search(pattern, text).group())# 100
命名分组(?P<name>...)让分组有可读名称,便于维护。
pattern = r"(?P<area>\d{3})-(?P<group>\d{2})-(?P<serial>\d{4})"
match = re.search(pattern, "SSN 123-45-6789")
print(match.group("area")) # 123
print(match.group("group"))# 45
print(match.group("serial")) # 6789
调试与性能优化
调试时加re.DEBUG标志查看解析树。
re.compile(r"\d{3}-\d{2}-\d{4}", re.DEBUG)
优化建议:
- 优先使用非贪婪匹配,避免回溯爆炸
- 避免嵌套量词
- 多次使用同一正则时预编译
- 对于大型文本,用re.finditer()替代re.findall()以节省内存
pattern = re.compile(r"\d{3}-\d{2}-\d{4}")
for match in pattern.finditer(text):
print("Found:", match.group())
常见应用场景代码
验证电子邮件地址:
pattern = r"^+@+\.+$"
email = "example@example.com"
if re.match(pattern, email):
print("Valid")
提取URL:
pattern = r"https?://(?:www\.)?\S+"
text = "Visit https://example.com for more info."
print(re.findall(pattern, text))# ['https://example.com']
提取HTML标签(含标签名和内容):
pattern = r"<(\w+)[^>]*>(.*?)</\1>"
html = "<h1>Title</h1><p>Paragraph</p>"
print(re.findall(pattern, html))
# [('h1', 'Title'), ('p', 'Paragraph')]
正则表达式是文本处理的瑞士军刀,熟练运用元字符、分组、预查等特性,再配合编译与优化,可以高效解决多数匹配、提取与替换任务。上面的示例覆盖了日常开发中最常用的场景,推荐收藏以备查阅。
Re: Python正则表达式实用技巧:re模块元字符、分组与性能优化
感谢楼主这么系统地整理了Python正则表达式的核心用法,从基础元字符到预查、命名分组,覆盖面很全,还附带了清晰的代码示例,对新手和进阶者都很有帮助。 有个小点想请教:在性能优化部分,您提到编译正则表达式可以提升多次重复匹配时的效率,但在实际项目中,如果需要根据用户输入动态构造模式,直接编译可能不太方便。这种情况有没有推荐的折中方案,比如预编译一些常见模式,或者利用re模块的缓存机制?另外,对于超长文本或复杂回溯的正则,除了非贪婪匹配外,还有没有其他避免灾难性回溯的实用技巧?Re: Python正则表达式实用技巧:re模块元字符、分组与性能优化
感谢楼主的分享!内容非常系统实用,从基础元字符到高级预查都覆盖了,代码示例也很清晰直观。我平时在处理日志解析和爬虫数据提取时也经常用 re 模块。 特别赞同编译正则表达式和用非捕获分组减少内存占用的建议。另外想请教一下,楼主在实际项目中会使用 re.VERBOSE 来写多行、带注释的正则吗?有时候长模式直接写一行的可读性确实不太好。还有就是对于超长文本(比如几 MB 的字符串),除了 re.compile,还有没有其他性能方面的注意事项?期待继续交流!Re: Python正则表达式实用技巧:re模块元字符、分组与性能优化
很详细的教程!预查和命名分组这块平时用得不多,看了例子以后感觉非常实用。另外有个小补充:re.sub里可以通过\1来引用捕获组,比如把日期格式从“2024-01-01”换成“01/01/2024”时特别方便。感谢分享!
页:
[1]