正则表达式(Regular Expression,简称 regex 或 RE)是 Python 中处理文本的强大工具。它使用一系列特殊字符定义搜索模式,用于字符串匹配、查找、替换和分割等操作。在 Python 中,正则表达式通过内置的
re
模块实现。本教程将从基础概念入手,逐步深入到复杂模式提取,提供详细的解释、代码示例和实战场景,帮助你掌握正则表达式的精髓。无论你是初学者还是有经验的开发者,都能从中获益。
1. 正则表达式基础概念
正则表达式本质上是一个字符串,用于描述文本模式。它由普通字符(如字母、数字)和特殊字符(元字符)组成。普通字符直接匹配自身,而元字符具有特殊含义,例如
.
匹配任意字符,
*
匹配零次或多次重复。
为什么使用正则表达式?
高效性
:一次性处理复杂模式,而非编写多行 if-else 代码。
灵活性
:适用于日志解析、数据清洗、表单验证等场景。
跨语言
:Python 的
re
模块与其他语言(如 JavaScript、Perl)的正则语法高度兼容。
Python 中的
re
模块
在使用前,需要导入模块:
import re
核心函数包括:
re.match()
:从字符串开头匹配。
re.search()
:在字符串中搜索第一个匹配。
re.findall()
:返回所有匹配的列表。
re.finditer()
:返回匹配对象的迭代器。
re.sub()
:替换匹配项。
re.split()
:根据模式分割字符串。
基本语法:字面量匹配
最简单的正则表达式是纯文本。例如,匹配 “hello”:
pattern = r”hello” # r 表示原始字符串,避免转义问题
text = “hello world”
match = re.search(pattern, text)
if match:
print(“匹配成功:”, match.group()) # 输出: hello
这里,
r”hello”
是正则模式,
re.search()
在
text
中搜索,如果找到,返回匹配对象。
match.group()
提取匹配的字符串。
2. 元字符和基本匹配规则
元字符是正则表达式的“魔法”。以下是常用元字符及其含义:
元字符
描述
示例
匹配结果
.
匹配除换行符外的任意字符
a.c
“abc”, “a c”
^
匹配字符串开头
^hello
“hello world”(仅开头)
$
匹配字符串结尾
world$
“hello world”(仅结尾)
*
匹配前一个字符 0 次或多次
ab*c
“ac”, “abc”, “abbc”
+
匹配前一个字符 1 次或多次
ab+c
“abc”, “abbc”(不匹配 “ac”)
?
匹配前一个字符 0 次或 1 次
ab?c
“ac”, “abc”
{n}
匹配前一个字符恰好 n 次
a{3}
“aaa”
{n,}
匹配前一个字符至少 n 次
a{2,}
“aa”, “aaa”, “aaaa”
{n,m}
匹配前一个字符 n 到 m 次
a{2,4}
“aa”, “aaa”, “aaaa”
[]
字符集,匹配方括号内任意字符
[aeiou]
任意元音字母
[^]
否定字符集,匹配不在方括号内的字符
[^0-9]
非数字字符
|
或运算符
cat|dog
“cat” 或 “dog”
()
分组,捕获子模式
(ab)+
“ab”, “abab”
示例:基本匹配
假设我们想从文本中提取所有数字:
import re
text = “订单号: 12345, 金额: 678.90″
pattern = r”\d+” # \d 匹配数字,+ 表示至少一个
matches = re.findall(pattern, text)
print(matches) # 输出: [‘12345’, ‘678’, ’90’] # 注意:小数点被忽略,因为 \d 只匹配数字
要匹配小数,使用更精确的模式:
pattern = r”\d+\.\d+” # \. 转义点号
matches = re.findall(pattern, text)
print(matches) # 输出: [‘678.90’]
转义元字符
如果需要匹配元字符本身,使用反斜杠
\
转义。例如,匹配点号
.
:
pattern = r”\.” # 匹配实际的点号
text = “example.com”
match = re.search(pattern, text)
print(match.group() if match else “无匹配”) # 输出: .
3. 字符类和量词
字符类
字符类允许匹配一组特定字符。
[a-z]
:小写字母 a 到 z。
[A-Z]
:大写字母 A 到 Z。
[0-9]
:数字 0 到 9。
[a-zA-Z0-9]
:字母和数字。
预定义字符类:
\d
:数字,等价于
[0-9]
。
\D
:非数字。
\w
:单词字符(字母、数字、下划线),等价于
[a-zA-Z0-9_]
。
\W
:非单词字符。
\s
:空白字符(空格、制表符、换行符)。
\S
:非空白字符。
示例:匹配电子邮件地址的用户名部分(字母数字下划线):
pattern = r”\w+” # 匹配一个或多个单词字符
text = “user_name123@example.com”
match = re.match(pattern, text)
print(match.group() if match else “无匹配”) # 输出: user_name123
量词
量词控制重复次数。
*
:0 次或多次(贪婪模式,尽可能多匹配)。
+
:1 次或多次。
?
:0 次或 1 次。
{n}
:恰好 n 次。
{n,}
:至少 n 次。
{n,m}
:n 到 m 次。
贪婪 vs 非贪婪:默认贪婪,使用
?
变为非贪婪。
text = “
”
pattern_greedy = r”<.*>” # 贪婪:匹配整个字符串
pattern_non_greedy = r”<.*?>” # 非贪婪:只匹配
print(re.findall(pattern_greedy, text)) # [‘
‘]
print(re.findall(pattern_non_greedy, text)) # [‘
‘]
4. 分组、捕获和引用
分组使用
()
捕获子模式,便于提取特定部分或引用。
捕获分组
pattern = r”(\d{4})-(\d{2})-(\d{2})” # 捕获年、月、日
text = “2023-10-15”
match = re.match(pattern, text)
if match:
print(“完整匹配:”, match.group(0)) # 2023-10-15
print(“年:”, match.group(1)) # 2023
print(“月:”, match.group(2)) # 10
print(“日:”, match.group(3)) # 15
print(“所有组:”, match.groups()) # (‘2023′, ’10’, ’15’)
非捕获分组
使用
(?:…)
避免捕获,提高效率:
pattern = r”(?:\d{4})-(\d{2})-(\d{2})” # 只捕获月和日
match = re.match(pattern, text)
print(match.groups()) # (’10’, ’15’)
引用分组
在模式中引用已捕获的组,使用
\1
,
\2
等:
pattern = r”(\w+) \1″ # \1 引用第一个组,匹配重复单词
text = “hello hello world”
match = re.search(pattern, text)
print(match.group() if match else “无匹配”) # hello hello
命名分组(Python 特有)
使用
(?P
命名组:
pattern = r”(?P
match = re.match(pattern, text)
if match:
print(match.groupdict()) # {‘year’: ‘2023’, ‘month’: ’10’, ‘day’: ’15’}
print(match.group(‘year’)) # 2023
5. 锚点和边界
锚点匹配位置而非字符。
^
:字符串开头(多行模式下匹配行首)。
$
:字符串结尾(多行模式下匹配行尾)。
\b
:单词边界(\w 和 \W 之间)。
\B
:非单词边界。
示例:匹配完整单词:
pattern = r”\bworld\b” # 只匹配独立的 “world”
text = “hello world, worldwide”
matches = re.findall(pattern, text)
print(matches) # [‘world’] # 不匹配 “worldwide”
多行模式(
re.MULTILINE
或
re.M
):
text = “line1\nline2\nline3″
pattern = r”^line2$” # 默认不匹配,因为 $ 在末尾
match = re.search(pattern, text, re.M)
print(match.group() if match else “无匹配”) # line2
6. 修饰符(标志)
修饰符改变正则行为:
re.IGNORECASE
或
re.I
:忽略大小写。
re.MULTILINE
或
re.M
:^ 和 $ 匹配每行开头/结尾。
re.DOTALL
或
re.S
:. 匹配包括换行符的所有字符。
re.VERBOSE
或
re.X
:允许添加注释和空格,提高可读性。
示例:忽略大小写:
pattern = r”hello”
text = “HELLO world”
match = re.search(pattern, text, re.I)
print(match.group() if match else “无匹配”) # HELLO
使用
re.VERBOSE
:
pattern = re.compile(r”””
\d{4} # 年
– # 分隔符
\d{2} # 月
– # 分隔符
\d{2} # 日
“””, re.VERBOSE)
match = pattern.match(“2023-10-15”)
print(match.group() if match else “无匹配”) # 2023-10-15
7. 高级功能:前瞻、后顾和断言
这些用于条件匹配,而不消耗字符。
正向前瞻
(?=…)
:匹配后跟模式的部分。
负向前瞻
(?!…)
:匹配后不跟模式的部分。
正向后顾
(?<=...)
:匹配前有模式的部分(Python 3.7+ 支持变长)。
负向后顾
(?
标签的 href 和文本(注意:实际 HTML 解析应用 BeautifulSoup,但正则适合简单场景):
html = ‘Click here Test‘
pattern = r’([^<]*)' # 捕获 href 和文本(忽略嵌套标签)
matches = re.findall(pattern, html)
print(matches) # [('https://example.com', 'Click '), ('http://test.org', 'Test')]
解释:
href="([^"]+)"
捕获双引号内的 URL,
>([^<]*)
捕获 > 后直到 < 前的文本。
场景4:数据清洗(提取和替换)
从文本中提取所有 URL 并替换为 [URL]:
text = "访问 https://www.google.com 或 http://example.org 获取信息"
pattern = r"https?://[^\s]+" # http 或 https 后跟非空白字符
urls = re.findall(pattern, text)
cleaned = re.sub(pattern, "[URL]", text)
print("提取的 URL:", urls)
print("清洗后:", cleaned)
# 输出: 提取的 URL: ['https://www.google.com', 'http://example.org']
# 清洗后: 访问 [URL] 或 [URL] 获取信息
场景5:复杂提取:CSV 样式数据(带引号处理)
提取 CSV 行中的字段,支持引号内逗号:
csv_line = '1,"Smith, John",30,"New York, NY"'
pattern = r'"([^"]*)"|([^,]+)' # 双引号内内容或非逗号序列
matches = re.findall(pattern, csv_line)
fields = [m[0] or m[1] for m in matches] # 合并组
print(fields) # ['1', 'Smith, John', '30', 'New York, NY']
解释:
"([^"]*)"
捕获引号内,
|
或
([^,]+)
捕获普通字段。使用
findall
返回元组列表,需合并。
场景6:使用
re.finditer
处理大文本
对于大文件,避免
findall
的内存开销:
pattern = r"\d{3}-\d{2}-\d{4}" # 社保号模式
text = "SSN: 123-45-6789, Another: 987-65-4321"
for match in re.finditer(pattern, text):
print(f"找到: {match.group()} at position {match.start()}-{match.end()}")
# 输出: 找到: 123-45-6789 at position 5-16
# 找到: 987-65-4321 at position 28-39
9. 常见错误和最佳实践
常见错误
忘记转义
:如
.
匹配任意字符,导致意外匹配。使用
\.
匹配点。
贪婪过度
:如
<.*>
匹配过多。使用非贪婪
<.*?>
。
性能问题
:复杂模式在长文本上慢。使用
re.compile()
预编译。
忽略边界
:如
\b
确保完整单词匹配。
最佳实践
使用原始字符串
:始终用
r”pattern”
避免 Python 转义干扰。
预编译模式
:对于重复使用,
pattern = re.compile(r”…”)
然后
pattern.match(text)
。
测试工具
:使用在线工具如 regex101.com 验证模式。
文档化
:复杂模式添加注释(用
re.VERBOSE
)。
安全
:避免用户输入直接作为模式,防止 ReDoS 攻击(指数级回溯)。
结合其他工具
:正则不适合嵌套结构(如 JSON),用
json
模块代替。
10. 调试和优化正则表达式
调试技巧
逐步构建
:从简单模式开始,逐步添加。
使用
re.debug
(Python 3.11+):显示解析细节。
import re
pattern = r”\d+”
print(re.debug(pattern)) # 输出调试信息
捕获组检查
:用
match.groups()
验证提取。
错误处理
:用 try-except 捕获
re.error
。
try:
re.compile(r”[invalid”)
except re.error as e:
print(“语法错误:”, e)
优化
避免回溯
:如
(a+)+b
在 “aaaa…c” 上慢。用原子组(Python 不支持)或简化。
字符集优先
:用
[a-z]
代替
(a|b|c|…)
。
锚点优化
:用
^
和
$
限制搜索范围。
性能测试
:用
timeit
模块比较模式。
import timeit
def test1():
return re.findall(r”\d+”, “123 456 789” * 100)
print(timeit.timeit(test1, number=1000)) # 测试时间
11. 总结
正则表达式是 Python 文本处理的利器,从简单匹配到复杂提取,都能高效完成。通过本教程,你已学习基础元字符、分组、锚点、修饰符、高级断言,以及多个实战示例。实践是关键:尝试修改代码,解决实际问题,如解析日志或验证输入。记住,正则强大但易出错,多用工具调试。继续探索
re
模块文档,结合项目应用,你将成为正则高手!如果有特定场景疑问,欢迎提供更多细节。