正则表达式(分组、零宽断言)
目录
- 正则表达式分组
-
- 捕获组
-
- 编号捕获组 (pattern)
- 命名捕获组 (?\<name>pattern)
- 非捕获组 (?:pattern)
- 零宽断言
-
- 先行断言
-
- 零宽正向先行断言 (?=pattern1)pettern2
- 零宽负向先行断言 (?!pattern1)pettern2
- 后行断言
-
- 零宽正向后行断言 (?<=pattern1)pettern2
- 零宽负向后行断言 (?<!pattern1)pettern2
正则表达式分组
分组格式:一对小括号()表示。
- 一个正则表达式中有几个()就表示有几个分组。
():表示有1个分组,匹配空字符串。
(\w)(\w)(\w):表示有3个分组。- 分组(捕获组)索引以1开始,0表示整个正则表达式组。
1表示你定义的第一个分组,以此类推。分组类型:捕获组(Capturing Groups)与非捕获组Non-Capturing Groups。
分组目的:缓存捕获数据、反向引用、拆分匹配数据。
捕获组
我们通常所说的分组,默认就是指捕获组,用一对小括号(pattern)表示。
捕获组类型:
- 编号捕获组Numbered Capturing Groups
- 命名捕获组Named Capturing Groups
是否消耗原始字符串:是。
编号捕获组 (pattern)
-
整体匹配(非分组模式,做对比展示)
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0" pattern = "\w+_\d\.\d\.\d" res = re.match(pattern,raw_string) print(res) # <re.Match object; span=(0, 10), match='CSDN_6.3.0'> print(res.groups()) # () 分组匹配为空 print(res.group(0)) # CSDN_6.3.0 整体匹配结果 # print(res.group(1)) # 无分组,会报错“IndexError: no such group”
-
拆分并缓存捕获数据
拆分匹配到的数据:
- 将整体表达式 “\w+\d.\d.\d” ,拆分为 “(\w+)(\d.\d.\d)”,包含分组一(\w+) ,分组二(\d.\d.\d)
缓存捕获数据:
- 将分组中的正则表达式匹配到的内容,保存到该分组里面。
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0" pattern = "(\w+)_(\d\.\d\.\d)" res = re.match(pattern, raw_string) print(res) # <re.Match object; span=(0, 10), match='CSDN_6.3.0'> print(res.groups()) # ('CSDN', '6.3.0') 两个分组 print(res.group(0)) # CSDN_6.3.0 整体匹配结果 print(res.group(1)) # CSDN 分组1匹配结果 print(res.group(2)) # 6.3.0 分组2匹配结果
-
反向引用 \1
引用格式:\number
- number: 分组编号,从1开始,如:\1表示引用第一个分组的匹配结果
作用:当原始字符串中出现不连续的相同子字符串时,可以通过分组引用的方式,简化匹配模式。
特别注意:只引用匹配结果,而不是引用匹配模式。
# 例1:CSDN_6.3.0_CSDN import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" pattern = r"(\w+)_(\d\.\d\.\d)_\1" res = re.match(pattern, raw_string) print(res) # <re.Match object; span=(0, 15), match='CSDN_6.3.0_CSDN'> print(res.groups()) # ('CSDN', '6.3.0') 两个分组 print(res.group(0)) # CSDN_6.3.0_CSDN 整体匹配结果 print(res.group(1)) # CSDN 分组1匹配结果 print(res.group(2)) # 6.3.0 分组2匹配结果 # print(res.group(3)) 报错,分组引用时,不会创建分组。\1引用的是匹配结果“CSDN”。 # 例2:<html>text</html> import re raw_string = "<html>testtext</html>" pattern = r"<(\w+)>\w+</\1>" res = re.match(pattern, raw_string) print(res) # <re.Match object; span=(0, 27), match='<html>thisistesttext</html>'> print(res.groups()) # ('html',) 两个分组 print(res.group(0)) # <html>thisistesttext</html> 整体匹配结果 print(res.group(1)) # html 分组1匹配结果 # print(res.group(2)) 报错,分组引用时,不会创建分组。\1引用的是匹配结果“html”。
命名捕获组 (?<name>pattern)
语法:(?P<name>pattern)
优点:用命名捕获组会比较直观,可以用名称描述捕获到的内容的含义。
-
基本使用
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0" pattern = r"(?P<n1>\w+)_(?P<n2>\d\.\d\.\d)" res = re.match(pattern,raw_string) print(res) # <re.Match object; span=(0, 10), match='CSDN_6.3.0'> print(res.groups()) # ('CSDN', '6.3.0') print(res.group(0)) # CSDN_6.3.0 print(res.group("n1")) # CSDN print(res.group("n2")) # 6.3.0
-
反向引用
语法:
- python:(?P=name)
- 其他用法:\name、\k、\k’name’
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" pattern = r"(?P<n1>\w+)_(?P<n2>\d\.\d\.\d)_(?P=n1)" res = re.match(pattern,raw_string) print(res) # <re.Match object; span=(0, 15), match='CSDN_6.3.0_CSDN'> print(res.groups()) # ('CSDN', '6.3.0') print(res.group(0)) # CSDN_6.3.0_CSDN print(res.group("n1")) # CSDN print(res.group("n2")) # 6.3.0 # print(res.group(3)) # 报错,分组引用时,不会创建分组。(?P=n1)引用的是匹配结果“CSDN”。
非捕获组 (?:pattern)
在捕获组的基础上,在左括号的右侧加上?:,即:(?:pattern)。
不会把正则匹配到的内容保存到分组里面。提升效率。
是否消耗原始字符串:是。
是否保存匹配到的字符串到分组:否。
-
基础用法
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" pattern = r"(?P<n1>\w+)_(?:\d\.\d\.\d)_(?P=n1)" res = re.match(pattern,raw_string) print(res) # <re.Match object; span=(0, 15), match='CSDN_6.3.0_CSDN'> print(res.groups()) # ('CSDN', ) 未保存第二个分组 print(res.group(0)) # CSDN_6.3.0_CSDN print(res.group("n1")) # CSDN # print(res.group(1)) # 报错,非捕获分组不会保存匹配的数据,且分组引用时,也不会创建分组。(?P=n1)引用的是匹配结果“CSDN”。
-
使用场景说明
- 非捕获组和零宽断言的区别:
- 非捕获组消耗原始字符串,零宽断言不消耗原始字符串。
- 非捕获组和零宽断言的相同点:
- 都不会创建分组,不会保存匹配结果到分组。
- 非捕获组和零宽断言的区别:
零宽断言
一种特殊形式的正则表达式,匹配时不消耗原始字符,只判断pattern1是否匹配成功,如同:^ $ \b等边界匹配一样。
然后根据pattern1匹配结果进行断言,断言结果为“成功”或者“失败”,作为是否进行下一步匹配的条件
- 若断言“成功”,继续进行正则匹配,返回匹配结果。
- 若断言“失败”,则终止匹配行为,返回为空。
4种场景:
- 零宽正向先行断言
- 零宽负向先行断言
- 零宽正向后行断言
- 零宽负向后行断言
零宽:表示不消耗原始字符串。去除pattern1后正则表达式依旧能够匹配到目标字符串。
断言:对pattern1匹配结果进行正/负向判断。
正向:表示pattern1匹配到字符串时,断言结果为true。
负向:与正向相反,表示pattern1匹配到字符串时,断言结果为false。
先行断言
断言先于匹配位置发生。相对于pattern1表达式后面的表达式(pattern2)而言。
pattern0捕获匹配 -> pattern1先行断言 -(先于pattern2判断)->pattern2捕获匹配->返回匹配结果。
即:
step1:先根据pattern0匹配原始字符串。
step2:用pattern1匹配pattern2左侧的字符串,然后对pettern1匹配结果进行断言,若断言失败,则返回:None。反之,则继续下一步pattern2的匹配。
step3:若pattern2匹配成功,则返回整合表达式的匹配结果,反之,返回结果为:None。
零宽正向先行断言 (?=pattern1)pettern2
满足pattern1匹配,为成功。
不满足pattern1匹配,为失败。
- 示例
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" # pattern0(?<=pattern1)pettern2 # 表达式技巧:去除零宽断言pattern1后,可以正常匹配字符串。 pattern = r"(\w+)_(?=\d\.\d\.\d)(.+)" # 匹配逻辑:pattern0捕获匹配 -> pattern1先行断言 -(判断是否成功)->pattern2捕获匹配 res = re.match(pattern,raw_string) print(res) # <re.Match object; span=(0, 15), match='CSDN_6.3.0_CSDN'> print(res.groups()) # ('CSDN', '6.3.0_CSDN') 未保存第二个分组 print(res.group(0)) # CSDN_6.3.0_CSDN print(res.group(1)) # CSDN print(res.group(2)) # 6.3.0_CSDN # print(res.group(3)) # 报错,零宽断言不创建分组。
匹配步骤:
- step1:先根据pattern0匹配到第1个“CSDN”
- step2:然后判断pattern1是否匹配成功CSDN_后面的字符串(匹配到“6.3.0”,成功)
- step3:然后进行pattern2匹配(“.+”从“CSDN_”字符串开始匹配,匹配到“6.3.0_CSDN”)。
零宽负向先行断言 (?!pattern1)pettern2
满足pattern1匹配,为失败。
不满足pattern1匹配,为成功。
解释: pattern1匹配结果,进行“非”运算,结果与正向相反。
- 示例1:断言成功
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" # pattern0(?<=pattern1)pettern2 # 表达式技巧:去除零宽断言pattern1后,可以正常匹配字符串。 pattern = r"(\w+)_(?!AAA)(.+)" # 匹配逻辑:pattern0捕获匹配 -> pattern1先行断言 -(先于pattern2判断)->pattern2捕获匹配 res = re.match(pattern,raw_string) print(res) # <re.Match object; span=(0, 15), match='CSDN_6.3.0_CSDN'> print(res.groups()) # ('CSDN', '6.3.0_CSDN') 未保存第二个分组 print(res.group(0)) # CSDN_6.3.0_CSDN print(res.group(1)) # CSDN print(res.group(2)) # 6.3.0_CSDN # print(res.group(3)) # 报错,零宽断言不创建分组。
匹配步骤:
- step1:先根据pattern0匹配到第1个“CSDN”
- step2:然后判断pattern1是否匹配成功CSDN_后面的字符串(匹配不到“AAA”,成功)
- step3:然后进行pattern2匹配(“.+”从“CSDN_”字符串开始匹配,匹配到“6.3.0_CSDN”)。
- 示例2:断言失败
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" # pattern0(?<=pattern1)pettern2 # 表达式技巧:去除零宽断言pattern1后,可以正常匹配字符串。 pattern = r"(\w+)_(?!\d\.\d\.\d)(.+)" # 匹配逻辑:pattern0捕获匹配 -> pattern1先行断言 -(先于pattern2判断)-> pattern2捕获匹配 res = re.match(pattern,raw_string) print(res) # None print(res.groups()) # 报错,res为None。
匹配步骤:
- step1:先根据pattern0匹配到第1个“CSDN”
- step2:然后判断pattern1是否匹配成功CSDN_后面的字符串(匹配到“6.3.0”,成功)
- step3:不会进行pattern2匹配,返回匹配结果为:None。
后行断言
断言晚于匹配位置发生。相对于pattern1表达式后面的表达式(pattern2)而言。
即:
step1:先根据正则表达式匹配到pattern2,若pattern2匹配失败,则返回:None。反之,则进行下一步pattern1匹配断言。
step2:用pattern1匹配pattern2左侧的字符串,然后对pettern1匹配结果进行断言。
step3:若断言成功,则返回整个表达式匹配结果。反之,断言失败,返回结果为:None。
零宽正向后行断言 (?<=pattern1)pettern2
- 示例1:断言成功
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" # pattern0(?<=pattern1)pettern2 # 表达式技巧:去除零宽断言pattern1后,可以正常匹配字符串。 pattern = r"(CSDN_\d\.\d\.\d)(?<=\d\.\d\.\d)_CSDN" # 匹配逻辑:pattern0捕获匹配 -> pattern2捕获匹配 -> pattern1后行断言(晚于pattern2) res = re.search(pattern,raw_string) print(res) # <re.Match object; span=(0, 15), match='CSDN_6.3.0_CSDN'> print(res.groups()) # ('CSDN_6.3.0',) print(res.group(0)) # CSDN_6.3.0_CSDN 匹配结果 print(res.group(1)) # CSDN_6.3.0 分组值 print(res.group(2)) # 报错,零宽断言不创建分组。
匹配步骤:
- step1:先根据"CSDN_(\d.\d.\d)_CSDN进行匹配,匹配到“CSDN_6.3.0_CSDN”。
- step2:断言:判断pattern1是否成功匹配“_CSDN”前字符串(匹配到“6.3.0”,成功)。
- step3:返回step1匹配的结果。
零宽负向后行断言 (?<!pattern1)pettern2
- 示例1:断言成功
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" # pattern0(?<=pattern1)pettern2 # 表达式技巧:去除零宽断言pattern1后,可以正常匹配字符串。 pattern = r"(CSDN_\d\.\d\.\d)(?<!AAA)_CSDN" # 匹配逻辑:pattern0捕获匹配 -> pattern2捕获匹配 -> pattern1后行断言(晚于pattern2) res = re.search(pattern,raw_string) print(res) # <re.Match object; span=(0, 15), match='CSDN_6.3.0_CSDN'> print(res.groups()) # ('CSDN_6.3.0',) print(res.group(0)) # CSDN_6.3.0_CSDN 匹配结果 print(res.group(1)) # CSDN_6.3.0 分组值 print(res.group(2)) # 报错,零宽断言不创建分组。
匹配步骤:
- step1:先根据"CSDN_(\d.\d.\d)_CSDN进行匹配,匹配到“CSDN_6.3.0_CSDN”。
- step2:断言:判断pattern1是否成功匹配“_CSDN”前的字符串(未匹配到“AAA”,成功)。
- step3:则返回step1匹配的结果。
- 示例2:断言失败
import re # 原始字符串 = 产品名称_发布版本 raw_string = "CSDN_6.3.0_CSDN" # pattern0(?<=pattern1)pettern2 # 表达式技巧:去除零宽断言pattern1后,可以正常匹配字符串。 pattern = r"(CSDN_\d\.\d\.\d)(?<!\d\.\d\.\d)_CSDN" res = re.search(pattern,raw_string) print(res) # None
匹配步骤:
- step1:先根据"CSDN_(\d.\d.\d)_CSDN进行匹配,匹配到“CSDN_6.3.0_CSDN”。
- step2:断言:判断pattern1是否成功匹配“_CSDN”前的字符串(匹配到“6.3.0”,失败)。
- step3:返回结果为:None。
2024-08-05 22:23:14【出处】:https://blog.csdn.net/qq_40214669/article/details/136911386
=======================================================================================
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/18344169
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!