Python中的转义字符
不少初学者在学习Python的过程中,特别是学到了正则表达式的时候,都为转义字符而迷惑。当遇到以下这些样例的时候,很容易就解释不清楚它的原理:
>>> '\\ ' == '\ '
True
>>> import re
>>> re.search('\\ ', '\\ ')
<re.Match object; span=(1, 2), match=' '>
>>> re.search('\n', '\n')
<re.Match object; span=(0, 1), match='\n'>
>>> re.search('\\n', '\n')
<re.Match object; span=(0, 1), match='\n'>
>>> re.search('\\\A', '\A')
<re.Match object; span=(0, 2), match='\\A'>
>>> re.search('\\\\\\\\\\ ', '\\\ ')
<re.Match object; span=(0, 3), match='\\\\ '>
下面我们来集中了解一下Python中的转义字符。
原生转义
Python中自带的转义字符有:
- 反斜杠:
\\
- 单引号:
\'
- 双引号:
\"
- ASCII码中的特殊字符:
- 响铃:
\a
- 退格键:
\b
- 换页符:
\f
- 换行符:
\n
- 回车键:
\r
- 水平制表符:
\t
- 垂直制表符:
\v
- 响铃:
- 八进制编码字符:
\o..
- 十六进制编码字符:
\x..
除此之外就没有了。
当我们在Python中通过直接创建的方式(也就是手动输入一对引号,中间填字符)构造出一个字符串对象,它会默认按照上述规则进行转义(从左至右遍历,若能转义的,则转义,否则不转义)。比如,'\\n'
会转义为一个反斜杠和一个n
;'\A'
则是一个反斜杠和一个A
。
需要补充的一点是,强制不转义是在构造过程中处理的,而不是在一开始。因而我们就能解释,为什么无论是'\'
还是r'\'
都是不合法的,因为Python都会优先将\
和其后的'
理解成转义字符。
当我们要将一个字符串对象显示出来的时候(这里是“显示”,而不是“打印”),Python会试图显示一个转义前的字符串版本。这个“转义前”不代表就是它被构造时传入的字符串,也就是说,一个\
和一个n
会显示成'\\n'
,一个\
和一个A
会显示成'\\A'
,尽管它可能是由'\A'
构造出的。
当我们要把字符串打印出来的时候(也就是调用print
函数),Python会显示最简洁的版本,也就是转义后的版本。如,print('\n')
会直接换两行(包括print
函数自带的一行),而不是显示'\\n'
。
以上就是Python中原生的转义字符。我们也可以说字符串在Python中有三种可见的形态:构造传入的、显示的、打印的。
我们由此可以解释第一个样例:'\\ ' == '\ '
。因为前一个构造以后是一个\
和一个空格,后一个也是,所以两者相等。
正则模块中的转义字符
在Python的re
模块中,为了增强字符串匹配时的工作效率,除了包含原生的转义字符外,又引入了更多的转义字符,它们有:
'\number'
,表示组号为number
的组。'\A'
,匹配字符串的开头,相当于'^'
。'\Z'
,匹配字符串的末尾。'\b'
,匹配单词开头或结尾处的空串。'\B'
,匹配非单词开头或结尾处的空串。'\d'
,匹配数字,相当于'[0-9]'
。'\D'
,匹配非数字,相当于'[^\d]'
。'\s'
,匹配空白字符,相当于'[ \t\n\r\f\v]'
。'\w'
,匹配单词字符,相当于'[a-zA-Z0-9_]'
。'\W'
,匹配非单词字符,相当于'[^\w]'
。
同时我们又规定,跟在\
后面的字符统统被转义,当那个字符没有对应的转义规则的时候,转义后的字符就表示那个字符本身,比如'\A'
会被转义为A
。
当我们构造一个Pattern
对象的时候(通常使用re.compile
方法,或者调用re.findall
等函数时传入字符串自动构造),它会先按照原生转义的规则进行转义(相当于先构造出一个str
对象出来,然后传给re.compile
),然后再进行re
模块的二次转义(这一过程中也包含原生的转义规则,知道一点很重要),得到最终的Pattern
对象。两个转义过程都是从左至右进行的。
我们来解释一下一开始的几个样例:
>>> re.search('\\ ', '\\ ')
<re.Match object; span=(1, 2), match=' '>
对于re.search
函数,前一个是pattern
,所以'\\ '
先经过原生的转义,得到一个\
和一个空格(这还只是一个str
对象而不是Pattern
对象),然后再经过模块的转义变成一个空格;后一个只经过原生转义,变成一个\
和一个空格。因此最终匹配的只是空格。
>>> re.search('\n', '\n')
<re.Match object; span=(0, 1), match='\n'>
前一个'\n'
先经过原生转义变成单个换行符,然后经过模块转义后不变;后面的'\n'
只经过原生转义,变为单个换行符。因此匹配整个。
>>> re.search('\\n', '\n')
<re.Match object; span=(0, 1), match='\n'>
'\\n'
经过原生转义得到\n
(以下我直接表示的不带引号的字符串,都应理解为print
出来的字符串),再经过模块转义得到单个换行符;'\n'
经过原生转义得到单个换行符。因此匹配整个。
>>> re.search('\\\A', '\A')
<re.Match object; span=(0, 2), match='\\A'>
'\\\A'
变成\\A
,然后再变成\A
;'\A'
变成\A
。因此匹配整个。
>>> re.search('\\\\\\\\\\ ', '\\\ ')
<re.Match object; span=(0, 3), match='\\\\ '>
'\\\\\\\\\\ '
先变成\\\\\_
(这里的下划线表示空格),然后变成\\_
;'\\\ '
变成\\_
。因此匹配整个。
总结
Python中的转义字符为什么很容易引起初学者的疑惑,归根结底在于正则表达式中的转义和原生的转义是一个包含但又有区别的关系。只要我们严格按照其转义规则来分析,看懂是很简单的。