老齐教室

Python中的正则表达式(三)

作者:老齐

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


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

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

量词元字符

量词元字符,在正则表达式中表示匹配若干个字符,可能是0个、1个或者多个。

*

* 在正则表达式中表示匹配0个或者多个字符,例如a*,就表示匹配0个或者更多个字符a,例如可以匹配空字符串、aaaa等等。

1
2
3
4
5
6
7
8
>>> re.search('foo-*bar', 'foobar')                     # 匹配0个横线
<_sre.SRE_Match object; span=(0, 6), match='foobar'>

>>> re.search('foo-*bar', 'foo-bar') # 匹配1个
<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>

>>> re.search('foo-*bar', 'foo--bar') # 匹配2个
<_sre.SRE_Match object; span=(0, 8), match='foo--bar'>

上面示例中的正则表达式foo-*bar,意思是在foo三个字符之后,匹配0个或者更多个-,然后是三个字符barfoobarfoo之后没有-,即0个,并且最后三个字符是bar,符合正则表达式的规则;foo--bar则是匹配了两个-字符。

前面已经介绍过.,表示任何字符(除了换行符),如果和*组合,即.*表示0个或者任意多个任何字符(除了换行符),换句话说,匹配字符串中任何字符,直到改行结束(遇到换行符)。

1
2
>>> re.search('foo.*bar', '# foo $qux@grault % bar #')
<_sre.SRE_Match object; span=(2, 23), match='foo $qux@grault % bar'>

对于字符串'# foo $qux@grault % bar #',按照正则表达式foo.*bar进行匹配,从第索引为2的字符开始,符合正则表达式的规则,直到索引为23的字符为止,即匹配了f(含)和r(含)之间的所有字符。特别注意观察返回结果中spanmatch的值。

+

+ 与上面的*类似,但是,它匹配的是1个或多个字符,即至少要有一个。

1
2
3
4
5
6
7
8
>>> print(re.search('foo-+bar', 'foobar'))              # 0个横线,不匹配
None

>>> re.search('foo-+bar', 'foo-bar') # 1个,匹配
<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>

>>> re.search('foo-+bar', 'foo--bar') # 2个,匹配
<_sre.SRE_Match object; span=(0, 8), match='foo--bar'>

通过上面演示的示例,比较*+的差异。

?

前面已经有匹配“0个或多个”和“1个或多个”的元字符了,现在应该有匹配0个或1个的了,那就是?

1
2
3
4
5
6
7
8
>>> re.search('foo-?bar', 'foobar')                     # 0个,匹配
<_sre.SRE_Match object; span=(0, 6), match='foobar'>

>>> re.search('foo-?bar', 'foo-bar') # 1个,匹配
<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>

>>> print(re.search('foo-?bar', 'foo--bar')) # 2个,不匹配
None

下面的示例,将前面三个云字符进行比较,进一步理解它们的含义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> re.match('foo[1-9]*bar', 'foobar')
<_sre.SRE_Match object; span=(0, 6), match='foobar'>
>>> re.match('foo[1-9]*bar', 'foo42bar')
<_sre.SRE_Match object; span=(0, 8), match='foo42bar'>

>>> print(re.match('foo[1-9]+bar', 'foobar'))
None
>>> re.match('foo[1-9]+bar', 'foo42bar')
<_sre.SRE_Match object; span=(0, 8), match='foo42bar'>

>>> re.match('foo[1-9]?bar', 'foobar')
<_sre.SRE_Match object; span=(0, 6), match='foobar'>
>>> print(re.match('foo[1-9]?bar', 'foo42bar'))
None
  • foo[1-9]*bar,匹配foobar之间有1~9的数字0个或多个。
  • foo[1-9]+bar,匹配foobar之间有1~9的数字1个或多个。
  • foo[1-9]?bar,匹配foobar之间有1~9的数字0个或1个。

以上三种量词元字符,还经常组合使用,例如:*?+???等。

