python之正则表达式3

上一篇博客《python之正则表达式2》学习了正则表达是的7种syntax,本片博客继续学习。

本文分享5种正则语法,是比较高级的正则表达式在python中的应用。理解起来会比前一篇文章中的语法难度大一点,但是我相信,如果你认真的阅读本文,一定有所收获!!!

正则表达式的语法

(?=…)

Matches if ... matches next, but doesn’t consume any of the string. This is called a lookahead assertion. For example, Isaac (?=Asimov) will match 'Isaac ' only if it’s followed by 'Asimov'.

(?=...)是positive lookahead assertion。我这里翻译为 肯定前向断言

例如,正则表达是为Isaac (?=Asimov),只有待匹配字符串 'Isaac ‘后面紧跟’Asimov’,才会匹配命中,但是’Asimov’不会作为匹配结果的一部分,也就是说(?=...)中的...相当于一种判断条件。只有’条件’满足,才能匹配。

import re
test_str1 = 'Isaac Asimov'
test_str2 = 'Isaac Asimo'
RE_POSITIVE_LOOKAHEAD_ASSERTION = r'Isaac (?=Asimov)'
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str1)) # <re.Match object; span=(0, 6), match='Isaac '>
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str2)) # None

如果以上的描述,你还不能理解 什么是 肯定前向断言,我下面使用一段伪代码再描述一下,如果已理解就不用再看了

if 'Isaac ' 后面紧跟 'Asimov':
    匹配结果为'Isaac '
else:
    匹配结果为 None

因为 (?=...) 不是 捕获分组(capturing group),所以小括号中的正则表达式会被正则引擎执行匹配动作,如果匹配命中匹配结果中并不包含小括号中的正则表达式内容;如果匹配不命中,就没有匹配结果。另外,...也不能作为 反向引用(backreferences)被复用。

这里我就有个问题,如果我一定要引用(?=...)中的...,咋办呢?解决办法是 将 ...外再加一对小括号,也就是这个样子,(?=(...))

import re
test_str1 = 'Isaac Asimov'
RE_POSITIVE_LOOKAHEAD_ASSERTION2 = r'Isaac (?=(Asimov))'
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION2, test_str1).group(1)) # 输出结果为 Asimov,这样就可以引用'...'的内容了。

怎样一句话描述 肯定前向断言呢?

官方的描述是:(我个人觉得 非常精辟)

‘Matches if ... matches next, but doesn’t consume any of the string.’

我尝试用中文描述:

如果...匹配,那么有匹配结果,但匹配结果不包含...的内容;否则,无匹配结果。

理解了 肯定前向断言,接下来再理解 否定前向断言就非常简单了。

(?!..)

Matches if ... doesn’t match next. This is a negative lookahead assertion. For example, Isaac (?!Asimov) will match 'Isaac ' only if it’s not followed by 'Asimov'.

(?!...)是negative lookahead assertion。我这里翻译为 否定前向断言**。

同样,怎样一句话描述 否定前向断言呢?

如果...不匹配,那么有匹配结果。也就是和肯定前向断言的"判断条件"刚好相反。

import re
test_str1 = 'Isaac Asimov'
test_str2 = 'Isaac Asimo'
RE_NEGATIVE_LOOKAHEAD_ASSERTION = r'Isaac (?!Asimov)'
print(re.search(RE_NEGATIVE_LOOKAHEAD_ASSERTION, test_str1)) # None
print(re.search(RE_NEGATIVE_LOOKAHEAD_ASSERTION, test_str2)) # <re.Match object; span=(0, 6), match='Isaac '>

第5行,test_str1中的’Asimov’刚好匹配(?!...)中的...,由于是不匹配才会产生匹配结果,所以输出结果为None。

第6行,test_str2中的’Asimo’ 不匹配(?!...)中的...,由于是不匹配才会产生匹配结果,所以输出结果为 'Isaac '。

应用

第一篇文章介绍过character class,在character class中,我们知道^表示否定的意思并且它总是要匹配一个字符。例如, r’apple[^s]’,表示匹配’apple’后的一个非’s’的字符。

如果待匹配字符串为’apple’,正则表达式为 r’apple[^s]’,它的含义是:匹配apple后是非字符’s’的一个字符,如果apple后没有字符了,则匹配结果为None;如有’apple’后有非’s’字符,那么匹配结果为’apple’+“非s字符”。

