老齐教室

Python中的正则表达式(四)

作者:老齐

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


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

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

前面已经对Python中正则表达的基本内容做了比较完整的讲述,从本文开始,将进入高级应用部分。

分组和捕获

分组,就是将一个正则表达式分成若干个子表达式。分组包括两个操作:

  • 分组:即利用前面已经介绍过的元字符,在圆括号中写出一个一个的正则表达式(子表达式)
  • 捕获:有的分组,可以按照子表达式匹配字符串。

分组符号

通常,用( )表示一个分组,在其中写子表达式。

1
2
3
4
5
>>> re.search('(bar)', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>

>>> re.search('bar', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>

在这个示例中,(bar)是一个分组的正则表达式,即一个子表达式,但是,这里因为只有一组,所以与不分组的bar效果是一样。

一个分组作为一个单元

如果在一个分组的子表达式后面跟一个量词,那么就是把这个分组作为一个单元。

例如,(bar)+,意味着将字符串bar看做一个单元,即要匹配至少1个bar

1
2
3
4
5
6
>>> re.search('(bar)+', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('(bar)+', 'foo barbar baz')
<_sre.SRE_Match object; span=(4, 10), match='barbar'>
>>> re.search('(bar)+', 'foo barbarbarbar baz')
<_sre.SRE_Match object; span=(4, 16), match='barbarbarbar'>

下面用表格的方式,比较bar+(bar)+的区别:

正则表达式 说明 举例
bar+ +作用在字符r,匹配的字符串要求在ba的后面可以有1个或更多个r 'bar', 'barr', 'barrr'
(bar)+ +作用在bar,匹配的字符串中药出现bar 'bar', 'barbar', 'barbarbar'

下面再看个复杂的示例。

1
2
3
4
>>> re.search('(ba[rz]){2,4}(qux)?', 'bazbarbazqux')
<_sre.SRE_Match object; span=(0, 12), match='bazbarbazqux'>
>>> re.search('(ba[rz]){2,4}(qux)?', 'barbar')
<_sre.SRE_Match object; span=(0, 6), match='barbar'>

正则表达式(ba[rz]){2,4}(qux)?中,(ba[rz])是一个分组,表示要匹配barbaz(ba[rz]){2,4}则表示匹配的数量范围(2到4个),后面的(qux)?又将qux作为一个单元,匹配0个或1个。

在上述示例基础上,进一步理解:

1
2
3
4
5
6
>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoobar')
<_sre.SRE_Match object; span=(0, 9), match='foofoobar'>
>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoobar123')
<_sre.SRE_Match object; span=(0, 12), match='foofoobar123'>
>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoo123')
<_sre.SRE_Match object; span=(0, 9), match='foofoo123'>

下面用表格的方式,将上面示例中的几个表达式进行说明。

正则表达式 说明
foo(bar)? 匹配foo后面跟着0个或1个bar
(foo(bar)?)+ 按照上面规则匹配的字符串出现1次或多次
\d\d\d 匹配三个整数字符
(\d\d\d)? 0个或1个符合上述规则的字符串

将上表中的组合在一起,就是(foo(bar)?)+(\d\d\d)?正则表达式。

捕获

分组的目的之一是搜索字符串,根据分组原则捕获相应部分。

re模块中,有re.search(),可以返回搜索到的匹配对象,针对分组操作,此对象有.groups.group两个方法。

m.groups()

m.groups()根据正则表达式,返回所有分组所捕获的字符串。

1
2
3
>>> m = re.search('(\w+),(\w+),(\w+)', 'foo,quux,baz')
>>> m
<_sre.SRE_Match object; span=(0, 12), match='foo:quux:baz'>

在此示例中,正则表达式(\w+),(\w+),(\w+)包含三个组,每组都是要匹配至少1个字母、数字,即要从foo,quux,baz中得到三个字符串fooquuxbaz,不包含原字符串中的,

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

执行m.groups(),返回结果为一个元组,其中包括所捕获的三个字符串。

m.group(n)

m.groups()返回的元组,包含了所有捕获的内容。但在操作中,可能需要返回某个分组,此时使用m.group(n)方法实现。

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

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

m.group(1)中的参数是1,表示捕获第一个分组的结果——注意,不是从0开始。如果参数为0,会是下面的结果:

1
2
3
4
>>> m.group(0)
'foo,quux,baz'
>>> m.group() # 不写参数,即默认参数是0
'foo,quux,baz'

m.group()中的参数,还可以传入多个,如下所示:

1
2
3
4
5
6
7
>>> m.groups()
('foo', 'quux', 'baz')

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

此时,能够按照参数的顺序和数值得到多个指定分组捕获,特别注意观察m.group(3, 2, 1)的结果。

向后引用

“向后引用”这个术语的英文是“backference”,很多中文资料翻译为“反向引用”,在本文中,我使用“向后引用”这个术语,原因在于这个翻译比较直白地反应了相关的效果。

所谓“向后引用”就是将前面的分组所捕获的结果向后再复制n个,比如:(\w+), \1,第一个分组(\w+),后面的\1表示将前面的分组捕获的结果在后面再次依样捕获1个。

1
2
3
4
5
6
7
>>> regex = r'(\w+),\1'

>>> m = re.search(regex, 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group(1)
'foo'

在这个示例中,分组(\w+)已经捕获了字符串foo,在正则表达式中的\1表示向后引用前面的捕获结果,即可以继续在字符串中搜索,能够要再捕获一个foo。再如:

1
2
3
4
5
>>> m = re.search(regex, 'qux,qux')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='qux,qux'>
>>> m.group(1)
'qux'

如果搜索的字符串是foo,qux,由于捕获第一个foo之后,继续在字符串中搜索,无法得到foo了,所以,会搜索失败。

1
2
3
>>> m = re.search(regex, 'foo,qux')
>>> print(m)
None

(未完,待续)

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

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

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

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

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

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