兩個不起眼但是比較重要的設定
- 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 官方文檔