处理所有注释,是编译器的看家本领。
编译器在读取代码时,就是在处理文本,自然就包括删除代码注释。
每一个编程语言都有一个叫做词法分析器的工具,编译器就是用它来处理代码文本的,基于正则匹配。它不仅要处理注释,还要处理保留字,标识符等等,要复杂多了
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,则仅使用第一个引号作用范围来分割成两段,当然如果考虑分割符本身,则是三段。
其它符号如.*?可以艰难参考官方中文文档。
既然确定了引号的作用范围,就能看#是不是引号里面的普通字符了,既然知道了#是普通字符还是注释符,就能找到哪些是注释内容了。
其他的如'''''',/**/等注释符,也都是处理文本,道理一个样把。