老齐教室

Python中的正则表达式(五)

Python中的正则表达式(五)

作者:老齐

与本文相关的图书推荐:《跟老齐学Python:轻松入门》


《Python正则表达式》这个系列,已经完成了四篇,本文是第五篇,请继续阅读。

如果错过了前几篇,请关注微信公众号:老齐教室

其他分组

分组的形式多种多样,以上简要介绍了几种最基本的,在上述内容基础上,可以进一步探讨其他分组形式。

(?P<name><regex>)

在前面的操作中,如果有多个正则表达式分组,可以用从1开始(注意不是从0开始)的需要,获得相应分组捕获的对象。例如:

1
2
3
4
5
6
>>> m = re.search('(\w+),(\w+),(\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')

>>> m.group(1, 2, 3)
('foo', 'quux', 'baz')

此外,为了意义更明确,我们也可以用(?P<name><regex>)方式,给每个分组命名,之后通过命名得到每组捕获的对象。

1
2
3
>>> m = re.search('(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')

以上为每个分组分别命名为w1w2w3m.groups()的执行结果没有变化。重点看下面操作:

1
2
3
4
5
6
>>> m.group('w1')
'foo'
>>> m.group('w3')
'baz'
>>> m.group('w1', 'w2', 'w3')
('foo', 'quux', 'baz')

这样,通过分组的名称,得到了相应分组捕获的对象。

有了分组名称的命名之后,原有序号依然有效,你可以混合使用。

1
2
3
4
5
6
7
8
9
10
11
>>> m = re.search('(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'foo,quux,baz')

>>> m.group('w1')
'foo'
>>> m.group(1)
'foo'

>>> m.group('w1', 'w2', 'w3')
('foo', 'quux', 'baz')
>>> m.group(1, 2, 3)
('foo', 'quux', 'baz')

(?P=<name>)

(?P=<name>)匹配向后引用的字符串,类似\<n>,但是这里给出了名称。例如:

1
2
3
4
5
>>> m = re.search(r'(\w+),\1', 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group(1)
'foo'

这是前面已经熟悉的\<n>模式,如果按照序号,可以捕获向后引用所对应的字符。

同样的操作,下面对分组命名,然后用(?P=<name>)模式向后引用。

1
2
3
4
5
>>> m = re.search(r'(?P<word>\w+),(?P=word)', 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group('word')
'foo'

(?P=<word>\w+)匹配字符串'foo',并将它保存为word这个命名的捕获,然后,逗号后面表示的向后引用(?P=word),再次匹配和捕获一个字符串'foo'

注意在向后引用的那部分分组命名的写法,不要在名称外面用尖括号包裹。

1
2
3
4
5
6
>>> m = re.match(r'(?P<num>\d+)\.(?P=num)', '135.135')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='135.135'>

>>> m.group('num')
'135'

(?:<regex>)

(?:<regex>)(<regex>)类似,都是在<regex>中指定匹配的正则表达式,但是(?:<regex>)不会捕获所匹配的字符,以后也无法检索到。

1
2
3
4
5
6
7
8
>>> m = re.search('(\w+),(?:\w+),(\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'baz')

>>> m.group(1)
'foo'
>>> m.group(2)
'baz'

在上面的示例中,中间的字符串quux就没有被捕获,与它对应的就会前面正表达式中的(?:\w+)

根据条件匹配

根据条件匹配的方式有两种:

  • (?(<n>)<yes-regex>|<no-regex>):如果存在<n>,则匹配<yes-regex>,否则,它将匹配<no-regex>
  • (?(<name>)<yes-regex>|<no-regex>):如果存在<name>,则匹配<yes-regex>,否则,它将匹配<no-regex>

例如:

1
regex = r'^(###)?foo(?(1)bar|baz)'

这个正则表达式示例,含义为:

  • ^(###)?表示要匹配以###开头的字符串,如果找到,就根据###的分组括号创建编号为1的组。否则,不存在改组。
  • 后面的foo,表示匹配字符串中的foo
  • 最后,(?(1)bar|baz),如果组1存在,就匹配bar,否则baz

将上面的正则表达式用在下面的示例中。

1
2
>>> re.search(regex, '###foobar')
<_sre.SRE_Match object; span=(0, 9), match='###foobar'>

###foobar是以###开头,因此创建组1,然后匹配bar,字符串中也有此匹配对象,最后返回匹配结果。

1
2
>>> print(re.search(regex, '###foobaz'))
None

###foobar是以###开头,因此创建组1,然后匹配bar,但是,字符串中后面是baz,没有匹配成功,最后返回None

1
2
>>> print(re.search(regex, 'foobar'))
None

foobar不是###开头,没有创建组1,根据条件,就要匹配baz,但字符串中是bar,所以返回None

1
2
>>> re.search(regex, 'foobaz')
<_sre.SRE_Match object; span=(0, 6), match='foobaz'>

foobar不是###开头,没有创建组1,根据条件,就要匹配baz,字符串中后面恰好是bar

下面的正则表达式,与上面不同之处在于,对所创建的组进行了命名。

1
>>> regex = r'^(?P<ch>\W)?foo(?(ch)(?P=ch)|)$'

将这个正则表达式分解,并说明其含义:

  • ^:字符串的开始
  • (?P<ch>\W):匹配一个非字母字符,并将改组捕获对象命名为ch
  • (?P<ch>\W)?:以上情况,匹配0个或1个。
  • foo:匹配字符串foo
  • (?(ch)(?P=ch)|):如果ch的组存在,匹配的内容和ch组一样,否则为空。
  • $:字符串的结尾

如果非字母字符位于foo之前,则解析器创建一个名为ch的组,其中包含该字符。然后,条件匹配匹配<yes-regex>,它是(?P=ch),还是同样的字符。这意味着相同的字符也必须跟在foo后面,这样整个匹配才会成功。

如果foo前面没有非字母字符,那么解析器就不会创建ch组,<no-regex>是空字符串,这意味着在foo后面必须没有任何内容,整个匹配才会成功。因为^$锚定整个正则表达式,所以字符串必须恰好等于foo

通过下面示例,进一步演示上述正则表达式的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> re.search(regex, 'foo')
<_sre.SRE_Match object; span=(0, 3), match='foo'>

>>> re.search(regex, '#foo#')
<_sre.SRE_Match object; span=(0, 5), match='#foo#'>

>>> re.search(regex, '@foo@')
<_sre.SRE_Match object; span=(0, 5), match='@foo@'>

>>> print(re.search(regex, '#foo'))
None

>>> print(re.search(regex, 'foo@'))
None

>>> print(re.search(regex, '#foo@'))
None

>>> print(re.search(regex, '@foo#'))
None

比照前面的解释,理解上述操作结果。

Python中条件正则表达式有点深奥和具有挑战性的,替代它的一个方法,就是使用多个单独的re.search()调用来实现相同的目标,这样代码就不会那么复杂了。

(未完,待续)

参考资料:https://realpython.com/regex-python/

搜索技术问答的公众号:老齐教室

在公众号中回复:老齐,可查看所有文章、书籍、课程。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

关注微信公众号,读文章、听课程,提升技能