37·模块八:装饰器与高级主题进阶

正则表达式基础与 re 模块

regexrepattern-matching

第37课 - 正则表达式基础与 re 模块

学习目标

完成本课学习后,你将能够:

  1. 理解正则表达式的基本概念和核心元字符
  2. 掌握 Python re 模块的核心函数:search()match()findall()sub()
  3. 编写简单的正则表达式模式来匹配和提取文本
  4. 区分贪婪匹配和非贪婪匹配的行为差异
  5. 应用正则表达式解决实际的文本处理问题

核心概念

什么是正则表达式?

正则表达式(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

小结

关键要点回顾:

  1. 正则表达式是一种强大的文本模式匹配工具,使用特殊符号描述匹配规则
  2. re 模块提供核心函数:search()(任意位置)、match()(开头)、findall()(全部)、sub()(替换)
  3. 重要元字符\d(数字)、\w(单词字符)、.(任意字符)、*+?(量词)
  4. 贪婪与非贪婪:默认贪婪匹配(尽可能多),使用 *?+? 进行非贪婪匹配
  5. 实际应用:数据提取、格式验证、文本清洗、日志分析等

最佳实践:

  • 使用原始字符串 r"..." 编写正则表达式
  • 复杂模式分步骤构建和测试
  • 注意性能:避免过度复杂的正则表达式
  • 使用 re.VERBOSE 标志编写可读性更好的正则表达式

下一课预告:

下一课我们将深入学习正则表达式的高级用法,包括分组捕获、反向引用、前瞻断言等强大功能,解决更复杂的文本处理问题!

# 小测验:这个正则表达式匹配什么?
pattern = r"^[\w.-]+@[\w.-]+\.\w{2,3}$"
# 答案:匹配标准的邮箱地址格式!

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「正则表达式高级用法与分组」 以巩固所学知识。