正则表达式 (Regular Expression)
正则表达式是一种强大的模式匹配工具,用于在文本中查找、替换、验证特定格式的数据。Python的 re 模块提供了完整的正则表达式支持。
基本概念
┌─────────────────────────────────────────────────────────────────┐
│ 正则表达式速查表 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【字符类】 │
│ . 任意字符(换行符除外) │
│ \d 数字 [0-9] │
│ \D 非数字 [^0-9] │
│ \w 字母数字下划线 [a-zA-Z0-9_] │
│ \W 非字母数字下划线 │
│ \s 空白字符(空格、制表符、换行) │
│ \S 非空白字符 │
│ [abc] 字符集合:a、b 或 c │
│ [^abc] 取反:除 a、b、c 外的字符 │
│ [a-z] 范围:a 到 z │
│ │
│ 【数量词】 │
│ * 0 次或多次 │
│ + 1 次或多次 │
│ ? 0 次或 1 次 │
│ {n} 恰好 n 次 │
│ {n,} 至少 n 次 │
│ {n,m} n 到 m 次 │
│ │
│ 【锚点】 │
│ ^ 字符串开头 │
│ $ 字符串结尾 │
│ \b 单词边界 │
│ \B 非单词边界 │
│ │
└─────────────────────────────────────────────────────────────────┘
re 模块基本函数
1. match - 从开头匹配
match() 从字符串的开头匹配,成功返回匹配对象,失败返回 None。
Python
import re
# match 从字符串开头匹配
result = re.match(r"\d+", "123abc")
if result:
print(result.group()) # "123"
# 匹配失败
result = re.match(r"\d+", "abc123")
print(result) # None
# 常用场景:验证手机号格式
phone = "13812345678"
if re.match(r"1[3-9]\d{9}", phone):
print("有效手机号")
2. search - 搜索匹配
search() 在字符串中任意位置搜索第一个匹配。
Python
import re
# search 搜索第一个匹配
result = re.search(r"\d+", "abc123def456")
if result:
print(result.group()) # "123"
# 查找邮箱
text = "我的邮箱是 test@example.com,请联系我"
email = re.search(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
if email:
print(email.group()) # test@example.com
3. findall - 查找所有匹配
findall() 返回字符串中所有匹配的列表。
Python
import re
# findall 返回所有匹配的列表
numbers = re.findall(r"\d+", "abc123def456ghi789")
print(numbers) # ['123', '456', '789']
# 找出所有单词
words = re.findall(r"\w+", "Hello, World! Python 3.9")
print(words) # ['Hello', 'World', 'Python', '3', '9']
# 找出所有邮箱
text = "联系人: a@test.com, b@test.com"
emails = re.findall(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
print(emails) # ['a@test.com', 'b@test.com']
4. finditer - 迭代器版本
Python
import re
# finditer 返回迭代器,适合大数据量
text = "abc123def456ghi789"
for match in re.finditer(r"\d+", text):
print(f"匹配: {match.group()}, 位置: {match.span()}")
5. split - 分割
Python
import re
# 使用正则分割
parts = re.split(r"\s+", "hello world python")
print(parts) # ['hello', 'world', 'python']
# 按数字分割
parts = re.split(r"\d+", "a1b2c3")
print(parts) # ['a', 'b', 'c']
# 限制分割次数
parts = re.split(r",", "a,b,c,d", maxsplit=2)
print(parts) # ['a', 'b', 'c,d']
6. sub - 替换
Python
import re
# 基本替换
text = "电话: 010-12345678"
result = re.sub(r"\d{3}-\d{8}", "XXX-XXXXXXXX", text)
print(result) # 电话: XXX-XXXXXXXX
# 使用函数进行替换
def mask_phone(match):
"将电话号码中间四位隐藏"
phone = match.group()
return phone[:3] + "****" + phone[-4:]
text = "张三:13812345678, 李四:13998765432"
result = re.sub(r"1[3-9]\d{9}", mask_phone, text)
print(result) # 张三:138****5678, 李四:139****5432
# 替换次数限制
text = "a1b2c3"
result = re.sub(r"\d", "#", text, count=2)
print(result) # a#b#c3
compile 编译正则
对于重复使用的正则表达式,先编译可以提高效率。
Python
import re
# 编译正则表达式
phone_pattern = re.compile(r"1[3-9]\d{9}")
email_pattern = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
# 使用编译后的模式
text = "电话: 13812345678, 邮箱: test@example.com"
phone = phone_pattern.search(text)
email = email_pattern.search(text)
print(phone.group()) # 13812345678
print(email.group()) # test@example.com
# 编译后的模式可以多次使用
phones = phone_pattern.findall("13812345678, 13912345678, 13712345678")
print(phones) # ['13812345678', '13912345678', '13712345678']
分组 (Groups)
使用圆括号 () 创建分组,可以提取特定的子模式。
┌─────────────────────────────────────────────────────────────────┐
│ 分组示意图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 正则: (\d{4})-(\d{2})-(\d{2}) │
│ 文本: 2026-01-15 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 完整匹配: 2026-01-15 → group(0) 或 group() │ │
│ │ 第1组: 2026 → group(1) │ │
│ │ 第2组: 01 → group(2) │ │
│ │ 第3组: 15 → group(3) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 命名的分组: (?Ppattern) │
│ (?P\d{4})-(?P\d{2})-(?P\d{2}) │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
import re
# 基本分组
date = "2026-01-15"
match = re.match(r"(\d{4})-(\d{2})-(\d{2})", date)
print(match.group()) # 2026-01-15 (完整匹配)
print(match.group(1)) # 2026 (第一个分组)
print(match.group(2)) # 01 (第二个分组)
print(match.group(3)) # 15 (第三个分组)
# 具名分组
match = re.match(r"(?P\d{4})-(?P\d{2})-(?P\d{2})" , date)
print(match.group("year")) # 2026
print(match.group("month")) # 01
print(match.group("day")) # 15
# 提取所有分组
print(match.groups()) # ('2026', '01', '15')
# 常见应用:提取URL组成部分
url = "https://www.example.com:8080/path/to/page"
pattern = re.compile(r"(?Phttps?)://(?P[^:/]+):?(?P\d+)?(?P/.*)?" )
m = pattern.match(url)
if m:
print(f"协议: {m.group('scheme')}")
print(f"域名: {m.group('domain')}")
print(f"端口: {m.group('port')}")
print(f"路径: {m.group('path')}")
贪婪 vs 非贪婪
┌─────────────────────────────────────────────────────────────────┐
│ 贪婪 vs 非贪婪模式 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 贪婪模式: 尽可能多地匹配 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 文本: <div>hello</div> │ │
│ │ 模式: <div>.*</div> │ │
│ │ 结果: hello (整个字符串!) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 非贪婪模式: 尽可能少地匹配 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 文本: <div>hello</div> │ │
│ │ 模式: <div>.*?</div> │ │
│ │ 结果: hello │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 数量词后加 ? 变成非贪婪: │
│ *? +? ?? {n,}? │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
import re
# 贪婪匹配
text = "<div>hello</div><div>world</div>"
result = re.findall(r"<div>.*</div>", text)
print(f"贪婪: {result}")
# ['<div>hello</div><div>world</div>'] (匹配整个字符串)
# 非贪婪匹配
result = re.findall(r"<div>.*?</div>", text)
print(f"非贪婪: {result}")
# ['<div>hello</div>', '<div>world</div>']
# 其他非贪婪示例
text = "aaaa"
print(re.findall(r"a+", text)) # ['aaaa'] (贪婪)
print(re.findall(r"a+?", text)) # ['a', 'a', 'a', 'a'] (非贪婪)
知识测验
问题1:re.match() 和 re.search() 的主要区别是?
问题2:如何匹配一个或多个数字但尽可能少(非贪婪)?
课后练习
练习1:验证身份证号
编写正则表达式验证身份证号:
- 15位数字(老版)或18位数字(新版)
- 18位身份证最后一位可能是 X
- 验证出生日期是否合理
练习2:提取日志信息
从日志文本中提取信息:
- 日志格式:[2026-01-15 10:30:45] ERROR: 连接失败 (192.168.1.100)
- 提取日期、时间、级别、消息、IP地址
- 使用具名分组
练习3:文本清洗
实现文本清洗功能:
- 去除多余空格
- 将多个标点符号替换为单个
- 统一日期格式(如 2026/01/15 → 2026-01-15)
- 脱敏手机号(13812345678 → 138****5678)
🐍 Python 在线代码编辑器
📝 运行结果