1
2
>>> re.search('<.*>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 18), match='<foo> <bar> <baz>'>

上面示例中的<.*>表示要匹配<字符之后的任何字符(除了换行字符),然后是>。按照这个规则,匹配了'%<foo> <bar> <baz>%'中的<foo> <bar> <baz>,但是,如果只想匹配<foo>怎么办?

1
2
>>> re.search('<.*?>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 6), match='<foo>'>

上面使用了*?,这种表示可以称为惰性匹配,也将此组合称为惰性量词,对应着,前面提到的*等则称为贪婪量词,对应的匹配是贪婪匹配。下面以表格形式列出它们之间的比较(顺便也增加了另外一种“支配量词”,一并列出进行比较)。

贪婪量词 惰性量词 支配量词 描述
* *? *+ 可以不出现,也可以出现任意次
? ?? ?+ 可以出现0次或1次
+ +? ++ 至少出现1次或以上
{n} {n}? {n}+ 有且只能出现n次
{n,m} {n,m}? {n,m}+ 至少出现n次,至多出现m次
{n,} {n,}? {n,}+ 至少出现n次或以上
  • 贪婪匹配:如果符合要求就一直往后匹配,一直到无法匹配为止。
  • 惰性匹配:一旦匹配到合适的就结束,不在继续匹配下去了。
  • 支配匹配:只尝试匹配整个字符串。如果整个字符串不匹配,则不做进一步。

结合线面示例,理解上述匹配模式。

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

>>> re.search('<.+?>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 6), match='<foo>'>

>>> re.search('ba?', 'baaaa')
<_sre.SRE_Match object; span=(0, 2), match='ba'>
>>> re.search('ba??', 'baaaa')
<_sre.SRE_Match object; span=(0, 1), match='b'>

{m}

{m}表示匹配m个字符。

1
2
3
4
5
6
7
8
>>> print(re.search('x-{3}x', 'x--x'))                # 2个横线,不匹配
None

>>> re.search('x-{3}x', 'x---x') # 3个横线,匹配
<_sre.SRE_Match object; span=(0, 5), match='x---x'>

>>> print(re.search('x-{3}x', 'x----x')) # 4个横线,不匹配
None

x-{3}x表示要匹配x字符后面有3个-,再接x的字符串。

{m,n}

{m,n}表示匹配大于等于m,小于等于n个字符,例如-{2,4}可以匹配2个、3个、4个-

1
2
3
4
5
6
7
8
9
>>> for i in range(1, 6):
... s = f"x{'-' * i}x"
... print(f'{i} {s:10}', re.search('x-{2,4}x', s))
...
1 x-x None
2 x--x <_sre.SRE_Match object; span=(0, 4), match='x--x'>
3 x---x <_sre.SRE_Match object; span=(0, 5), match='x---x'>
4 x----x <_sre.SRE_Match object; span=(0, 6), match='x----x'>
5 x-----x None

有时候会省略m或者n,其含义分别为:

  • {, n},等效于{0, n}
  • {m, },匹配大于等于m个字符
  • {, },等效{0, }或者*

注意,如果只是{},则表示花括号那个字符了。

1
2
>>> re.search('x{}y', 'x{}y')
<_sre.SRE_Match object; span=(0, 4), match='x{}y'>

{m,n}?

如前面表格所示,这是一种惰性量词,它表示匹配m到n个字符,只要匹配到了,就结束。例如:

1
2
3
4
5
>>> re.search('a{3,5}', 'aaaaaaaa')
<_sre.SRE_Match object; span=(0, 5), match='aaaaa'>

>>> re.search('a{3,5}?', 'aaaaaaaa')
<_sre.SRE_Match object; span=(0, 3), match='aaa'>

a{3,5}是贪婪匹配,要匹配到最大长度,即5个字符a;而a{3,5}?是惰性匹配,只匹配3个字符a即可。

(未完,待续)

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

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

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

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

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

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