import re
test_str1 = 'apple'
test_str2 = 'apple is kind of fruit'
test_str3 = 'lot of apples'
RE_CHARACTER_CLASS = r'apple[^s]'
print(re.search(RE_CHARACTER_CLASS, test_str1))	# None;因为'apple'后没有字符了,所以匹配结果为None
print(re.search(RE_CHARACTER_CLASS, test_str2)) # <re.Match object; span=(0, 6), match='apple '>;因为空格也是非's’字符,所以匹配结果为'apple '
print(re.search(RE_CHARACTER_CLASS, test_str3)) # None
场景1:

假设有这样一种需求,如果’apple’后没有字符了,'apple’也要被匹配命中。怎么办呢?可使用negative lookahead assertion解决。

import re
test_str1 = 'apple'
test_str2 = 'apple is kind of fruit'
test_str3 = 'lot of apples'
RE_NEGATIVE_LOOKAHEAD_ASSERTION = r'apple(?!s)'
print(re.search(RE_NEGATIVE_LOOKAHEAD_ASSERTION, test_str1)) # <re.Match object; span=(0, 5), match='apple'>
print(re.search(RE_NEGATIVE_LOOKAHEAD_ASSERTION, test_str2)) # <re.Match object; span=(0, 5), match='apple'>
print(re.search(RE_NEGATIVE_LOOKAHEAD_ASSERTION, test_str3)) # None

通过这个例子可以看出,使用 否定前向断言(negative lookahead assertion),第6行 正常输出了 ‘apple’,表明匹配命中。第7行 匹配结果为 ‘apple’,说明 (?!s)匹配的内容(s) 不作为匹配结果的一部分,仅作为断言。也相当于一种判断条件,如果判断条件为真,就输出’apple’;如果判断条件为假,就输出None。

场景2:

我们知道character class的匹配的是 单个字符,如果要匹配字符串,就需要用到 lookahead assertion。

import re
test_str1 = "If you can't explain it to a six year old, you don't understand it yourself."
RE_POSITIVE_LOOKAHEAD_ASSERTION = r'un(?=(derstand))'
RE_CHARACTER_CLASS = r'un[a-z]'
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str1).group(1)) # 输出结果为 derstand
print(re.search(RE_CHARACTER_CLASS, test_str1).group()) # 输出结果为 und

(?<=…)

Matches if the current position in the string is preceded by a match for ... that ends at the current position.

(?<=...) 是 positive lookbehind assertion。我这里翻译为 肯定后向断言

相较于(?=...)肯定后向断言 多了一个<,而这个左尖括号也非常形象,表示搜索方向变成了后向(从右到左)。肯定后向断言肯定前向断言 作用类似,唯一区别在 搜索方向上,lookbehind是向后搜索lookahead是向前搜索

那么,肯定后向断言要怎么理解或者正确使用呢?接下来进行详细阐述。

为了更明显的对比,这里沿用(?=...)的示例,并稍作修改。

import re
test_str1 = 'Asimov Isaac'
test_str2 = 'Asimo Isaac'
test_str3 = 'Asimov Isaa'
RE_POSITIVE_LOOKAHEAD_ASSERTION = r'(?<=Asimov) Isaac'
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str1)) # <re.Match object; span=(6, 12), match=' Isaac'>
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str2)) # None
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str3)) # None

​ 首先,正则引擎 从 test_str1的第1个字符(‘A’)开始后向搜索,发现’A’前面没有任何字符,那么,匹配失败;搜索继续,再从第2个字符(‘s’)开始后向搜索,发现’s’前面的字符是’A’,不能匹配’Asimov’,匹配失败;搜索继续,直到空格字符,发现空格字符前的字符串是’Asimov’,满足后向断言,同时test_str1中的’ Isaac’与RE_POSITIVE_LOOKAHEAD_ASSERTION中的’ Isaac’也完全匹配,至此,整个正则匹配成功。由于(?<=...)中的...不会作为匹配结果的内容,所以最终的匹配结果是’ Isaac’。

​ test_str2 没有匹配结果,是因为’Asimo’ 匹配不上’Asimov’。

​ test_str3 没有匹配结果,是以为’ Isaa’ 匹配不上’ Isaac’。

(?<!..)

Matches if the current position in the string is not preceded by a match for ....

