python之正则表达式4-零宽匹配

之前有看到过"零宽匹配"这个概念,但是一直没有搞清楚什么是"零宽匹配"。本文基于这个目的,分享一下我对于零宽匹配的理解。如果你已经非常清楚什么是"零宽匹配",那么可以不用阅读本文;如果自问还有点儿犹豫,那么你一定要看看,帮助解决心中的犹豫。

为了尽量保证 知识点的连贯和循序渐进,会先讲解定位符,然后在自然的过渡到零宽匹配。

定位符 anchors

定位符就是 只匹配位置,不匹配字符。用于"锚定"正则匹配的位置。

python的正则中,有四种定位符,^,$,\A,\Z

^有两种定位

  • 在单行模式下,字符串起始位置定位
  • 在多行模式下,行起始位置定位
import re
test_str1 = 'first\nsecond\n'
RE_ANCHOR = r'^'
for e in re.finditer(RE_ANCHOR, test_str1):
    print(f'single line:{e}')
# single line:<re.Match object; span=(0, 0), match=''>
# 说明 单行模式下,是字符串起始位置定位

for e in re.finditer(RE_ANCHOR, test_str1, re.M):
    print(f'multi line:{e}')
# multi line:<re.Match object; span=(0, 0), match=''>
# 说明 多行模式下,是 行起始位置定位
# multi line:<re.Match object; span=(6, 6), match=''>
# 说明 多行模式下,是 行起始位置定位
# multi line:<re.Match object; span=(13, 13), match=''>
# 说明 第二个\n后也作为一个新的行,所以也会匹配命中,行起始位置定位

$有三种定位

  • 在单行模式下, 字符串末尾位置定位
  • 在单行模式下,除了在字符串末尾位置定位外, 如果最后一个字符是换行符的话,也在最后一个换行符之前的位置定位(为了后面方便表述,叫做 行末尾位置定位)
  • 在多行模式下,除了在字符串末尾位置定位外,也在每个行末尾位置定位
import re
test_str1 = 'first\nsecond\n'
RE_ANCHOR = r'$'
for e in re.finditer(RE_ANCHOR, test_str1):
    print(f'single line:{e}')
# single line:<re.Match object; span=(12, 12), match=''>
# 说明 单行模式下,是 最后一个换行符之前位置定位
# single line:<re.Match object; span=(13, 13), match=''>
# 说明 单行模式下,是字符串末尾位置定位

for e in re.finditer(RE_ANCHOR, test_str1, re.M):
    print(f'multi line:{e}')
# multi line:<re.Match object; span=(5, 5), match=''>
# 说明 多行模式下,是 行末尾位置定位
# multi line:<re.Match object; span=(12, 12), match=''>
# 说明 多行模式下,是 行末尾位置定位
# multi line:<re.Match object; span=(13, 13), match=''>
# 说明 多行模式下,是 字符串末尾位置定位

\A,仅匹配字符串起始位置

import re
test_str1 = 'first\nsecond\n'
RE_ANCHOR = r'\A'
for e in re.finditer(RE_ANCHOR, test_str1):
    print(f'single line:{e}')
# single line:<re.Match object; span=(0, 0), match=''>

for e in re.finditer(RE_ANCHOR, test_str1, re.M):
    print(f'multi line:{e}')
# multi line:<re.Match object; span=(0, 0), match=''>

\Z,仅匹配字符串末尾位置

import re
test_str1 = 'first\nsecond\n'
RE_ANCHOR = r'\Z'
for e in re.finditer(RE_ANCHOR, test_str1):
    print(f'single line:{e}')
# single line:<re.Match object; span=(13, 13), match=''>

for e in re.finditer(RE_ANCHOR, test_str1, re.M):
    print(f'multi line:{e}')
# multi line:<re.Match object; span=(13, 13), match=''>

小结,这四种定位符都是匹配位置,并不匹配字符,另外从上面实例的打印结果中也可以看出,匹配结果的是一个空字符串,字符串的长度为零。这就是引出了正则中的一个概念,零宽匹配。

零宽匹配(zero-length match )

因为正则支持位置匹配,所以位置匹配的匹配结果长度为零,这就是为什么叫零宽匹配。

因为有了零宽匹配,正则表达式的使用会更加的灵活,如果能正确的使用,让将会非常有用,如果使用不当,会给你带来意想不到的结果。

import re
test_str1 = ''
RE_ZERO_LENGTH = r'^\d*$'
for e in re.finditer(RE_ZERO_LENGTH, test_str1):
    print(e)
# <re.Match object; span=(0, 0), match=''>

这个例子是非常典型的零宽匹配,test_str1是一个空字符串,长度为0;为了便于描述,空字符串中仅有一个’字符’位置,叫做void。RE_ZERO_LENGTH的第一个字符是^,它将匹配test_str1的void;RE_ZERO_LENGTH的下一个字符是\d,显然不能匹配test_str1,但是因为有*,正则引擎会继续匹配;RE_ZERO_LENGTH的下一个字符是$,显然也能匹配void。所以,最后整个RE_ZERO_LENGTH匹配命中了test_str1,但是匹配结果为'',这就是它特殊的地方。

从这个例子,可以猜想,如果正则表达式在待匹配字符串中的任意位置 有零宽匹配的话,那么它都会产生匹配结果,而且这个匹配结果为'',空字符串。为了验证这个猜想,还是结合例子来看看。

import re
test_str1 = 'a1'
RE_ZERO_LENGTH = r'\d*|a'
for e in re.finditer(RE_ZERO_LENGTH, test_str1):
    print(e)

在执行代码之前,我们可以试着分析一下,这段代码会产生多少匹配结果?如果能想清楚这个例子每一个字符是怎样匹配的,将会非常有趣!

因为python正则支持零宽匹配。首先,从test_str1的首字符的起始位置开始匹配,因为是位置匹配,显然\d不会匹配命中,但是由于*存在,所以test_str1的起始位置没有数字也可以匹配命中,这是产生的第一个匹配结果(零宽匹配),这一轮的起始位置的匹配结束。接着,从test_str1的’a’开始第二轮匹配,显然\d*不能匹配命中,但还有|,那么test_str1中的’a’匹配命中RE_ZERO_LENGTH的’a’,产生第二个匹配结果(‘a’),这一轮的第一个字符匹配结束。接着,从test_st1的’1’开始第三轮匹配,显然\d*匹配命中,产生第三个匹配结果(‘1’)。注意,此时test_str1的匹配并未结束,还有字符串的末尾位置。从test_str1的末尾位置开始匹配,因为\d*,所以末尾位置零宽匹配命中,产生第四个匹配结果

通过上面的分析,可以看出,test_str1通过RE_ZERO_LENGTH产生了四个匹配结果。那么,实际的运行结果是不是这样的呢?看看执行上面的一段代码后的结果:

<re.Match object; span=(0, 0), match=''>
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(1, 2), match='1'>
<re.Match object; span=(2, 2), match=''>

说明,由于不同语言对于零宽匹配的内部处理逻辑不完全一样,所以相同的待匹配字符串和正则表达式,在不同的语言中产生的结果可能不完全一样。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值