0 Comments

一、概述

正则表达式(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')) # 匹配成功

三、常见坑点与避免方法

  1. 元字符转义问题:如果要匹配元字符本身(比如. * + ?等),需要用反斜杠转义,或者使用原始字符串r”避免Python字符串转义和正则转义的冲突。比如匹配点号要用\.或者r’\.’。
  2. 默认不匹配换行符:默认情况下.不会匹配换行符,如果需要跨行匹配,记得加re.S标志位。
  3. 贪婪匹配陷阱:默认的贪婪匹配很容易匹配到多余的内容,在需要匹配最短内容的时候一定要加?改成非贪婪模式。
  4. 字符集的^含义不同:^在字符集[]里面表示“非”,在外面表示字符串开头,比如[^abc]表示匹配不是a、b、c的任意字符。
  5. 零宽断言的使用限制:Python的re模块只支持固定长度的后视断言(lookbehind),不支持可变长度的,比如(?<=a+)会报错,因为a+长度不固定。
  6. 正则效率问题:复杂的正则表达式尤其是含有多个嵌套量词的正则,很容易出现回溯性能问题,处理大文本的时候会非常慢,甚至卡住。建议:
    • 避免使用过于复杂的正则,复杂逻辑可以拆分成多个简单正则处理
    • 尽量精确匹配,减少.*这样的模糊匹配
    • 对于多次使用的正则,提前用re.compile编译,提高匹配速度
  7. 中文匹配问题:在Python3中,\w可以匹配中文,如果要只匹配英文字母,需要用[a-zA-Z];如果要匹配指定范围的中文,可以用[\u4e00-\u9fa5]

四、总结

正则表达式是非常强大的文本处理工具,但也很容易写出有bug的正则。掌握常用技巧、避开常见坑点,就能写出高效、正确的正则表达式。对于非常复杂的文本处理场景,也可以考虑用专门的解析库来处理,避免过度依赖正则表达式,降低代码维护成本。

本文为原创技术总结,发布于 2026-03-24,如需转载请注明出处:https://qzdd.net