(?<!...) 是 negativelookbehind assertion。我这里翻译为 否定后向断言

相较于 肯定后向断言,否定后向断言就是一个相反的判断条件。即(?<!...)中的...不匹配,才产生匹配结果。

接下来,还是通过实例来阐述

import re
test_str1 = 'Asimov Isaac'
test_str2 = 'Asimo Isaac'
test_str3 = 'Asimov Isaa'
RE_POSITIVE_LOOKAHEAD_ASSERTION = r'(?<!Asimov) Isaac'
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str1)) # None
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str2)) # <re.Match object; span=(5, 11), match=' Isaac'>
print(re.search(RE_POSITIVE_LOOKAHEAD_ASSERTION, test_str3)) # None

test_str1中的’Asimov’ 与 RE_POSITIVE_LOOKAHEAD_ASSERTION中的’Asimov’匹配,不产生匹配结果。

test_str2中的’Asimo’ 与 RE_POSITIVE_LOOKAHEAD_ASSERTION中的’Asimov’不匹配,test_str2中的’ Isaac’ 与 RE_POSITIVE_LOOKAHEAD_ASSERTION中的’ Isaac’匹配,满足条件,产生匹配结果 ’ Isaac’

test_str3中的’Asimov’ 与 RE_POSITIVE_LOOKAHEAD_ASSERTION中的’Asimov’匹配,不产生匹配结果。

小结:

上面分享了四种正则语法的理解,分别是:

positive lookahead assertion
negative lookahead assertion
positive lookbehind assertion
negative lookbehind assertion

核心点有三个:断言,方向,是否

断言 对应 assertion;类似于条件判断 if-else;

方向 对应 lookaround(lookahead/lookbehind);表示搜索方向,lookahead-前向-从左向右,lookbehind-后向-从右向左;通过记号 <来表示方向。

是否 对应 positive/negative;通过记号!来表示是否。

(?(id/name)yes-pattern|no-pattern)

Will try to match with yes-pattern if the group with given id or name exists, and with no-pattern if it doesn’t. no-pattern is optional and can be omitted.

(?(id/name)yes-pattern|no-pattern)属于 if-then-else条件正则表达式中的一种。

if-then-else条件正则的语法是:(?ifthen|else),它的组成部分是 ‘(’ + ‘?’ + ‘if部分’ + ‘then部分’ + ‘|’ + ‘else部分’ + ‘)’;其中 ‘|’ + 'else部分’可以省略。

该正则表达式的处理逻辑是:如果*‘if部分’为True,正则引擎就会执行’then部分’;如果’if部分’为False,正则引擎就会执行’else部分’*。

对应到(?(id/name)yes-pattern|no-pattern):
’if部分’ 就是 (id/name)
‘then部分’ 就是 yes-pattern
‘else部分’ 就是 no-pattern

为了方便后面描述,我将(?(id/name)yes-pattern|no-pattern)叫做 分组条件正则

接下来会举例来阐述 分组条件正则的使用及原理,分组条件正则 已经属于高级正则表达式的范畴,所以理解起来会有难度,但我会尽量描述清楚。为了尽量减少复杂度,下面的例子使用最简单的abcd。

# 示例1
import re
test_str1 = 'abc'
RE_CONDITIONAL = r'(a)?b(?(1)c|d)'
print(re.search(RE_CONDITIONAL, test_str1))				# <re.Match object; span=(0, 3), match='abc'>
print(re.search(RE_CONDITIONAL, test_str1).group())		# abc
print(re.search(RE_CONDITIONAL, test_str1).group(1))	# a

在示例1中,RE_CONDITIONAL由 (a)? + b + (?(1)c|d) 三部分组成。接下来分析正则引擎的匹配原理。

首先从test_str1的第一个字符’a’开始,test_str1中的’a’ 能与RE_CONDITIONAL中的无名捕获分组’(a)‘匹配,然后test_str1中的’b’ 也能与 RE_CONDITIONAL中的’b’匹配,接下来 正则引擎进入 分组条件判断,因为分组(1)存在,即前面的’a’,所以接着匹配RE_CONDITIONAL中的yes-pattern(也就是’c’字符),而test_str1中也存在’c’,那么匹配命中。也就是,test_str1全部匹配命中,匹配结果为’abc’。test_str1 的匹配搜索路径是:id exists —> yes-pattern

