第37课 - 正则表达式基础与 re 模块
学习目标
完成本课学习后,你将能够:
- 理解正则表达式的基本概念和核心元字符
- 掌握 Python
re模块的核心函数:search()、match()、findall()、sub() - 编写简单的正则表达式模式来匹配和提取文本
- 区分贪婪匹配和非贪婪匹配的行为差异
- 应用正则表达式解决实际的文本处理问题
核心概念
什么是正则表达式?
正则表达式(Regular Expression,简称 regex 或 regexp)是一种描述字符串模式的工具。你可以把它想象成一种"通配符升级版"——它不仅能用 * 匹配任意字符,还能定义更复杂的规则。
比如,要验证一个邮箱地址是否合法,你可以用正则表达式:
r"[\w.-]+@[\w.-]+\.\w+"
这行代码的意思是:[\w.-]+ 匹配一个或多个字母/数字/下划线/点/连字符,然后是 @,接着是类似的域名部分,最后是 . 和顶级域名。
re 模块基础
Python 通过 re 模块提供正则表达式支持:
import re
# 简单示例:在字符串中查找数字
text = "我的电话是 138-1234-5678,备用 010-87654321"
pattern = r"\d{3,4}-\d{7,8}" # 匹配电话号码模式
matches = re.findall(pattern, text)
print(matches) # 输出: ['138-1234-5678', '010-87654321']
核心元字符速查表
| 元字符 | 含义 | 示例 | 匹配结果 |
|---|---|---|---|
. | 匹配任意单个字符 | a.c | "abc", "a1c" |
\d | 匹配数字 | \d{3} | "123" |
\w | 匹配字母/数字/下划线 | \w+ | "hello_123" |
\s | 匹配空白字符 | \s+ | " \t\n" |
* | 0次或多次 | ab*c | "ac", "abc" |
+ | 1次或多次 | ab+c | "abc" |
? | 0次或1次 | colou?r | "color", "colour" |
{n} | 恰好n次 | \d{3} | "123" |
[] | 字符集合 | [aeiou] | 任意一个元音字母 |
代码示例
示例1:基础匹配函数对比
import re
text = "Python 是一门非常棒的编程语言,Python 3.10 版本很受欢迎"
# 1. search() - 找到第一个匹配项
search_result = re.search(r"Python \d", text)
if search_result:
print(f"search找到: '{search_result.group()}' 位置: {search_result.start()}")
# 输出: search找到: 'Python 3' 位置: 21
# 2. match() - 只从字符串开头匹配
match_result = re.match(r"Python", text)
if match_result:
print(f"match找到: '{match_result.group()}'")
# 输出: match找到: 'Python'
# 3. findall() - 找到所有匹配项
findall_result = re.findall(r"Python", text)
print(f"findall找到: {findall_result}")
# 输出: findall找到: ['Python', 'Python']
# 4. sub() - 替换匹配内容
sub_result = re.sub(r"Python", "🐍", text)
print(f"替换后: {sub_result}")
# 输出: 替换后: 🐍 是一门非常棒的编程语言,🐍 3.10 版本很受欢迎
示例2:实际应用 - 提取信息
import re
# 场景:从文本中提取邮箱和电话
contact_info = """
联系方式:
- 工作邮箱:[email protected]
- 个人邮箱:[email protected]
- 办公电话:010-12345678
- 手机号码:138-0013-8000
"""
# 提取邮箱
email_pattern = r"[\w.-]+@[\w.-]+\.\w+"
emails = re.findall(email_pattern, contact_info)
print(f"找到邮箱: {emails}")
# 输出: 找到邮箱: ['[email protected]', '[email protected]']
# 提取电话(支持不同分隔符)
phone_pattern = r"(?:\d{3,4}[- ]?)?\d{7,8}|1[3-9]\d{9}|1[3-9]\d[- ]?\d{4}[- ]?\d{4}"
phones = re.findall(phone_pattern, contact_info)
print(f"找到电话: {phones}")
# 输出: 找到电话: ['010-12345678', '138-0013-8000']
# 敏感信息替换
censored = re.sub(r"\b\d{3}[- ]?\d{4}[- ]?\d{4}\b", "***-****-****", contact_info)
print("脱敏后:")
print(censored)
示例3:贪婪 vs 非贪婪匹配
import re
html_content = "<p>这是一个<b>重要的</b>段落</p>"
# 贪婪匹配(默认)- 尽可能多地匹配
greedy_pattern = r"<.*>"
greedy_match = re.search(greedy_pattern, html_content)
print(f"贪婪匹配: {greedy_match.group()}")
# 输出: 贪婪匹配: <p>这是一个<b>重要的</b>段落</p>
# 非贪婪匹配 - 尽可能少地匹配
non_greedy_pattern = r"<.*?>"
non_greedy_match = re.findall(non_greedy_pattern, html_content)
print(f"非贪婪匹配: {non_greedy_match}")
# 输出: 非贪婪匹配: ['<p>', '<b>', '</b>', '</p>']
实践练习
练习1:验证用户名格式(简单)
要求:编写一个正则表达式验证用户名是否符合规则:
- 只能包含字母、数字、下划线
- 长度为4-16个字符
- 必须以字母开头
测试数据:
test_usernames = ["alice", "bob123", "1_charlie", "david_extra_long_username", "eve!"]
预期输出:
alice: 通过
bob123: 通过
1_charlie: 不通过
david_extra_long_username: 不通过
eve!: 不通过
练习2:提取日期信息(中等)
要求:从给定文本中提取所有日期,并统一格式化为 YYYY-MM-DD 格式。
测试文本:
text = """
会议日期:
- 2023年12月25日 圣诞节
- 2024/1/1 新年
- Jan 15, 2024 项目截止
- 2024-02-10 春节
"""
预期输出:
找到的日期:
2023-12-25
2024-01-01
2024-01-15
2024-02-10
练习3:解析日志文件(困难)
要求:解析以下日志格式,提取错误级别、时间戳和消息,然后统计每种错误级别的出现次数。
日志格式示例:
log_data = """
[ERROR] 2024-01-15 10:30:22 数据库连接失败
[WARNING] 2024-01-15 10:31:15 磁盘空间不足
[ERROR] 2024-01-15 10:32:45 无法解析配置文件
[INFO] 2024-01-15 10:33:20 系统启动完成
[ERROR] 2024-01-15 10:34:55 内存溢出
"""
预期输出:
解析结果:
级别: ERROR, 时间: 2024-01-15 10:30:22, 消息: 数据库连接失败
级别: WARNING, 时间: 2024-01-15 10:31:15, 消息: 磁盘空间不足
级别: ERROR, 时间: 2024-01-15 10:32:45, 消息: 无法解析配置文件
级别: INFO, 时间: 2024-01-15 10:33:20, 消息: 系统启动完成
级别: ERROR, 时间: 2024-01-15 10:34:55, 消息: 内存溢出
统计:
ERROR: 3次
WARNING: 1次
INFO: 1次
常见错误
1. 混淆 match() 和 search()
# 错误:用 match 搜索字符串中间的内容
text = "我的Python很厉害"
result = re.match(r"Python", text)
print(result) # None!match只从开头匹配
# 正确:应该用 search
result = re.search(r"Python", text)
print(result.group()) # "Python"
2. 忘记原始字符串前缀 r
# 错误:反斜杠被转义
pattern = "\d+" # 实际上是单个反斜杠 + d
# 正确:使用原始字符串
pattern = r"\d+" # 保持原样
3. 贪婪匹配导致结果不符合预期
html = "<div>内容</div>"
# 错误:贪婪匹配得到整个字符串
result = re.search(r"<.*>", html)
print(result.group()) # "<div>内容</div>"
# 正确:使用非贪婪匹配
result = re.search(r"<.*?>", html)
print(result.group()) # "<div>"
4. 不测试边界情况
# 错误:只测试正常情况
pattern = r"\d{3}-\d{4}" # 匹配电话号码
test = "123-4567"
print(re.match(pattern, test)) # 匹配成功
# 正确:也要测试边界情况
test_edge = "12-4567" # 数字位数不足
print(re.match(pattern, test_edge)) # None
小结
关键要点回顾:
- 正则表达式是一种强大的文本模式匹配工具,使用特殊符号描述匹配规则
- re 模块提供核心函数:
search()(任意位置)、match()(开头)、findall()(全部)、sub()(替换) - 重要元字符:
\d(数字)、\w(单词字符)、.(任意字符)、*+?(量词) - 贪婪与非贪婪:默认贪婪匹配(尽可能多),使用
*?或+?进行非贪婪匹配 - 实际应用:数据提取、格式验证、文本清洗、日志分析等
最佳实践:
- 使用原始字符串
r"..."编写正则表达式 - 复杂模式分步骤构建和测试
- 注意性能:避免过度复杂的正则表达式
- 使用
re.VERBOSE标志编写可读性更好的正则表达式
下一课预告:
下一课我们将深入学习正则表达式的高级用法,包括分组捕获、反向引用、前瞻断言等强大功能,解决更复杂的文本处理问题!
# 小测验:这个正则表达式匹配什么?
pattern = r"^[\w.-]+@[\w.-]+\.\w{2,3}$"
# 答案:匹配标准的邮箱地址格式!
练习编辑器
python
Loading...