【第6章 字符串】Python 字符串之正则表达式完全指南(含丰富示例)
Python 字符串之正则表达式完全指南(含丰富示例·完整版)
正则表达式(Regular Expression)是字符串处理的“瑞士军刀”,广泛应用于数据清洗、文本提取、格式验证等场景。Python 内置 re 模块提供了完整的正则表达式支持,本文将从基础到进阶,系统覆盖正则核心语法、re 模块用法、实战场景,补充花括号量词、反向引用等关键知识点,帮你彻底掌握正则表达式的使用。
一、正则表达式基础(核心元字符与量词)
元字符是正则表达式的“语法符号”,量词用于控制字符重复次数,二者结合构成正则的基础骨架。
1. 普通字符(直接匹配)
- 字母、数字、下划线等普通字符,直接匹配自身。
- 示例:
"abc"匹配字符串中的"abc","123"匹配"123","_"匹配"_"。
2. 核心特殊元字符(必掌握)
| 元字符 | 含义 | 示例 |
|---|---|---|
. |
匹配任意单个字符(除换行符 \n,re.S 模式下可匹配换行) |
"a.b" 匹配 "aab"、"acb"、"a1b"(不匹配 "ab"、"a\nb") |
^ |
匹配字符串开头(re.M 模式下匹配行开头) |
"^abc" 匹配 "abc123"(不匹配 "xabc") |
| ` | 元字符 | 含义 |
| -------- | ------ | ------ |
. |
匹配任意单个字符(除换行符 \n,re.S 模式下可匹配换行) |
"a.b" 匹配 "aab"、"acb"、"a1b"(不匹配 "ab"、"a\nb") |
^ |
匹配字符串开头(re.M 模式下匹配行开头) |
"^abc" 匹配 "abc123"(不匹配 "xabc") |
\| 匹配字符串结尾(`re.M` 模式下匹配行结尾) \| `"abc$"` 匹配 `"123abc"`(不匹配 `"abcx"`) |
| [] | 字符集:匹配括号内任意一个字符(支持范围 [a-z]、排除 [^a-z]) | "[abc]" 匹配 "a"/"b"/"c";"[0-9a-f]" 匹配十六进制数字 |
| [^] | 否定字符集:匹配括号外任意一个字符(^ 必须在括号开头) | "[^abc]" 匹配除 "a"/"b"/"c" 外的任意字符 |
| () | 分组:将括号内内容视为整体,可用于提取子串、反向引用 | "(ab)+" 匹配 "ab"/"abab";"(a)(b)" 可通过 \1/\2 引用 |
| \| | 或:匹配左右任意一个表达式(优先级较低,需配合分组使用) | "a\|b" 匹配 "a"/"b";"(ab)\|(cd)" 匹配 "ab"/"cd" |
| \ | 转义字符:将特殊字符转为普通字符(如 \. 匹配 .) | "\." 匹配 ".";"\*" 匹配 "*";"\\n" 匹配换行符 |
3. 量词(控制重复次数,含花括号完整用法)
量词用于定义前导字符/子表达式的重复次数,支持贪婪与非贪婪模式,是正则的核心特性之一。
| 量词类型 | 语法 | 贪婪/非贪婪 | 含义 | 示例(匹配 a) |
|---|---|---|---|---|
| 零次或多次 | * |
贪婪 | 重复 0 次或多次(优先最多) | a* 匹配 ""/"a"/"aaa" |
| 零次或多次 | *? |
非贪婪 | 重复 0 次或多次(优先最少) | a*? 优先匹配 "" |
| 一次或多次 | + |
贪婪 | 重复 1 次或多次(优先最多) | a+ 匹配 "a"/"aaa" |
| 一次或多次 | +? |
非贪婪 | 重复 1 次或多次(优先最少) | a+? 优先匹配 "a" |
| 零次或一次 | ? |
贪婪 | 重复 0 次或 1 次(优先 1 次) | a? 匹配 ""/"a" |
| 零次或一次 | ?? |
非贪婪 | 重复 0 次或 1 次(优先 0 次) | a?? 优先匹配 "" |
| 精确次数 | {n} |
无(固定) | 必须重复 n 次 | a{3} 匹配 "aaa" |
| 范围次数 | {m,n} |
贪婪 | 重复 m~n 次(优先 n 次) | a{2,4} 匹配 "aa"/"aaa"/"aaaa" |
| 范围次数 | {m,n}? |
非贪婪 | 重复 m~n 次(优先 m 次) | a{2,4}? 优先匹配 "aa" |
| 最少次数 | {m,} |
贪婪 | 至少重复 m 次(无上限,优先最多) | a{2,} 匹配 "aa"/"aaa"... |
| 最多次数 | {,n} |
贪婪 | 最多重复 n 次(0~n 次,优先 n 次) | a{,3} 匹配 ""/"a"/"aaa" |
量词实战示例
import re
text = "aaa aaaaa aa aaaaaaa"
# 1. 精确匹配 3 次 a
print(re.findall(r"a{3}", text)) # 输出:['aaa', 'aaa'](从 'aaaaa' 和 'aaaaaa' 中各匹配前3个a)
# 2. 匹配 2~4 次 a(贪婪:优先取上限4次)
print(re.findall(r"a{2,4}", text)) # 输出:['aaa', 'aaaa', 'aa', 'aaaa']
# 3. 匹配 2~4 次 a(非贪婪:优先取下限2次)
print(re.findall(r"a{2,4}?", text)) # 输出:['aa', 'aa', 'aa', 'aa']
# 4. 至少 3 次 a(无上限)
print(re.findall(r"a{3,}", text)) # 输出:['aaa', 'aaaaa', 'aaaaaa']
# 5. 最多 3 次 a(0~3次)
print(re.findall(r"a{,3}", text)) # 输出:['aaa', 'aaa', 'aa', 'aaa']
4. 预定义字符集(简化写法)
预定义字符集是常用字符集的简写,提高正则可读性,无需手动编写复杂字符集。
| 预定义字符 | 等价于 | 含义 | 示例 |
|---|---|---|---|
\d |
[0-9] |
匹配任意数字 | "\d+" 提取字符串中所有数字 |
\D |
[^0-9] |
匹配非数字 | "\D+" 提取字符串中非数字内容 |
\w |
[a-zA-Z0-9_] |
匹配字母、数字、下划线 | "\w+" 匹配单词(含下划线) |
\W |
[^a-zA-Z0-9_] |
匹配非字母、数字、下划线 | "\W+" 匹配标点、空格等 |
\s |
[ \t\n\r\f] |
匹配空白字符(空格、制表符、换行等) | "\s" 匹配字符串中的空格 |
\S |
[^ \t\n\r\f] |
匹配非空白字符 | "\S+" 匹配非空白内容 |
\b |
- | 匹配单词边界(单词与非单词的分隔) | "\bhello\b" 匹配独立的 "hello" |
\B |
- | 匹配非单词边界 | "\Bhello\B" 匹配单词内部的 "hello" |
预定义字符集实战示例
import re
text = "Python 3.11 是最好的编程语言!Version_3.12"
# 提取所有数字(含浮点数)
print(re.findall(r"\d+\.?\d*", text)) # 输出:['3.11', '3.12']
# 提取所有单词(字母+数字+下划线)
print(re.findall(r"\w+", text)) # 输出:['Python', '3', '11', 'Version_3', '12']
# 提取所有非空白字符组合
print(re.findall(r"\S+", text)) # 输出:['Python', '3.11', '是最好的编程语言!', 'Version_3.12']
# 匹配独立的单词 "Python"(不匹配包含 Python 的长单词)
print(re.findall(r"\bPython\b", text)) # 输出:['Python']
二、Python re 模块核心函数(实战必备)
re 模块提供了 6 个核心函数,覆盖“匹配、查找、替换、分割”等场景,掌握这些函数就能应对绝大多数字符串处理需求。
1. re.match(pattern, string, flags=0)
- 功能:从字符串开头匹配正则表达式,仅返回第一个匹配结果。
- 返回值:匹配成功返回
Match对象,失败返回None。 Match对象常用方法:group():返回匹配的整个字符串。group(n):返回第 n 个分组的内容(n 从 1 开始)。span():返回匹配的起始和结束索引(元组:(start, end))。
示例:验证手机号格式
import re
# 手机号规则:以 1 开头,第二位 3-9,后面跟 9 位数字(共 11 位)
phone_pattern = r"^1[3-9]\d{9}$"
phone_list = ["13812345678", "12345678901", "13998765432", "1881234567"]
for phone in phone_list:
result = re.match(phone_pattern, phone)
if result:
print(f"{phone}:有效(匹配范围:{result.span()})")
else:
print(f"{phone}:无效")
# 输出:
# 13812345678:有效(匹配范围:(0, 11))
# 12345678901:无效
# 13998765432:有效(匹配范围:(0, 11))
# 1881234567:无效
2. re.search(pattern, string, flags=0)
- 功能:在整个字符串中查找第一个匹配项(不限制开头)。
- 区别于
match:match仅匹配开头,search匹配任意位置。
示例:提取字符串中的邮箱
import re
text = "我的邮箱是 abc123@qq.com,备用邮箱是 def456@gmail.com,联系电话 13812345678"
# 邮箱正则:用户名(字母/数字/下划线/点/减号)+ @ + 域名 + 顶级域名
email_pattern = r"[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
# 查找第一个邮箱
result = re.search(email_pattern, text)
if result:
print(f"找到邮箱:{result.group()}(位置:{result.span()})")
# 输出:找到邮箱:abc123@qq.com(位置:(6, 18))
3. re.findall(pattern, string, flags=0)
- 功能:在整个字符串中查找所有匹配项,返回列表(包含所有匹配结果)。
- 场景:批量提取文本中的目标内容(如所有邮箱、所有数字)。
示例:提取所有数字和邮箱
import re
text = "商品A价格:99元,商品B价格:199.9元,联系邮箱:test123@163.com、demo@outlook.com"
# 1. 提取所有整数(用单词边界避免误匹配)
nums = re.findall(r"\b\d+\b", text)
print("所有整数:", nums) # 输出:['99', '199', '123', '163']
# 2. 提取所有浮点数和整数
all_nums = re.findall(r"\d+\.?\d*", text)
print("所有数字(整数+浮点数):", all_nums) # 输出:['99', '199.9', '123', '163']
# 3. 提取所有邮箱
emails = re.findall(r"[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+", text)
print("所有邮箱:", emails) # 输出:['test123@163.com', 'demo@outlook.com']
4. re.sub(pattern, repl, string, count=0, flags=0)
- 功能:替换字符串中匹配的内容。
- 参数:
repl:替换后的字符串(可使用\1/\2引用分组内容)。count:替换次数(默认 0,表示全部替换)。
- 场景:敏感词替换、格式修正。
示例:敏感词替换与日期格式转换
import re
text = "张三是个笨蛋,李四是个傻瓜,2024-10-01 是国庆节,2024-10-02 是假期第二天"
# 1. 敏感词替换(将“笨蛋”“傻瓜”替换为“***”)
sensitive_pattern = r"笨蛋|傻瓜"
new_text = re.sub(sensitive_pattern, "***", text)
print("敏感词替换后:", new_text)
# 2. 日期格式转换(2024-10-01 → 2024年10月01日)
date_pattern = r"(\d{4})-(\d{2})-(\d{2})" # 分组匹配年、月、日
new_date_text = re.sub(date_pattern, r"\1年\2月\3日", text)
print("日期格式转换后:", new_date_text)
# 输出:
# 敏感词替换后:张三是个***,李四是个***,2024-10-01 是国庆节,2024-10-02 是假期第二天
# 日期格式转换后:张三是个笨蛋,李四是个傻瓜,2024年10月01日 是国庆节,2024年10月02日 是假期第二天
5. re.split(pattern, string, maxsplit=0, flags=0)
- 功能:根据正则表达式匹配的内容分割字符串,返回列表。
- 参数:
maxsplit:最大分割次数(默认 0,不限制)。 - 场景:复杂分隔符的字符串分割(如同时用逗号、空格、分号分割)。
示例:多分隔符分割字符串
import re
text = "苹果,香蕉 橙子;葡萄|芒果 西瓜"
# 用逗号、空格、分号、竖线分割
split_pattern = r"[, ;|]"
result = re.split(split_pattern, text)
print("全部分割:", result) # 输出:['苹果', '香蕉', '橙子', '葡萄', '芒果', '西瓜']
# 限制分割次数(只分割前 2 次)
result2 = re.split(split_pattern, text, maxsplit=2)
print("分割前 2 次:", result2) # 输出:['苹果', '香蕉', '橙子;葡萄|芒果 西瓜']
6. re.compile(pattern, flags=0)
- 功能:编译正则表达式模式,生成
Pattern对象,可重复使用。 - 优势:多次使用同一正则表达式时,编译后效率更高(避免重复解析正则)。
示例:批量验证手机号
import re
# 编译正则表达式(只编译一次)
phone_pattern = re.compile(r"^1[3-9]\d{9}$")
# 批量验证手机号
phones = ["13812345678", "12345678901", "13998765432", "1881234567"]
for phone in phones:
if phone_pattern.match(phone): # 直接调用Pattern对象的match方法
print(f"{phone}:有效")
else:
print(f"{phone}:无效")
# 输出:
# 13812345678:有效
# 12345678901:无效
# 13998765432:有效
# 1881234567:无效
三、进阶特性:分组、引用与断言
掌握基础语法后,进阶特性能让正则处理更复杂的场景(如嵌套结构匹配、条件判断)。
1. 分组与捕获(() 的深度用法)
分组不仅能将子表达式视为整体,还能“捕获”匹配的内容,通过 group(n) 提取。
(1)普通捕获分组
import re
text = "2024年10月01日 天气:晴,温度:25℃"
# 分组匹配日期(年、月、日)和温度
pattern = r"(\d{4})年(\d{2})月(\d{2})日.*温度:(\d+)℃"
result = re.search(pattern, text)
if result:
print("完整匹配:", result.group()) # 输出:2024年10月01日 天气:晴,温度:25℃
print("年份:", result.group(1)) # 输出:2024(第1个分组)
print("月份:", result.group(2)) # 输出:10(第2个分组)
print("日期:", result.group(3)) # 输出:01(第3个分组)
print("温度:", result.group(4)) # 输出:25(第4个分组)
(2)命名分组((?P<name>pattern))
给分组命名,通过 group("name") 提取,比索引更易读(Python 特有)。
import re
text = "姓名:张三,年龄:25,身高:175cm"
# 命名分组匹配姓名、年龄、身高
pattern = r"姓名:(?P<name>.*),年龄:(?P<age>\d+),身高:(?P<height>\d+)cm"
result = re.search(pattern, text)
print("姓名:", result.group("name")) # 输出:张三
print("年龄:", result.group("age")) # 输出:25
print("身高:", result.group("height")) # 输出:175
(3)非捕获分组((?:pattern))
仅将子表达式视为整体,不捕获内容(节省内存,无需提取时用)。
import re
text = "apple banana apple orange"
# 非捕获分组:匹配 "apple" 或 "banana",但不捕获分组内容
pattern = r"(?:apple|banana)"
print(re.findall(pattern, text)) # 输出:['apple', 'banana', 'apple'](仅返回匹配结果,无分组信息)
2. 反向引用(\1~\9)
引用前面分组捕获的内容,实现“重复匹配已出现的子串”。
示例1:匹配重复的单词或字符
import re
text = "abab 1212 abcabc hellohello"
# 匹配重复两次的两位字符(如 abab → ab 重复)
pattern = r"(\w\w)\1" # \1 引用第一个分组的两位字符
print(re.findall(pattern, text)) # 输出:['ab', '12', 'he']
# 匹配重复两次的单词(如 hellohello → hello 重复)
pattern2 = r"(\w+)\1"
print(re.findall(pattern2, text)) # 输出:['hello']
示例2:匹配成对的 HTML 标签
import re
text = "<h1>标题</h1><div>内容</div><p>段落</p>"
# 匹配成对标签(如 <h1>...</h1>,确保标签名一致)
pattern = r"<(\w+)>.*?</\1>" # \1 引用标签名(h1/div/p)
print(re.findall(pattern, text)) # 输出:['h1', 'div', 'p']
3. 零宽断言(匹配位置而非字符)
断言用于匹配“某个位置前后是否满足条件”,不消耗字符(零宽),常用于“排除特定前缀/后缀”的场景。
| 断言类型 | 语法 | 含义 | 示例 |
|---|---|---|---|
| 正向先行断言 | (?=pattern) |
匹配后面是 pattern 的位置 | "a(?=b)" 匹配后面是 b 的 a(如 "ab" 中的 a) |
| 负向先行断言 | (?!pattern) |
匹配后面不是 pattern 的位置 | "a(?!b)" 匹配后面不是 b 的 a(如 "ac" 中的 a) |
| 正向后行断言 | (?<=pattern) |
匹配前面是 pattern 的位置 | "(?<=a)b" 匹配前面是 a 的 b(如 "ab" 中的 b) |
| 负向后行断言 | (?<!pattern) |
匹配前面不是 pattern 的位置 | "(?<!a)b" 匹配前面不是 a 的 b(如 "cb" 中的 b) |
示例1:提取带特定前缀的数字
import re
text = "价格:99元,优惠价:88元,原价:120元,数量:5"
# 提取“价格:”或“优惠价:”后面的数字(排除“原价:”和“数量:”)
pattern = r"(?<=价格:|优惠价:)\d+" # 正向后行断言:前面是“价格:”或“优惠价:”
print(re.findall(pattern, text)) # 输出:['99', '88']
示例2:排除特定后缀的单词
import re
text = "apple banana applet orange app"
# 匹配以 "app" 开头但不以 "let" 结尾的单词
pattern = r"app\w+(?!let)\b" # 负向先行断言:后面不是 "let"
print(re.findall(pattern, text)) # 输出:['apple', 'app'](排除 applet)
四、正则模式修饰符(flags 参数)
修饰符用于改变正则的匹配行为,常见修饰符如下:
| 修饰符 | 含义 | 简写 |
|---|---|---|
re.IGNORECASE |
忽略大小写匹配 | re.I |
re.DOTALL |
让 . 匹配换行符 \n |
re.S |
re.MULTILINE |
让 ^ 匹配行开头,` |
修饰符 |
| -------- | ------ | ------ |
re.IGNORECASE |
忽略大小写匹配 | re.I |
re.DOTALL |
让 . 匹配换行符 \n |
re.S |
| 匹配行结尾 | re.M |
|
re.VERBOSE |
允许正则换行和注释,提高可读性 | re.X |
示例:忽略大小写与多行匹配
import re
# 1. 忽略大小写匹配
text = "Python python PYTHON"
pattern = r"python"
print(re.findall(pattern, text, re.I)) # 输出:['Python', 'python', 'PYTHON']
# 2. 多行模式(匹配每行开头的数字)
text2 = """1. 第一项
2. 第二项
3. 第三项"""
pattern2 = r"^\d+"
print(re.findall(pattern2, text2, re.M)) # 输出:['1', '2', '3'](多行模式下 ^ 匹配每行开头)
# 3. 允许注释的正则(re.X 模式)
pattern3 = re.compile(r"""
\d+ # 匹配数字部分
\. # 匹配小数点
\d* # 匹配小数部分(可选)
""", re.X)
print(pattern3.findall("价格:99.5元,折扣:0.8,数量:5")) # 输出:['99.5', '0.8']
五、实战场景综合案例
案例1:解析日志文件(提取关键信息)
日志内容:
2024-10-01 08:30:25 [INFO] User 'admin' login from 192.168.1.1
2024-10-01 09:15:42 [ERROR] Database connection failed (code: 500)
2024-10-01 10:00:10 [INFO] User 'guest' login from 10.0.0.5
目标:提取所有日志的时间、级别、详情。
import re
log_text = """2024-10-01 08:30:25 [INFO] User 'admin' login from 192.168.1.1
2024-10-01 09:15:42 [ERROR] Database connection failed (code: 500)
2024-10-01 10:00:10 [INFO] User 'guest' login from 10.0.0.5"""
# 正则:分组匹配时间、级别、详情
pattern = r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(INFO|ERROR)\] (.*)"
results = re.findall(pattern, log_text)
for time, level, detail in results:
print(f"时间:{time},级别:{level},详情:{detail}")
# 输出:
# 时间:2024-10-01 08:30:25,级别:INFO,详情:User 'admin' login from 192.168.1.1
# 时间:2024-10-01 09:15:42,级别:ERROR,详情:Database connection failed (code: 500)
# 时间:2024-10-01 10:00:10,级别:INFO,详情:User 'guest' login from 10.0.0.5
案例2:清洗HTML文本(提取纯文字)
HTML内容:
<div class="content">
<h2>正则表达式指南</h2>
<p>本文介绍<span style="color:red">Python正则</span>的用法。</p>
</div>
目标:去除所有HTML标签,保留纯文字。
import re
html = """<div class="content">
<h2>正则表达式指南</h2>
<p>本文介绍<span style="color:red">Python正则</span>的用法。</p>
</div>"""
# 正则:匹配所有HTML标签(<...>)并替换为空
pattern = r"<.*?>" # 非贪婪匹配标签,避免跨标签匹配
clean_text = re.sub(pattern, "", html)
# 去除多余空白(换行、空格)
clean_text = re.sub(r"\s+", " ", clean_text).strip()
print(clean_text) # 输出:正则表达式指南 本文介绍Python正则的用法。
案例3:验证复杂密码(密码强度检测)
密码规则:
- 长度8~16位
- 包含至少1个大写字母、1个小写字母、1个数字、1个特殊字符(!@#$%^&*)
import re
def check_password(password):
# 长度8~16位
if not re.match(r"^.{8,16}$", password):
return "密码长度必须8~16位"
# 包含大写字母
if not re.search(r"[A-Z]", password):
return "密码必须包含至少1个大写字母"
# 包含小写字母
if not re.search(r"[a-z]", password):
return "密码必须包含至少1个小写字母"
# 包含数字
if not re.search(r"\d", password):
return "密码必须包含至少1个数字"
# 包含特殊字符
if not re.search(r"[!@#$%^&*]", password):
return "密码必须包含至少1个特殊字符(!@#$%^&*)"
return "密码强度合格"
print(check_password("Aa123456")) # 输出:密码必须包含至少1个特殊字符(!@#$%^&*)
print(check_password("Aa!12345678901234")) # 输出:密码强度合格
print(check_password("aa!123456")) # 输出:密码必须包含至少1个大写字母
六、常见问题与避坑指南
-
贪婪与非贪婪的选择:
- 嵌套结构(如HTML标签、多组引号)必须用非贪婪(
.*?),避免跨结构匹配; - 简单文本(无重复结束符)用贪婪更高效(如提取URL中的域名)。
- 嵌套结构(如HTML标签、多组引号)必须用非贪婪(
-
转义字符的坑:
- 正则中的特殊字符(
. * + ? ^ $ () [] {} | \)需用\转义; - Python字符串中
\本身需转义(如匹配\需写r"\\"或"\\\\")。
- 正则中的特殊字符(
-
性能优化:
- 多次使用的正则务必用
re.compile编译; - 避免过度使用
.*(贪婪匹配可能导致大量回溯),尽量用具体字符集(如[^>]+匹配标签内容)。
- 多次使用的正则务必用
-
不要用正则解析HTML/XML:
- 复杂HTML/XML嵌套结构(如标签嵌套、属性含特殊字符)无法用正则完美解析,应使用
BeautifulSoup等专用库。
- 复杂HTML/XML嵌套结构(如标签嵌套、属性含特殊字符)无法用正则完美解析,应使用
七、总结
正则表达式是Python字符串处理的核心工具,掌握其语法规则(元字符、量词)、re 模块函数、进阶特性(分组、断言)后,能高效解决文本提取、格式验证、内容清洗等问题。
学习建议:
- 基础阶段:熟记元字符和量词,通过
re.findallre.sub练习简单场景; - 进阶阶段:掌握分组、反向引用、断言,处理复杂匹配逻辑;
- 实战阶段:结合具体业务(日志解析、数据清洗),优先保证正则可读性(必要时添加注释)。
通过本文的系统讲解和示例练习,你已具备应对绝大多数正则场景的能力,剩下的就是在实践中不断优化和积累经验。

浙公网安备 33010602011771号