Python r」\" SyntaxError 源碼分析

這是前幾天偶爾碰到的問題, 發現跟本身的對python理解不一致, 雖然文檔上已明確說明不支持的,可是爲何就不支持這麼個語法?python

Even in a raw literal, quotes can be escaped with a backslash, but the backslash remains in the result; for example, r""" is a valid string literal consisting of two characters: a backslash and a double quote; r"" is not a valid string literal (even a raw string cannot end in an odd number of backslashes). Specifically, a raw literal cannot end in a single backslash (since the backslash would escape the following quote character). Note also that a single backslash followed by a newline is interpreted as those two characters as part of the literal, not as a line continuation.bash

1. 首先,說下問題:

"\"在python字符串中算特殊字符, 起轉義做用. \n轉義n變回車, \\轉義\等等,這個應該沒有問題. 在正常狀況下a="\" 是存在語法問題, 由於\轉義了右邊的",因此字符串少一個右引號,錯誤緣由很明顯了. 可是python(很做死的)有個r操做符(全稱 原始字符串操做符), 就是對字符串內的內容按字面意思解析,不作特殊處理,因此若是a=r"\n" 輸出的就是"\\n",而不是回車,爲何是"\\n", goto 1 再看看. 那麼如今問題來了, 按r的功能, a=r"\", 應該沒問題,由於在r的場子裏,\是不能轉義",可是如今仍是給你一個語法錯誤,syntaxError.spa

2. 而後,我想說:

其實我剛纔說錯了, 這不是一個語法錯誤, 這tmd是個詞法錯誤.(略裝B的說法,語法詞法概念模糊的能夠網上稍微看下). 爲何這麼說, 跟了下python的源碼,發現問題出在Parser\tokenizer.c內, python對字符串對象的詞法解析部分.代碼以下:code

/* 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;  
    }  
複製代碼

其實蠻簡單的. 幾個地方須要扯一下:對象

  • letter_quote:這個跳轉標籤,請關注之,最後揭曉.
  • tok_nextc從輸入流中獲取下一個字符.
  • tok_backup將字符放回輸入流去.
  • triple 和tripcount這兩個變量跟三引號處理有關,triple標記如今是否處於三引號模式, 等於1時,說明當前處於三引號模式, tripcount記錄連續的引號數.

Ok,剩下的有點c基礎的就能夠啃了. 這裏簡單分析下, if (c == ''' || c == '"')當前字符是'或"進入字符串對象的詞法解析過程了,因此python支持'和"兩種引號字符。在for循環一共有4個else if,其實就是說明python的字符串有4類特殊字符。token

  1. \n回車. 若是在非三引號模式下,檢測到回車後,設置下相應的錯誤碼, 而後返回失敗, 相關的錯誤定義以下:
#define E_EOFS      23  /* EOF in triple-quoted string */  
#define E_EOLS      24  /* EOL in single-quoted string */  
複製代碼

這搞的好像在單引號模式下,不能輸回車同樣的,可是有點python經驗的都知道行尾加個\就能夠輸入回車, 在下一行重頭再來.是的, 請記住, 要輸入\. 從中也能夠看出,在三引號模式下, 回車是能夠隨便輸的.ip

  1. EOF文件尾(輸入流停水了). 這個簡單除暴了,根據當前模式,設置下錯誤碼,而後返回失敗。ci

  2. quote引號. 先跳過if (tok->cur - tok->start == quote2). 單看if (!triple || tripcount == 3). 若是沒在三引號模式下,又"摸到"個quote,那當前這個字符串詞法解析就完了, break出去,一個字符串就ko了(尼瑪,略簡單的說). 回過頭說下if (tok->cur - tok->start == quote2) 這個,就是判斷三引號的地方了.跟本文核心內容無關, 就不扯了哈.rem

  3. '\',終於到這貨了. 處理也很簡單, 核心就是c = tok_nextc(tok), 把的下個字符從輸入流裏取了,其餘什麼都無論. 由於還在詞法階段,無法解析轉義字符.那麼如今不少問題找到答案:文檔

  • 加個\,單行模式能夠輸入回車了,由於python在解析到一個\後,直接把後面的回車從輸入流裏取出來了, 同時也說明,回車必定要緊跟\後面,由於\只取他後面的一個字符.
  • "\"爲何失敗, 應爲解析到\後,\把"取走了.那麼下面的解析時,面對的就是\n,因此Syntax EOL in single-quoted string

ok, python對string對象的詞法解析就這樣了. 那麼扯了這麼多, 還沒提r操做符,在詞法階段,他的處理很偷懶,代碼以下:

case 'r':  
case 'R':  
    c = tok_nextc(tok);  
    if (c == '"' || c == '\'')       {           goto letter_quote;       }   複製代碼

尼瑪, 就是一個goto 到字符串解析部分了. (?:啥表情也不作,就把活甩給別人了? r:詞法分析階段,老子能幹嗎!!!). 由於r原始操做符, 應該屬於python語法上內容, 因此詞法分析階段,他的處理方式就是常規字符串的處理方式(r:贊一個). 因此面對"\"時,也要報個Syntax EOL in single-quoted string.

3. 後話

其實解決這個問題仍是很簡單的, 加個標示變量

int bInRMode = 0;  

case 'r':  
case 'R':  
    c = tok_nextc(tok);  
    if (c == '"' || c == '\'')       {           bInRMode = 1; //設置下標示位           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);       }   }   複製代碼

在解析\時, 須要特殊處理下. 應爲r模式下, \應該是普通字符,不該該有取下個字符的功能,因此我加個代碼把取的字符又塞回去了. 原本在else if (c == '\')這裏能夠直接處理的,可是若是不對回車特殊處理,那單引號模式下就不能經過\輸回車了,因此對\後接\n,又不採用r的功能了.

相關文章
相關標籤/搜索