上一篇博客《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 withno-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)
就介绍到这里。
条件正则的使用 非常灵活,如果使用地恰当,能起到事半功倍的效果。