正则表达式是一个十分强大的工具,可以完成对字符串的模式匹配以及提取工作,在很多工程中都被广泛使用,但是我之前对于Python正则表达式模块re的使用都没有进行一些深入学习,对原始字符串之类的了解也比较浅显,之前有个同学问到我关于反斜线\在正则中的一些表现的时候我也迷惑了,跟周围人讨论后上StackOverflow问了一番,对这方面又多了一些了解,今天跟大家分享。

字符串中的反斜线\

我们知道,反斜线\在字符串以及正则表达式中都是完成转义的工作,比如我有一个字符串s1:

1
>>> s1 = '123'

现在这个字符串s1内容为123,如果我想在字符串中保留一个单引号',那么我可能的一种做法是使用双引号来包含内容,字符串s2:

1
>>> s2 = "12'3"

Python中的单引号'和双引号'其实都可以用来指示字符串,我们在此处使用双引号而不是单引号,原因是因为如果使用单引号Python解释器将会无法对这条语句进行正常解释,因为紧跟2之后的'被用来指示字符串结束,然而后面还剩下3这个字符,所以会引发Syntax Error:

1
2
3
4
5
>>> s = '12'3'
File "<stdin>", line 1
s = '12'3'
^
SyntaxError: invalid syntax

但是,只是使用语句中不包含的那种引号来指示字符串有时候是行不通的,因为有时候一个字符串可能同时包含单引号和双引号,比如字符串1"2'3,所以这时候我们需要使用反斜线\来对内容进行转义,因此对于字符串1"2'3,我们可以采用如下表示:

1
2
3
>>> s = '1"2\'3'
>>> s
'1"2\'3'

注意我们并没有对字符串内容中的双引号"进行转义,原因同上,字符串使用单引号指示的,因此双引号只会被认为是一个普通的字符。对于字符串内部的单引号',为了让解释器得知它是一个普通字符,我们在其前面增加了一个反斜线\\'组合起来的意思是对反斜线后面的字符'进行转义,可以这样理解,通过添加一个反斜线\,对于\',你告诉了解释器:不要按照普通的方式去解释这个字符',这样解释器就能正确地完成对该字符串的解释。

有编程基础的读者应该会知道转义这一概念广泛存在于很多编程语言中,对\n\r等字符应该有一定了解,类比前面的解释,\n表示:不要按照普通的方式去解释这个字符n

进一步理解,假设我们从键盘输入了字符串:abc\nde,一共输入了7个字符,这7个字符经过Python解释器解释,两个字符\n将会被转义成一个字符\n,请特别注意这一点,虽然从表面上看是两个字符,但其实\n是一个特殊的字符,这一点我们可以从以下代码看出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> s = 'abc\nde'
>>> len(s)
6
>>> s[0]
'a'
>>> s[1]
'b'
>>> s[2]
'c'
>>> s[3]
'\n'
>>> s[4]
'd'
>>> s[5]
'e'
>>> s[6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range

有了这些基础,下面我们进入到正则的部分。

正则表达式中的反斜线\

因为有了Python解释器首先对字符串的转义,所以正则表达式模块re看到的和你输入的可能有所不同,比如,\n在字符串层被转义成一个换行字符,然后re看到的是字符\n而不是\n两个字符,看以下代码:

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

以上代码首先定义了一个前文提到的字符串s,然后使用re模块的search方法搜索,然后要搜索的字符串是\n,注意我们输入的是字符\和字符n,然后经过Python解释器的解释实际在内存中的是一个\n字符,因此re模块看到的是只有一个字符的字符串\n,而不是两个字符,最后完成搜索。

如果我们想匹配字符串\\\n,应该怎么输入模式呢?,一种方法是:

1
2
3
4
5
6
>>> s = '\\\n'
>>> pat = re.compile('\\\\\n')
>>> pat.pattern
'\\\\\n'
>>> pat.search(s)
<_sre.SRE_Match object; span=(0, 2), match='\\\n'>

对于字符串\\\n,它的内容其实是一个普通的\字符和一个换行字符\n,对于我们输入的匹配模式\\\\\n,首先经过Python解释器的字符串解释,变成了两个普通的\字符和一个换行字符\n,然后这些内容通过re模块的compile方法,compile方法发现有两个连续的\符号,因此将在接下来的匹配中匹配一个普通的\字符(请注意理解这一点),而\n字符将会匹配一个\n字符,因此该模式的匹配内容是字符串中的一个普通\字符和一个\n字符。当然,写这么多反斜线显得十分麻烦,所以Python也提供了一种方便的方式,即原始字符串,其格式为在字符串前添加一个r字符:r'content',通过这样的书写方式,Python解释器将使用不同的规则来解释转义(原始字符串不能以\符号结尾)。因此上述模式可以简化为:

1
2
3
4
5
6
>>> s = '\\\n'
>>> pat = re.compile(r'\\\n')
>>> pat.pattern
'\\\\\\n'
>>> pat.search(s)
<_sre.SRE_Match object; span=(0, 2), match='\\\n'>

让我们再回到普通字符串,考虑以下代码:

1
2
3
4
5
6
>>> s = '\\\n'
>>> pat = re.compile('\\\n')
>>> pat.pattern
'\\\n'
>>> pat.search(s)
<_sre.SRE_Match object; span=(1, 2), match='\n'>

对于模式\\\n,经过Python解释器的解释将变成一个普通’`字符和一个换行字符\nre模块的compile方法将会看到以上解释结果,然后该模式组合起来就是:匹配一个换行字符\n的转义字符,也就是对换行字符\n进行转义!结果是:

对一个转义字符进行转义将会得到其本身

相关连接: