這是前幾天偶爾碰到的問(wèn)題, 發(fā)現(xiàn)跟自己的對(duì)python理解不一致, 于是網(wǎng)上搜了下, 說(shuō)是Python的Bug(不過(guò)文檔上是有說(shuō)過(guò)說(shuō)不支持的) 正好最近在看python源碼, 于是就通過(guò)源碼分析了下.
1. 首先,說(shuō)下問(wèn)題:
""在python字符串中算特殊字符, 起轉(zhuǎn)義作用, \n轉(zhuǎn)義n變回車(chē), \轉(zhuǎn)義\等等,這個(gè)應(yīng)該沒(méi)啥問(wèn)題. 那么正常情況下a="" 是存在語(yǔ)法問(wèn)題, 因?yàn)閈轉(zhuǎn)義了右邊的",所以字符串少一個(gè)右括號(hào),錯(cuò)誤原因很明顯了. 但是python(很作死的)有個(gè)r操作符(全稱 原始字符串操作符), 就是對(duì)字符串內(nèi)的內(nèi)容按字面意思解析,不做特殊處理,所以如果a=r"\n" 輸出的就是"\n",而不是回車(chē),為什么是"\", goto 1 再看看. 那么現(xiàn)在問(wèn)題來(lái)了, 按r的功能, a=r"", 應(yīng)該沒(méi)問(wèn)題,因?yàn)樵趓的場(chǎng)子里,\是不能轉(zhuǎn)義",但是現(xiàn)在還是給你一個(gè)語(yǔ)法錯(cuò)誤,syntaxError.
2. 然后,我想說(shuō):
其實(shí)我剛才說(shuō)錯(cuò)了, 這不是一個(gè)語(yǔ)法錯(cuò)誤, 這tmd是個(gè)詞法錯(cuò)誤.(略裝B的說(shuō)法,語(yǔ)法詞法概念模糊的可以網(wǎng)上稍微看下). 為什么這么說(shuō), 跟了下python的源碼,發(fā)現(xiàn)問(wèn)題出在Parser\tokenizer.c內(nèi), python對(duì)字符串對(duì)象的詞法解析部分.代碼如下:
/* String */
letter_quote:
if (c == '\'' || c == '"') {
Py_ssize_t quote2 = tok->cur - tok->start + 1;
int quote = c;
int triple = 0;
int tripcount = 0;
for (;;) {
c = tok_nextc(tok);
if (c == '\n') {
if (!triple) {
tok->done = E_EOLS;
tok_backup(tok, c);
return ERRORTOKEN;
}
tripcount = 0;
tok->cont_line = 1; /* multiline string. */
}
else if (c == EOF) {
if (triple)
tok->done = E_EOFS;
else
tok->done = E_EOLS;
tok->cur = tok->inp;
return ERRORTOKEN;
}
else if (c == quote) {
tripcount++;
if (tok->cur - tok->start == quote2) {
c = tok_nextc(tok);
if (c == quote) {
triple = 1;
tripcount = 0;
continue;
}
tok_backup(tok, c);
}
if (!triple || tripcount == 3)
break;
}
else if (c == '\\' ) {
tripcount = 0;
c = tok_nextc(tok);
if (c == EOF) {
tok->done = E_EOLS;
tok->cur = tok->inp;
return ERRORTOKEN;
}
}
else
tripcount = 0;
}
*p_start = tok->start;
*p_end = tok->cur;
return STRING;
}
其實(shí)蠻簡(jiǎn)單的.
幾個(gè)地方需要扯一下:
- 是letter_quote:這個(gè)跳轉(zhuǎn)標(biāo)簽,請(qǐng)關(guān)注之,最后揭曉.
- tok_nextc從輸入流中獲取下一個(gè)字符.
- tok_backup將字符放回輸入流去.
- triple 和tripcount這兩個(gè)變量跟三引號(hào)處理有關(guān),triple標(biāo)記現(xiàn)在是否處于三引號(hào)模式, 等于1時(shí),說(shuō)明當(dāng)前處于三引號(hào)模式, tripcount記錄連續(xù)的引號(hào)數(shù).
Ok,剩下的有點(diǎn)c基礎(chǔ)的就可以啃了.
這里簡(jiǎn)單分析下, if (c == ''' || c == '"')當(dāng)前字符是'或"進(jìn)入字符串對(duì)象的詞法解析過(guò)程了,所以python支持'和"兩種引號(hào)字符。在for循環(huán)一共有4個(gè)else if,其實(shí)就是說(shuō)明python的字符串有4類(lèi)特殊字符。
- \n回車(chē). 如果在非三引號(hào)模式下,檢測(cè)到回車(chē)后,設(shè)置下相應(yīng)的錯(cuò)誤碼, 然后返回失敗, 相關(guān)的錯(cuò)誤定義如下:
#define E_EOFS 23 /* EOF in triple-quoted string */
#define E_EOLS 24 /* EOL in single-quoted string */
這搞的好像在單引號(hào)模式下,不能輸回車(chē)試的,但是有點(diǎn)python經(jīng)驗(yàn)的都知道行尾加個(gè)\就可以輸入回車(chē), 在下一行重頭再來(lái).是的, 請(qǐng)記住, 要輸入#. 從中也可以看出,在三引號(hào)模式下, 回車(chē)是可以隨便輸?shù)?
EOF文件尾(輸入流停水了). 這個(gè)簡(jiǎn)單除暴了,根據(jù)當(dāng)前模式,設(shè)置下錯(cuò)誤碼,然后返回失敗。
quote引號(hào). 先跳過(guò)if (tok->cur - tok->start == quote2). 單看if (!triple || tripcount == 3). 如果沒(méi)在三引號(hào)模式下,又"摸到"個(gè)quote,那當(dāng)前這個(gè)字符串詞法解析就完了, break出去,一個(gè)字符串就ko了(尼瑪,略簡(jiǎn)單的說(shuō)). 回過(guò)頭說(shuō)下if (tok->cur - tok->start == quote2) 這個(gè),就是判斷三引號(hào)的地方了.跟本文核心內(nèi)容無(wú)關(guān), 就不扯了哈.
'\',終于到這貨了. 處理也很簡(jiǎn)單, 核心就是c = tok_nextc(tok), 把的下個(gè)字符從輸入流里取了,其他什么都不管. 因?yàn)檫€在詞法階段,沒(méi)法解析轉(zhuǎn)義字符.那么現(xiàn)在很多問(wèn)題找到答案:
- 加個(gè)\,單行模式可以輸入回車(chē)了,因?yàn)閜ython在解析到一個(gè)\后,直接把后面的回車(chē)從輸入流里取出來(lái)了, 同時(shí)也說(shuō)明,回車(chē)一定要緊跟\后面,因?yàn)閈只取他后面的一個(gè)字符.
- ""為什么失敗, 應(yīng)為解析到\后,\把"取走了.那么下面的解析時(shí),面對(duì)的就是\n,所以Syntax EOL in single-quoted string
ok, python對(duì)string對(duì)象的詞法解析就這樣了. 那么扯了這么多, 還沒(méi)提r操作符,在詞法階段,他的處理很偷懶,代碼如下:
case 'r':
case 'R':
c = tok_nextc(tok);
if (c == '"' || c == '\'')
{
goto letter_quote;
}
尼瑪, 就是一個(gè)goto 到字符串解析部分了. (?:啥表情也不做,就把活甩給別人了? r:詞法分析階段,老子能干嘛!!!). 因?yàn)閞原始操作符, 應(yīng)該屬于python語(yǔ)法上內(nèi)容, 所以詞法分析階段,他的處理方式就是常規(guī)字符串的處理方式(r:贊一個(gè)). 所以面對(duì)""時(shí),也要報(bào)個(gè)Syntax EOL in single-quoted string.
3. 后話
其實(shí)解決這個(gè)問(wèn)題還是很簡(jiǎn)單的, 加個(gè)標(biāo)示變量
int bInRMode = 0;
case 'r':
case 'R':
c = tok_nextc(tok);
if (c == '"' || c == '\'')
{
bInRMode = 1; //設(shè)置下標(biāo)示位
goto letter_quote;
}
else if (c == '\\' ) {
tripcount = 0;
c = tok_nextc(tok);
if (c == EOF) {
tok->done = E_EOLS;
tok->cur = tok->inp;
return ERRORTOKEN;
}
if( c !='\n' && bInRMode){
tok_backup(tok ,c);
}
}
在解析\時(shí), 需要特殊處理下. 應(yīng)為r模式下, \應(yīng)該是普通字符,不應(yīng)該有取下個(gè)字符的功能,所以我加個(gè)代碼把取的字符又塞回去了. 本來(lái)在else if (c == '\')這里可以直接處理的,但是如果不對(duì)回車(chē)特殊處理,那單引號(hào)模式下就不能通過(guò)\輸回車(chē)了,所以對(duì)\后接\n,又不采用r的功能了.