python 3.3.3 字面量,正则,反斜杠和原始字符串分析

java哥 阅读:391 2021-05-26 14:30:20 评论:0

两个不起眼但是比较重要的设定

  • 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','123') 
['1', '2', '3'] 
>>> re.findall('\\d','123') 
['1', '2', '3']
  • 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 官方文档


声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
排行榜
关注我们

一个IT知识分享的公众号