老齐教室

Python中的正则表达式(二)

作者:老齐

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


在上一篇(《Python正则表达式(一)》)中,已经介绍了正则表达式的基本含义,并且对re模块中的元字符[ ]进行了说明,本文接续上文,介绍有关元字符。

re模块的元字符

点(.

.(注意必须是英文状态下的)是一个通配符,即表示了任何符号(换行符除外)。

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

>>> print(re.search('foo.bar', 'foobar'))
None
>>> print(re.search('foo.bar', 'foo\nbar'))
None

在上面示例中,正则表达式foo.bar表示的匹配规则是:先是三个字符foo,然后用通配符.说明第四个字符可以是除了换行符之外的任何字符,第五个及其后的字符是bar。显然,fooxbar符合这个规则,而foobarfoo\nbar不符合,前者foo之后是b,但b之后是ar而不是bar;后者中间是一个换行符\n

\w\W

\w匹配全部由字母和数字组成的字符串,即大写、小写字母以及0到9的数字,注意,也包括下划线。通常,也可以用[a-zA-Z0-9]来替代它。

1
2
3
4
>>> re.search('\w', '#(.a$@&')
<_sre.SRE_Match object; span=(3, 4), match='a'>
>>> re.search('[a-zA-Z0-9_]', '#(.a$@&')
<_sre.SRE_Match object; span=(3, 4), match='a'>

显然,字符串'#(.a$@&'中只有一个字母,上面演示中也都显示,匹配了字母a

如果是\W(大写),则意味着与\w相反,不包括字母、数字和下划线。

1
2
3
4
>>> re.search('\W', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>
>>> re.search('[^a-zA-Z0-9_]', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>

字符串'a_1*3Qb'中的*不属于字母、数字、下划线集合,在上述示例中匹配到了。注意,[^a-zA-Z0-9_]中以^开头,表示对后面字符集合的补集(取反)。

\d\D

\d匹配由数字组成的字符,与[0-9]相当。\D同样是\d取反,即[^0-9]

1
2
3
4
5
>>> re.search('\d', 'abc4def')
<_sre.SRE_Match object; span=(3, 4), match='4'>

>>> re.search('\D', '234Q678')
<_sre.SRE_Match object; span=(3, 4), match='Q'>

\s\S

\s匹配任何空白字符,包括空格、制表符、换行符等等。等价于 [\f\n\r\t\v]

1
2
>>> re.search('\s', 'foo\nbar baz')
<_sre.SRE_Match object; span=(3, 4), match='\n'>

与前面一样,\S也是对\s取反,不匹配任何空白字符。

1
2
>>> re.search('\S', '  \n foo  \n  ')
<_sre.SRE_Match object; span=(4, 5), match='f'>

上面几个元字符,也可以写到一个集合中。

1
2
3
4
5
6
>>> re.search('[\d\w\s]', '---3---')
<_sre.SRE_Match object; span=(3, 4), match='3'>
>>> re.search('[\d\w\s]', '---a---')
<_sre.SRE_Match object; span=(3, 4), match='a'>
>>> re.search('[\d\w\s]', '--- ---')
<_sre.SRE_Match object; span=(3, 4), match=' '>

[\d\w\s]的含义就是匹配任何数字、字母和空白字符,-则不在匹配之列。

转义符

跟Python中的字符串中规定一样,在正则表达式中,也用\表示对后面的字符转移。

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

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

第一个示例中的.,表示的是通配符,即任何字符。因此匹配了后面字符串中的第一个f。第二个示例中的\.,因为使用了转移符,它表示要匹配一个英文的句点,不再是通配符了,所以最终匹配了后面字符串中的句点符号。

使用\,需要特别小心。

1
2
3
4
5
>>> s = r'foo\bar'
>>> print(s)
foo\bar
>>> s
'foo\\bar'

这里创立了原始字符串s,注意,如果单单看print(s)的结果,容易产生误解。因为这里使用了原始字符串r'foo\bar,其中的\就表示了原本的反斜杠符号,而不是转义符。当执行s时,会看到,Python解析器会将其解析为'foo\\bar',也就是这种方式的字符串和前面定义的原始字符串是一样的,或者说前面定义原始字符串,在Python使用它的时候,会被解析为'foo\\bar',这就是我们要注意的地方。

下面要匹配s里面的\,按照通常方法,这样来试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> re.search('\\', s)
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
re.search('\\', s)
File "C:\Python36\lib\re.py", line 182, in search
return _compile(pattern, flags).search(string)
File "C:\Python36\lib\re.py", line 301, in _compile
p = sre_compile.compile(pattern, flags)
File "C:\Python36\lib\sre_compile.py", line 562, in compile
p = sre_parse.parse(p, flags)
File "C:\Python36\lib\sre_parse.py", line 848, in parse
source = Tokenizer(str)
File "C:\Python36\lib\sre_parse.py", line 231, in __init__
self.__next()
File "C:\Python36\lib\sre_parse.py", line 245, in __next
self.string, len(self.string) - 1) from None
sre_constants.error: bad escape (end of pattern) at position 0

出问题了,原因就在于Python解析器实际使用的是'foo\\bar',那么,这里就有了两个反斜杠,第一个反斜杠表示的是“转移符”,并把这个符号传给了re.search(),正则表达式收到了单个反斜杠,但这不是有意义字符,因此会出现混乱,导致了错误。

下面演示两种解决方式:

1
2
>>> re.search('\\\\', s)
<_sre.SRE_Match object; span=(3, 4), match='\\'>

或者:

1
2
>>> re.search(r'\\', s)
<_sre.SRE_Match object; span=(3, 4), match='\\'>

定位字符

定位字符是零宽度匹配,表示定位的符号不匹配字符串中的任何实际字符,并且不会使用任何搜索字符串。定位字符指示搜索字符串中必须发生匹配的特定位置。

^\A

^\A表示匹配输入字符串的开始位置。但是,当它们在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合,即补集或相反。如果要匹配^字符本身,必须要用\^

1
2
3
4
>>> re.search('^foo', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('^foo', 'barfoo'))
None

正则表达式^foo表示要匹配以foo开头的字符串,'foobar'符合规则,'barfoo'不符合规则。

\A的作用与^一样。

1
2
3
4
>>> re.search('\Afoo', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('\Afoo', 'barfoo'))
None

$\Z

与前述^相对应,$表示在字符串的结尾部分搜索相应匹配。

1
2
3
4
5
6
7
8
9
>>> re.search('bar$', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> print(re.search('bar$', 'barfoo'))
None

>>> re.search('bar\Z', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> print(re.search('bar\Z', 'barfoo'))
None

上面的示例中,bar$表示搜索字符串结尾是barfoobar符合此正则表达式的规则,barfoo则不符合。在这里,\Z$的效果一样。

但是,如果在下面的示例中,就只能使用$

1
2
>>> re.search('bar$', 'foobar\n')
<_sre.SRE_Match object; span=(3, 6), match='bar'>

\b

\b匹配一个单词(也包括中文字符)的边界,即单词的分界。

例如这样一句话:Laoqi Classroom,其中每个字符都有一个位置,包括空白(空格)也如此。如果某个字符后面的字符不是字母、数字和下划线,即不全是\w所匹配的字符,那么\b就会匹配后面的字符的后面(但不是下一个字符)。例如这句话中i后面是空格,不在\w范围内,那么\b就会匹配空格后面、C前面的位置。

例如:

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

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

字符串foo bar中有空格,正则表达式r'\bbar'就匹配这个字符后面的位置,并且此位置之后是bar。同理foo.bar亦然。

但是:

1
2
>>> print(re.search(r'\bbar', 'foobar'))
None

则不然。因为foobar本身没有\b要匹配的位置。

1
2
3
4
5
6
7
8
>>> re.search(r'foo\b', 'foo bar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>

>>> re.search(r'foo\b', 'foo.bar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>

>>> print(re.search(r'foo\b', 'foobar'))
None

按照前述解释,上面示例不难理解。读者可以自己尝试梳理一番。

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

>>> print(re.search(r'\bbar\b', 'foobarbaz'))
None

这几个示例中,则出现了两个\b,其含义是要匹配在bar两侧都能找到“位置”的字符串。

另外,\B则是对\b的取反。

1
2
3
4
5
6
7
8
>>> print(re.search(r'\Bfoo\B', 'foo'))
None

>>> print(re.search(r'\Bfoo\B', '.foo.'))
None

>>> re.search(r'\Bfoo\B', 'barfoobaz')
<_sre.SRE_Match object; span=(3, 6), match='foo'>

(未完,待续)

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

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

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

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

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

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