# 示例2
import re
test_str1 = 'bd'
RE_CONDITIONAL = r'(a)?b(?(1)c|d)'
print(re.search(RE_CONDITIONAL, test_str1))				# <re.Match object; span=(0, 2), match='bd'>
print(re.search(RE_CONDITIONAL, test_str1).group())		# bd
print(re.search(RE_CONDITIONAL, test_str1).group(1))	# None

示例2中,RE_CONDITIONAL不变,只是test_str1变成了’bd’。接下来分析正则引擎的匹配原理。

test_str1的第一个字符是’b’,那么RE_CONDITIONAL的(a)匹配失败,但是因为(a)后有?,所以test_str1中是否有’a’是可选的(optional);然后,test_str1中的’b’与RE_CONDITIONAL中的’b’匹配;接着,正则引擎进入 分组条件判断,因为前面无名捕获分组不存在,所以不会匹配RE_CONDITIONAL的yes-pattern(也就是’c’字符),而是去匹配RE_CONDITIONAL中的no-pattern(也就是’d’字符),发现,test_str1中也存在’d’,所以匹配命中。也就是,test_str1全部匹配命中,匹配结果为’bd’。test_str1 的匹配搜索路径是: id not exists —> no-pattern

# 示例3
import re
test_str1 = 'bc'
RE_CONDITIONAL = r'(a)?b(?(1)c|d)'
print(re.search(RE_CONDITIONAL, test_str1))	# None

示例3中,RE_CONDITIONAL不变,只是test_str1变成了’bc’。接下来分析正则引擎的匹配原理。

test_str1的第一个字符是’b’,那么RE_CONDITIONAL的(a)匹配失败,但是因为(a)后有?,所以test_str1中是否有’a’是可选的(optional);然后,test_str1中的’b’与RE_CONDITIONAL中的’b’匹配;接着,正则引擎进入 分组条件判断,因为前面无名捕获分组不存在,所以不会匹配RE_CONDITIONAL的yes-pattern(也就是’c’字符),而是去匹配RE_CONDITIONAL中的no-pattern(也就是’d’字符),由于test_str1中的’c’ 与 no-pattern中的’d’不匹配,所以没有匹配结果。接下来,再从test_str1的第二个字符’c’开始进行匹配,首先,test_str1的’c’与无名捕获分组(a)不匹配,’?‘是optional,搜索继续往下,test_str1的’c’ 与 RE_CONDITIONAL中的’b’ 不匹配,所以没有匹配结果。整个匹配过程结束。

# 示例4
import re
test_str1 = 'abd'
RE_CONDITIONAL = r'(a)?b(?(1)c|d)'
print(re.search(RE_CONDITIONAL, test_str1)) # <re.Match object; span=(1, 3), match='bd'>

示例4中,RE_CONDITIONAL不变,只是test_str1变成了’abd’。接下来分析正则引擎的匹配原理。

首先从test_str1的第一个字符’a’开始,test_str1中的’a’ 能与RE_CONDITIONAL中的无名捕获分组’(a)‘匹配,然后test_str1中的’b’ 也能与 RE_CONDITIONAL中的’b’匹配,接下来 正则引擎进入 分组条件判断,因为分组(1)存在,即前面的’a’,所以接着匹配RE_CONDITIONAL中的yes-pattern(也就是’c’字符),而test_str1中’d’与RE_CONDITIONAL中’c’不匹配,那么,本轮匹配失败。注意,因为无名捕获分组存在,所以只有yes-pattern会被执行,no-pattern不会被执行。但是,正则引擎的搜索匹配并没有结束。将会从test_str1的第二个字符’b’开始新的一轮搜索匹配,test_str1的’b’与RE_CONDITIONAL中的无名捕获分组’(a)‘不匹配,然后然后test_str1中的’b’ 与 RE_CONDITIONAL中的’b’匹配,接下来 正则引擎进入 分组条件判断,因为分组(1)不存在,所以no-pattern被执行,test_str1的’d’与no-pattern中的’d’匹配,至此,匹配结束,匹配结果为bd。

ok,(?(id/name)yes-pattern|no-pattern) 就介绍到这里。

条件正则的使用 非常灵活,如果使用地恰当,能起到事半功倍的效果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

sif_666

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

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

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

打赏作者

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

抵扣说明:

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

余额充值