處理所有注釋,是編譯器的看家本領。
編譯器在讀取代碼時,就是在處理文本,自然就包括刪除代碼注釋。
每一個編程語言都有一個叫做詞法分析器的工具,編譯器就是用它來處理代碼文本的,基於正則匹配。它不僅要處理注釋,還要處理保留字,標識符等等,要復雜多了
Python中字符"#"只有兩種用途,一個是string,字符串,另一個是注釋符,用於給代碼加入旁白
即#不能用於其他任何形式,比如標識符,運算符等。
所以思路很明確,判斷#是不是字符串,如果不是,那么它后面的所有東西都將被當作注釋,直到出現換行符\n。
但這個換行符不是我們可以通過輸入 \和n所表示的換行符,只能是我們看不見的換行符,比如敲一下鍵盤上的Enter。
實際上當我們在字符串中連續輸入\和n的時候,(並且只能在字符串中輸入),計算機在內部把它們轉義成了"\\n"。如果后面你又需要將字符串"\\n" print出來,計算機又把它們當成"\n",並輸出字符串里的內容以及格式。而在計算機內部,換行符確實以"\n"(或者深入一點二進制形式)形式存在,但只會以(給文本換行)的方式展現出來,而不會以"\n"的樣子展現出來,那個在屏幕上展現出"\n"的樣子的東西,在計算機內部以"\\n"的形式存在。
所以以下代碼:
a = "a"#這是個注釋?"\n"a = "1"
b = "b\nb"#這是個注釋?\nb = "2"
print(a) 輸出:a (沒有引號了,表示引號里面的內容) print(b) 輸出:b b (沒有引號了,表示輸出引號里面的內容及其格式) a 輸出:'a' (有引號,表示a是字符串) b 輸出:'b\nb' (有引號,表示b是字符串,而不看它里面的轉義字符\n)
所以上面代碼注釋中包含"\n",\n,但它們被計算機讀取的時候,都被轉義成了"\\n",不具有換行效果。而在a = "1"的后面,真真切切的跟着一個我們看不到的換行符,
#也不是無論如何都看不到,將代碼保存到文件D:/code.txt file = open('D:/code.txt')
file.read() 輸出: 'a = "a"#這是個注釋?"\\n"a = "1"\nb = "b\\nb"#這是個注釋?\\nb = "2"'
type(file.read())
輸出: str
print(file.read())
輸出: a = "a"#這是個注釋?"\n"a = "1"
b = "b\nb"#這是個注釋?\nb = "2"
說了這么多,就為了可以用#和換行符(看不見的那個)來找出注釋。
#和后面第一個換行符中間的所有內容都是注釋了。?
非,比如下面代碼:
a = "a#a" print(a) 輸出:a#a
要是#a"被當作注釋刪掉了就不好了。。
前面我們說過,#在python代碼中要么以注釋符形式存在,要么以字符串形式,上面這個代碼就是以字符串形式存在的。字符串,有個很明顯的特征:被英文引號(以下簡稱引號)圍住了。
所以,如果代碼沒有語法錯誤的話,即能夠進入運行期的話,沒有被引號括住的行內第一個#,一定是注釋符,被引號括住的#,一定不是注釋符。
所以思路很明確,先判斷#是不是字符串,即沒有被引號括住;如果不是,則提取出此#及其之后直到換行符的所有內容,它們就是注釋。
但python內置有一個readlines()函數,可以替我們省下找換行符的步驟,它返回一個列表,其中的元素即是文件內容中以換行符隔開的每一行。通過這個函數,我們可以先把代碼文件分成一行一行的,再在每一行內尋找注釋。需要注意的是,編輯器內自動換行的功能並不是在其中加入了換行符,它們看起來自動換到了下一行,其實在計算機看來還是一行,直到出現換行符。readlines()也不會以我們看到的自動換行的行數為標准,注釋符也是,它們只認換行符。
現在就可以開始判斷行內的#有沒有被引號括住了。
先來探究一下引號的作用機制。
a = "She said, 'All right!'"
b = "很明顯,',是一個單引號" b_2 = '很明顯,",是一個雙引號' b_3 = "很明顯,",是一個雙引號" #這個會報錯SyntaxError
b_4 = '很明顯,"",是兩個雙引號'
c = '"'2'"' #這個會報錯SyntaxError
#對於報錯,是因為多個同樣的引號引起作用范圍混淆,解決辦法是把里面不參與作用的引號加個\,轉義成普通字符,比如c = '"\'2\'"'
首先,計算機從行首第一個字符開始檢查,出現雙引號,則找第二個雙引號,這兩個雙引號就成雙成對了再不會分開了,不會參與后面的雙引號作用了,其中的內容也成了一個整體,其中若包含一個引號,比如上面的b,這個引號也不會參與外面的引號作用了。(單引號同理)
所以並不是很復雜,對於readlines()返回的每一行:
1,先看有沒有#;
2,如果有,則檢查有沒有引號;
3,如果有引號,看所有的引號的作用范圍是否包括#;
4,如果包括,則繼續往后,重復開始第1步;
對於上述每一步,如果遇到了換行符,那么抱歉,檢查結束,返回結果。
第1,2,3步的任意一個,結果若為否,則檢查完成。
對於如何確定引號的作用范圍,可以走個捷徑使用正則表達式。(艱難參考,Python中文文檔:https://docs.python.org/zh-cn/3/library/re.html)
a = ["a#'a", 'b"#"b']#這是個注釋?'\n"'a = "1"
將上面這一行代碼保存到code.py,然后把code.py當作文本處理。
text = open(文件路徑+code.py).read()
text
輸出:'a = ["a#\'a", \'b"#"b\']#這是個注釋?\'\\n"\'a = "1"'
print(text)
輸出:a = ["a#'a", 'b"#"b']#這是個注釋?'\n"'a = "1"
可以看到用print(),就跟我們原本的輸入一個模樣,是代碼,不用print()直接查看text,就出來一個字符串,是代碼的文本形式,里面的單引號為了不與最兩邊的單引號發生作用,加了斜杠轉義了,而里面的\n是我們自己輸入的\和n,並不是用Enter敲出來的換行符,所以計算機只把它們當成普通字符,以\\n的樣子存在於計算機內部。
下面代碼展示了如何用re正則表達式來提取字符串text中,引號的作用范圍
import re re.split('(".*?"|\'.*?\')', text, maxsplit=0) 輸出:['a = [', '"a#\'a"', ', ', '\'b"#"b\'', ']#這是個注釋?', '\'\\n"\'', 'a = ', '"1"', ''] re.split('".*?"|\'.*?\'', text, maxsplit=0) 輸出:['a = [', ', ', ']#這是個注釋?', 'a = ', '']
上述代碼用正則表達式匹配到所有的引號作用范圍,既考慮單引號,也考慮雙引號。
將匹配到的引號作用范圍當作分割符,把字符串text分割成若干子串,並返回列表。
第一個split(),比第二個split()的正則表達式里,多了一層小括號,作用是,返回的列表中也要包括分割符。
表達式的中間有個|,|的左右兩邊一個是匹配雙引號的,一個匹配單引號,
即,若只匹配雙引號,則使用
re.split('".*?"', text, maxsplit=0)
若只匹配單引號,則使用
re.split('\'.*?\'', text, maxsplit=0)
為什么要給表達式里的單引號加上轉義斜杠呢,因為表達式本身用了單引號括住,若不轉義為普通字符,則會與本身最外層的引號發生作用。若使用雙引號括住,則應該把里面的雙引號轉義。
maxsplit指定了從左往右分割幾次,若為0則不限,若為1,則僅使用第一個引號作用范圍來分割成兩段,當然如果考慮分割符本身,則是三段。
其它符號如.*?可以艱難參考官方中文文檔。
既然確定了引號的作用范圍,就能看#是不是引號里面的普通字符了,既然知道了#是普通字符還是注釋符,就能找到哪些是注釋內容了。
其他的如'''''',/**/等注釋符,也都是處理文本,道理一個樣把。