正则表达式:一种文本模式匹配工具,Python中使用内置模块re实现正则功能
一、正则表达式作用
1.文本过滤:从大段文本中提取需要的内容
2.合法性校验:检查用户输入是否符合格式(如手机号、邮箱、数字类型等)
二、re模块核心函数
| 函数 | 作用 | 返回值 | 说明 |
|---|---|---|---|
| re.search(pattern, string) | 在整个字符串中搜索第一个匹配项 | match对象或None | 不要求从开头匹配 |
| re.match(pattern, string) | 从字符串开头开始匹配 | match对象或None | 相当于自动加^ |
| re.findall(pattren, string) | 查找所有匹配项 | 列表 | 如果有分组,只返回分组内容 |
| re.finditer(pattern, string) | 查找所有匹配项 | 迭代器 | 每个元素是match对象 |
| re.sub(pattern, repl, string) | 替换所有匹配项 | 新字符串 | 可用\1等引用分组 |
| re.compile(pattern) | 编译正则表达式 | 正则对象 | 重复使用时提升效率 |
python
import re
msg = "hello, hunan; hello changsha"
print(re.findall(r"hello", msg)) # ['hello', 'hello']
print(re.search(r"hello", msg).group()) # hello
print(re.match(r"hello", msg).group()) # hello
三、match对象常用属性和方法
使用search或match返回的match对象
| 方法/属性 | 作用 |
|---|---|
| group() | 返回匹配到的完整字符串(参数默认为0) |
| group(n) | 返回第n个捕获分组匹配的内容(n从1开始) |
| start() | 匹配内容在原始字符串中的起始索引 |
| end() | 匹配内容在原始字符串中的结束索引(不包含) |
| span() | 返回(start, end)元组 |
四、原始字符串(raw string)
正则表达式中包含很多反斜杠 \ ,为避免Python字符转义干扰,一般在字符串前加上r
1.原始字符串中\d就是两个字符 \ 和 d,不会被解释为转义
2.普通字符串中要表示 \d 需要写成 \\d
python
# 匹配字符串中的 \t 两个字符(反斜杠+t)
print(re.search(r"o\\tsc", "hello\\tsc")) # 匹配成功
五、正则表达式基本语法
1.字符类
①匹配方括号内任意单个字符字符
②支持范围:a-z、0-9、A-Za-z0-9
③取反:\^... 匹配不在括号内的任意字符
python
re.findall("[pPfw]y", "I am learn python,hello wy")
# 结果:['py', 'wy'](匹配 py 或 Py 或 fy 或 wy)
2.或 |
①匹配左右任一表达式
②| 优先级较低,必要时用括号分组
python
re.findall(r"changsha|wuhan", "welcome to changsha, welcome to wuhan")
# 结果:['changsha', 'wuhan']
3.占位符 .
匹配除换行符 \n 外的任意单个字符(re.S使其可以匹配换行)
python
re.findall(".", "aA2*\t\n中文") # 不包含 \n,其余都匹配
4.锚点 ^ 和 $
①^:匹配字符串开头(多行模式下匹配每行开头,需re.M)
②$:匹配字符串结尾(多行模式下匹配每行结尾)
python
re.findall(r"^p.thon", "pwthon python") # 只匹配开头的 pwthon
re.findall(r"p.thon$", "pwthon python") # 只匹配结尾的 python
5.快捷方式(预定义字符类)
| 符号 | 含义 | 等价写法 |
|---|---|---|
| \d | 数字 | 0-9 |
| \D | 非数字 | \^0-9 |
| \w | 单词字符(字母、数字、下划线) | A-Za-z0-9_ |
| \W | 非单词字符 | \^A-Za-z0-9_ |
| \s | 空白字符(空格、\t、\n、\r、\f、\v) | \\t\\n\\r\\f\\v |
| \S | 非空白字符 | \^ \\t\\n\\r\\f\\v |
| \b | 单词边界(单词与空格/标点之间) | 无等价 |
| \B | 非单词边界 | 无等价 |
| \A | 字符串开始(不受 re.M 影响) |
类似 ^ |
python
re.findall(r"\d", "hello 123") # ['1','2','3']
re.findall(r"\bworld\b", "world# world123 worldxyz world abcworld")
# 结果:['world', 'world', 'world'] 只匹配独立的 world
6.量词(重复次数)
| 符号 | 含义 |
|---|---|
| * | 前面的元素出现 0 次或多次 |
| ? | 前面的元素出现 0 次或多次 |
| + | 前面的元素出现 1 次或多次 |
| {n} | 前面的元素恰好出现 n 次 |
| {n,} | 至少出现 n 次 |
| {n,m} | 出现 n 到 m 次 |
python
re.findall("py?", "python p pyy ps") # ['py', 'p', 'py', 'p']
re.findall("py+", "python p pyy ps") # ['py', 'pyy']
re.findall("py*", "python p pyy ps") # ['py', 'p', 'pyy', 'p']
re.findall("py{2,4}", "pyyyy pyy p") # ['pyyyy', 'pyy']
7.贪婪模式 vs 非贪婪模式
①贪婪模式(默认):量词尽可能多地匹配
②非贪婪模式:在量词后加 ?,匹配尽可能少
| 量词 | 非贪婪模式 | 说明 |
|---|---|---|
| * | *? | 最少 0 次(可为空) |
| + | +? | 最少 1 次 |
| ? | ?? | 最少 0 次 |
| {n,m} | {n,m}? | 最少 n 次 |
python
msg = "<div><p>hello</p></div><div><h1>world</h1></div>"
print(re.findall(r"<div>.*</div>", msg))
# 贪婪:匹配最长的 div 块 → ['<div><p>hello</p></div><div><h1>world</h1></div>']
print(re.findall(r"<div>.*?</div>", msg))
# 非贪婪:匹配最短的 div 块 → ['<div><p>hello</p></div>', '<div><h1>world</h1></div>']
六、分组与捕获
1.捕获分组(regex)
①将括号内的表达式作为一个整体
②匹配后会保存到内存,按左括号顺序编号1,2,3...
③可通过group(n)获取第n组内容
python
msg = "tel:173-7572-2991"
ret = re.search(r'(\d{3})-(\d{4})-(\d{4})', msg)
print(ret.group(0)) # 173-7572-2991 (完整匹配)
print(ret.group(1)) # 173
print(ret.group(2)) # 7572
print(ret.group(3)) # 2991
2.非捕获分组(?:regex)
①只分组不捕获,不占用分组编号
②作用:使结构清晰,但不保存内容
python
ret = re.search(r'(?:\d{3})-(\d{4})-(\d{4})', msg)
print(ret.group(1)) # 7572 (因为第一个分组是非捕获,编号从后面的开始)
3.向后引用 \1、\2...
①引用前面捕获分组匹配到的具体内容
②可用于匹配重复内容
python
msg = "tel:173-7572-173"
ret = re.search(r'(\d{3})-(\d{4})-\1', msg)
print(ret.group()) # 173-7572-173 (最后的 173 与第一组相同)
# 非捕获分组后的引用
msg = "tel:173-7572-7572"
ret = re.search(r'(?:\d{3})-(\d{4})-\1', msg)
print(ret.group()) # 173-7572-7572 (\1 引用第一捕获组即 \d{4})
4.findall与分组的关系
①如果正则中包含捕获分组,findall只返回分组内容,不返回完整匹配
②若想返回完整匹配,可使用非捕获分组(?:...) 或去掉分组
python
msg = "tel:173-7572-7572"
# 有捕获分组
print(re.findall(r'(\d{3})-(\d{4})-\d{4}', msg)) # [('173','7572')]
# 非捕获分组
print(re.findall(r'(?:\d{3})-(?:\d{4})-\d{4}', msg)) # ['173-7572-7572']
七、正则修饰符
| 修饰符 | 含义 |
|---|---|
| re.M | 多行模式,^ 和 $ 匹配每行的开头和结尾 |
| re.I | 忽略大小写 |
| re.S | 使 . 匹配包括换行符在内的任意字符 |
| re.X | 详细模式,允许在正则中添加注释和空白 |
| re.A | 使 \w、\b 等只匹配 ASCII 字符 |
python
msg = """
python
PyTHON
"""
print(re.findall("^python$", msg, re.M | re.I))
# 匹配两行,因为忽略大小写且每行单独匹配
八、零宽断言
零宽断言只匹配位置,不消耗字符,不会出现在匹配结果中
| 语法 | 名称 | 含义 |
|---|---|---|
| (?<=exp) | 正向后行断言 | 左边必须是 exp |
| (?<!exp) | 负向后行断言 | 左边不能是 exp |
| (?=exp) | 正向前行断言 | 右边必须是 exp |
| (?!exp) | 负向前行断言 | 右边不能是 exp |
python
msg = "hello, world! welcome to changsha"
# 正向后行:匹配以 w 开头的字母串(不包括 w)
print(re.findall(r"(?<=w)[a-z]+", msg)) # ['orld', 'elcome']
# 负向后行:匹配 l,但左边不能是 e
print(re.findall(r"(?<!e)l", msg)) # 只匹配 world 中的 l(左边是 r)
# 正向前行:匹配逗号前的字母串
print(re.findall(r"[a-z]+(?=,)", msg)) # ['hello']
# 负向前行:匹配后面不是逗号的字母串(按 findall 逐个字符找连续字母)
print(re.findall(r"[a-z]+(?!,)", msg)) # ['hell', 'world', 'welcom', 'to', 'changsh'] 等