Python 正则表达式:re 模块与匹配模式
正则表达式是处理文本的利器。无论你需要验证用户输入、提取网页数据,还是在日志中查找特定信息,正则表达式都能帮你用简洁的代码完成复杂任务。本文将系统讲解 Python 中 re 模块的使用方法,以及各种匹配模式的配置技巧。
一、为什么需要正则表达式
假设你有一串文本 "用户张三的电话是13800138000,邮箱是zhangsan@example.com"。如果你想提取其中的手机号和邮箱地址,传统的字符串处理方式需要多次调用 find()、split() 等方法,代码冗长且易出错。
而使用正则表达式,你只需编写一条模式规则:
import re
text = "用户张三的电话是13800138000,邮箱是zhangsan@example.com"
# 一行代码提取手机号
phone = re.search(r'\d{11}', text)
print(phone.group()) # 输出:13800138000
# 一行代码提取邮箱
email = re.search(r'\w+@\w+\.\w+', text)
print(email.group()) # 输出:zhangsan@example.com
这就是正则表达式的威力——用精炼的语法描述文本模式,实现高效的匹配与提取。
二、re 模块的核心函数
Python 的 re 模块提供了多个函数来处理正则表达式。理解这些函数的适用场景,是灵活运用正则的第一步。
2.1 搜索与匹配的区别
re.search() 会在整个字符串中搜索第一个匹配项,re.match() 只在字符串开头进行匹配。
import re
text = "Python编程指南"
# search 在整个字符串中查找
result = re.search(r'编程', text)
print(result.group()) # 输出:编程
# match 只在开头查找
result = re.match(r'Python', text)
print(result.group()) # 输出:Python
result = re.match(r'编程', text)
print(result) # 输出:None,因为"编程"不在开头
2.2 提取所有匹配项
当需要提取字符串中所有符合条件的内容时,使用 re.findall()。
import re
text = "苹果3个,香蕉2个,橘子5个,葡萄8个"
# 提取所有数字
numbers = re.findall(r'\d+', text)
print(numbers) # 输出:['3', '2', '5', '8']
如果匹配结果包含多个分组,findall() 会以元组形式返回所有分组。
text = "2024-01-15 和 2025-02-20"
# 分组提取年、月、日
dates = re.findall(r'(\d{4})-(\d{2})-(\d{2})', text)
print(dates) # 输出:[('2024', '01', '15'), ('2025', '02', '20')]
2.3 替换与分割
re.sub() 用于替换匹配的文本,功能类似于字符串的 replace() 方法,但支持更复杂的匹配规则。
import re
text = "原价100元,现在只要99元!"
# 将所有数字替换为[数字]
result = re.sub(r'\d+', r'[\g<0>]', text)
print(result) # 输出:原价[100]元,现在只要[99]元!
re.split() 支持按正则模式进行分割,比字符串的 split() 更灵活。
text = "苹果,香蕉;橘子|葡萄"
# 按逗号、分号或竖线分割
result = re.split(r'[;,|]', text)
print(result) # 输出:['苹果', '香蕉', '橘子', '葡萄']
三、匹配模式详解
re 模块的函数接受一个 flags 参数,用于控制匹配的行为方式。合理使用匹配模式可以让代码更简洁、更强大。
3.1 常用匹配模式一览
| 模式 | 常量 | 说明 |
|---|---|---|
| 不区分大小写 | re.IGNORECASE 或 re.I |
A 和 a 被视为相同 |
| 多行模式 | re.MULTILINE 或 re.M |
^ 和 $` 匹配每行开头和结尾 |
| 点号匹配所有 | `re.DOTALL` 或 `re.S` | `.` 可以匹配换行符 `\n` |
| 详细模式 | `re.VERBOSE` | 允许在正则中添加注释和空格 |
| Unicode 匹配 | `re.UNICODE` 或 `re.U` | 按 Unicode 规则匹配(Python 3 默认) |
### 3.2 不区分大小写模式
当你需要匹配英文文本,但不确定大小写时,使用 `re.IGNORECASE`。
```python
import re
text = "Python PYTHON python"
# 不区分大小写匹配
result = re.findall(r'python', text, re.IGNORECASE)
print(result) # 输出:['Python', 'PYTHON', 'python']
```
### 3.3 多行模式
默认情况下,`^` 只匹配整个字符串的开头,`$ 只匹配整个字符串的结尾。开启多行模式后,它们会匹配每一行的开头和结尾。 |
import re
text = """第一行内容
第二行内容
第三行内容"""
# 开启多行模式,^ 匹配每行开头
result = re.findall(r'^第二行', text, re.MULTILINE)
print(result) # 输出:['第二行']
# 不开启多行模式,^ 只匹配整个字符串开头
result = re.findall(r'^第二行', text)
print(result) # 输出:[]
3.4 点号匹配所有
点号 . 默认不匹配换行符。开启 re.DOTALL 后,点号可以匹配包括换行符在内的任意字符。
import re
text = """第一行
第二行"""
# 默认情况下,. 不匹配换行符
result = re.findall(r'第一行.第二行', text)
print(result) # 输出:[]
# 开启 DOTALL 模式
result = re.findall(r'第一行.第二行', text, re.DOTALL)
print(result) # 输出:['第一行\n第二行']
3.5 详细模式
当正则表达式变得复杂时,使用 re.VERBOSE 可以在模式中添加注释和空格,提高可读性。
import re
text = "联系方式:13800138000,邮箱:test@example.com"
# 未使用详细模式
pattern1 = r'联系方式:(\d{11}),邮箱:(\w+@\w+\.\w+)'
# 使用详细模式,便于阅读和维护
pattern2 = r"""
联系方式: # 匹配固定前缀
(\d{11}) # 匹配11位手机号
,邮箱: # 匹配分隔符
(\w+@\w+\.\w+) # 匹配邮箱格式
"""
result = re.search(pattern2, text, re.VERBOSE)
print(result.group(1)) # 输出:13800138000
print(result.group(2)) # 输出:test@example.com
四、编译正则表达式
如果同一个正则表达式需要多次使用,建议先使用 re.compile() 编译它,这样可以提升执行效率。
import re
# 编译一个匹配手机号的正则
phone_pattern = re.compile(r'\d{11}')
text1 = "张三的手机是13800138000"
text2 = "李四的手机是13900139000"
# 直接调用编译后的对象方法
result1 = phone_pattern.search(text1)
print(result1.group()) # 输出:13800138000
result2 = phone_pattern.search(text2)
print(result2.group()) # 输出:13900139000
编译时同样可以指定匹配模式:
# 编译一个不区分大小写的单词匹配模式
word_pattern = re.compile(r'python', re.IGNORECASE)
五、常用正则语法速查
掌握正则的核心语法,才能写出准确的匹配模式。
5.1 字符类
import re
# [abc] 匹配任意一个字符
re.findall(r'[aeiou]', "hello") # 输出:['e', 'o']
# [a-z] 匹配任意小写字母
re.findall(r'[a-z]', "Hello") # 输出:['e', 'llo']
# [^abc] 匹配除 a、b、c 之外的任意字符
re.findall(r'[^aeiou]', "hello") # 输出:['h', 'l', 'l']
5.2 预定义字符
| 符号 | 含义 |
|---|---|
\d |
任意数字,相当于 [0-9] |
\D |
任意非数字,相当于 [^0-9] |
\w |
任意字母、数字或下划线 |
\W |
任意非字母、数字、下划线 |
\s |
任意空白字符(空格、制表符、换行符) |
\S |
任意非空白字符 |
. |
任意字符(除换行符) |
5.3 量词
| 符号 | 含义 |
|---|---|
* |
零次或多次 |
+ |
一次或多次 |
? |
零次或一次 |
{n} |
恰好 n 次 |
{n,} |
至少 n 次 |
{n,m} |
至少 n 次,至多 m 次 |
import re
text = "color colour colooour"
# 匹配 "color" 或 "colour"(u 可有可无)
re.findall(r'colou?r', text) # 输出:['color', 'colour']
# 匹配至少 2 个 o
re.findall(r'co{2,}r', text) # 输出:['colooour']
5.4 锚点与边界
import re
text = "apple Apple APPLE"
# ^ 匹配字符串开头
re.findall(r'^apple', text) # 输出:['apple']
# $ 匹配字符串结尾
re.findall(r'APPLE$', text) # 输出:['APPLE']
# \b 匹配单词边界
re.findall(r'\bapple\b', text) # 输出:['apple']
5.5 分组与捕获
使用圆括号 () 创建分组,既可以限定量词的作用范围,也可以捕获匹配的子内容。
import re
text = "2024-05-15"
# 分组提取年月日
pattern = r'(\d{4})-(\d{2})-(\d{2})'
match = re.search(pattern, text)
print(match.group(0)) # 完整匹配:2024-05-15
print(match.group(1)) # 第一个分组:2024
print(match.group(2)) # 第二个分组:05
print(match.group(3)) # 第三个分组:15
# 使用分组名称
pattern_named = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
match = re.search(pattern_named, text)
print(match.group('year')) # 输出:2024
print(match.group('month')) # 输出:05
六、综合示例
以下示例演示如何组合使用 re 模块的各种功能,解决实际文本处理问题。
import re
# 示例文本
log_text = """
[2024-05-15 10:30:45] INFO: 用户登录成功,用户ID: user_123
[2024-05-15 10:31:02] ERROR: 数据库连接失败,错误码: DB_CONN_001
[2024-05-15 10:32:18] WARNING: 磁盘空间不足,使用率: 85%
[2024-05-15 10:33:00] INFO: 用户user_456完成交易
"""
# 1. 提取所有日志级别
levels = re.findall(r']\] (\w+):', log_text)
print("日志级别:", levels) # 输出:['INFO', 'ERROR', 'WARNING', 'INFO']
# 2. 提取所有用户ID
users = re.findall(r'用户[ID]?:?\s*(\w+)', log_text)
print("用户ID:", users) # 输出:['user_123', 'user_456']
# 3. 提取时间和用户(分组使用)
pattern = r'\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*?用户ID: (\w+)'
matches = re.findall(pattern, log_text)
print("时间和用户:", matches)
# 输出:[('2024-05-15 10:30:45', 'user_123')]
# 4. 替换所有用户ID为 [用户已隐藏]
hidden = re.sub(r'user_\d+', '[用户已隐藏]', log_text)
print(hidden)
# 5. 验证输入是否为有效邮箱
def is_valid_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
print(is_valid_email("test@example.com")) # 输出:True
print(is_valid_email("invalid-email")) # 输出:False
七、常见问题与注意事项
转义字符的处理
正则表达式中的特殊字符(如 .、*、? 等)需要转义。使用原始字符串 r'' 前缀可以避免双重转义的困扰。
import re
# 匹配点号,需要转义
text = "版本号:1.0.2"
result = re.search(r'1\.0\.2', text)
print(result.group()) # 输出:1.0.2
贪婪与非贪婪匹配
默认情况下,量词采用贪婪模式,会匹配尽可能多的字符。在量词后加上 ? 可以切换为非贪婪模式,匹配尽可能少的字符。
import re
html = "<div>内容一</div><div>内容二</div>"
# 贪婪模式:匹配到最后一个 </div>
match1 = re.search(r'<div>.*</div>', html)
print(match1.group()) # 输出:<div>内容一</div><div>内容二</div>
# 非贪婪模式:匹配到第一个 </div>
match2 = re.search(r'<div>.*?</div>', html)
print(match2.group()) # 输出:<div>内容一</div>
性能优化建议
对于需要多次使用的正则表达式,务必先编译。如果匹配规则固定不变,编译一次可以避免每次匹配时的重复解析开销。

暂无评论,快来抢沙发吧!