两个不起眼但是比较重要的设定
- Python str类型的字面量解释器
当反斜杠及其紧接字符无法构成一个具有特殊含义的序列('recognized escape sequences')时,Python选择保留全部字符.直接看例子:
>>> '\c'
'\\c'
>>> '\d'
'\\d'
官方管'\c'这种序列叫'unrecognized escape sequences'.官方文档相应部分:
Unlike Standard C, all unrecognized escape sequences are left in the string unchanged, i.e., the backslash is left in the string. (This behavior is useful when debugging: if an escape sequence is mistyped, the resulting output is more easily recognized as broken.)
按这段英文的意思,估计C语言里面,'c'和'\c'是等同的.Python是'\\c'和'\c'等同.这个等以后学C语言再确定.
与上面对应的是,如果紧接字符能够和反斜杠构成'recognized escape sequences'的全部或者起始部分,中文就叫'被承认的转义序列'吧.比如:
>>> '\b'
'\x08'
>>> '\n'
'\n'
>>> '\x'
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 0-1: truncated \xXX escape
>>> '\N'
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 0-1: malformed \N character escape
>>> '\U'
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 0-1: truncated \UXXXXXXXX escape
>>> '\u'
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 0-1: truncated \uXXXX escape
- Python re模块正则表达式解释器
当反斜杠及其紧接字符无法构成一个具有特殊含义的序列(special sequences)时,re选择忽略反斜杠,例如:
>>> re.findall('\e','eee')
['e', 'e', 'e']
>>> re.findall('e','eee')
['e', 'e', 'e']
可见,'\e'和'e'起到了完全一样的效果.Python相关文档描述是:
If the ordinary character is not on the list, then the resulting RE will match the second character. For example, \$ matches the character '$'.
与上面对应的是,如果能够构成special sequences,那么re会解释为相应含义.例如:
>>> re.findall('\w','abcdefghijklmnopqrstuvwxyz')
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
字面量
字面量(Literals),是用于表示一些Python内建类型的常量的符号.最常见的字面量类型是str literals 和 bytes literals.
比如:
>>> 'abc'
'abc'
>>> "abc"
'abc'
>>> '啊哦额'
'啊哦额'
>>> b'abc'
b'abc'
>>> r'\n'
'\\n'
>>> b'啊哦额'
SyntaxError: bytes can only contain ASCII literal characters.
反斜杠\的用途按紧接其后的字符种类可划分为3类:
1.将特殊字符转换为字面量.这特殊字符包括(单引号,双引号,反斜杠):'"\
2.将普通字符转换为特殊序列.包括:abfNnrtuUvx0123456789.
(注意,bytes字面量中,NuU这三个普通字符无法被转义成特殊序列)
3.将"新行"和自身忽略掉.这个比较抽象,举例说明:py文件中,某个字符串太长了,以至于需要分两行写,那么你可以插个反斜杠,紧接着换行,然后写剩余字符串.
下面是官方文档归纳的表:
Escape Sequence | Meaning | Notes |
---|---|---|
\newline | Backslash and newline ignored | |
\\ | Backslash (\) | |
\' | Single quote (') | |
\" | Double quote (") | |
\a | ASCII Bell (BEL) | |
\b | ASCII Backspace (BS) | |
\f | ASCII Formfeed (FF) | |
\n | ASCII Linefeed (LF) | |
\r | ASCII Carriage Return (CR) | |
\t | ASCII Horizontal Tab (TAB) | |
\v | ASCII Vertical Tab (VT) | |
\ooo | Character with octal value ooo | (1,3) |
\xhh | Character with hex value hh | (2,3) |
Escape sequences only recognized in string literals are:
Escape Sequence | Meaning | Notes |
---|---|---|
\N{name} | Character named name in the Unicode database | (4) |
\uxxxx | Character with 16-bit hex value xxxx | (5) |
\Uxxxxxxxx | Character with 32-bit hex value xxxxxxxx | (6) |
举例:
>>> '\N{END OF LINE}'
'\n'
>>> '\N{HORIZONTAL TABULATION}'
'\t'
>>> '\u9f6a'=='齪'
True
>>> '\1'=='\01'
True
>>> '\1'=='\001'
True
>>> '\1'=='\0000001'
False
正则
- 正则表达式的反斜杠的作用
一种是使紧跟在后面的元字符(special characters或metacharacters)失去特殊含义,变为字面量.这些元字符有14个:
.^$*+?{}[]()\|
另一种是使紧跟在后面的普通字符变得具有特殊含义.这些普通字符是:
AbBdDsSwWZ0123456789
以及在str字面量中能被反斜杠转义的字符:
\'"abfnrtuUvx0123456789
例如:
>>> re.findall('\"','"')
['"']
正则pattern的反斜杠的作用和Python字面量的反斜杠类似,这据说是带来"反斜杠灾难"的根源.最典型的莫过于你需要用正则'\\\\'才能匹配字面量反斜杠'\\'.
为方便说明,我们假设re.search(pattern,string)中,pattern表示正则表达式字符串,string表示待匹配的字符串.
>>> re.search('\\\\','\\')
<_sre.SRE_Match object at 0x02858528>
详细来说就是一个文本层级的反斜杠'\'(比如你在txt文件中看到的反斜杠),对应Python str 字面量的'\\',对应正则pattern的'\\\\'.这个确实比较难以理解,实在不行就住这点就好:如果不是最简单的正则类型(比如'ab'),强烈推荐对pattern使用r前缀符.这样容易理解:
>>> re.search(r'\\','\\')
<_sre.SRE_Match object at 0x02858448>
注意:
- 1.多重含义的特殊序列处理机制
b0123456789比较特殊,它们在Python字面量和re正则中都能和反斜杠构成作用不同的特殊序列.例如\b,在python 字面量中解释为"退格键".re正则中解释为'单词边界'.python 字面量有优先解释权,如下可证:
>>> re.findall('\b','\b') #'\b'被优先解释为退格键,而不是单词边界
['\x08']
>>> re.findall('\\b','\b')
[]
>>> re.findall('\\b','b')
['', '']
再比如:
>>> re.findall('(a)\1\1','aaa') #\1按字面量优先解释为八进制字符串,因此无匹配结果
[]
>>> re.findall('(a)\\1\\1','aaa') #\\1按正则引擎层级的反斜杠解释为第一个匹配组提取到的字符,相当于'(a)aa'
['a']
>>> re.findall('a\1\1','a\1\1') #\1按字面量优先解释为八进制字符串,所以有匹配结果
['a\x01\x01']
了解这个设置有什么用?
1.当你想使用正则层级的特殊序列\1时,如果你没有使用r作为前缀,那么你必须使用\\1才能如愿.
2.当你想使用字面量层级的特殊序列\1时,则不能使用r作为pattern前缀.
想想,你有可能在一个r前缀的字符串中写出能够匹配值为1的八进制字符串的pattern吗?
也许我太较真了,因为实践中好像从没遇到过需要匹配值为1的八进制字符串的情况,但理论上就是这样的.
- 2.正则表达式中特殊序列的准确定义的猜想
官方文档下面的一句话值得推敲:
Note that \b is used to represent word boundaries, and means “backspace” only inside character classes
意思是说\b只有在[...]里面时才表示退格键,这显然是错的.比如下面这个例子,\b没有在[]之内,但它是按"退格键"解释的,并非"单词边界":
>>> re.findall('\b','\b')
['\x08']
除非官方文档描述的\b是指文本层面的数据(比如你在txt文档里看到的\b).
由此引出了一个猜想,re的正则pattern中"反斜杠+普通字符"构成特殊序列或"反斜杠+特殊字符"构成字面量--这种描述中的反斜杠准确来说是指两个反斜杠!
仍然是举例说明:
>>> re.findall('\\b\w+\\b','one two three') #必须用\\b才能表示单词边界
['one', 'two', 'three']
>>> re.findall('\\b\\w+\\b','one two three') #想想,为什么\w和\\w都一样
['one', 'two', 'three']
>>> re.findall('\d','')
['', '', '']
>>> re.findall('\\d','')
['', '', '']
- 3.u和U只在str字面量中才能被转义,bytes字面量中是普通字符.
以下是我猜测的正则表达式分析器和Python字面量分析器的传递规则表格:
Python string literal | values passed to regular expression | number of characters | what regular expression engine does | real meaning for regular expression |
\e | \e | 2 | ignore the backslash | e |
\\e | \e | 2 | ignore the backslash | e |
e | e | 1 | nothing spacial | e |
\n | \n | 1 | nothing spacial | 换行符 |
\\n | \n | 2 | \n is special | 换行符 |
\b | \b | 1 | nothing spacial | 退格键 |
\\b | \b | 2 | \b is special | word boundary |
\s | \s | 2 | \s is special | Unicode whitespace characters |
\\ | \ | 1 | must followed by a charcter | Can't form any meaning |
\\\\ | \\ | 2 | remove all special meanning of \ | \ |
* | * | 1 | * is special | repeat the left characters 0 or more times |
\* | \* | 2 | remove all special meanning of * | * |
最后是待探究的例子:
>>> re.findall('\n','\n\n')
['\n', '\n']
>>> re.findall('\\n','\n\n')
['\n', '\n']
>>> re.findall('\\\n','\n\n')
['\n', '\n']
>>> re.findall('\\\\n','\n\n')
[]
>>> re.findall('\b','\b\b')
['\x08', '\x08']
>>> re.findall('\\b','\b\b')
[]
>>> re.findall('\\\b','\b\b')
['\x08', '\x08']
>>> re.findall('\\\\b','\b\b')
[]
>>> re.findall('\c','\c\c')
['c', 'c']
>>> re.findall('\\c','\c\c')
['c', 'c']
>>> re.findall('\\\c','\c\c')
['\\c', '\\c']
>>> re.findall('\\\\c','\c\c')
['\\c', '\\c']
参考:
Python 3.3.3 官方文档