正则表达式(分组、零宽断言)

目录

  • 正则表达式分组
    • 捕获组
      • 编号捕获组 (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。
  • 47
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值