删除代码中所有注释(基于文本处理的方法)

处理所有注释,是编译器的看家本领。

编译器在读取代码时,就是在处理文本,自然就包括删除代码注释。

每一个编程语言都有一个叫做词法分析器的工具,编译器就是用它来处理代码文本的,基于正则匹配。它不仅要处理注释,还要处理保留字,标识符等等,要复杂多了

Python中字符"#"只有两种用途,一个是string,字符串,另一个是注释符,用于给代码加入旁白

(但要排除掉python文件头的#!)

即#不能用于其他任何形式,比如标识符,运算符等。

所以思路很明确,判断#是不是字符串,如果不是,那么它后面的所有东西都将被当作注释,直到出现换行符\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,则仅使用第一个引号作用范围来分割成两段,当然如果考虑分割符本身,则是三段。

其它符号如.*?可以艰难参考官方中文文档。

 

既然确定了引号的作用范围,就能看#是不是引号里面的普通字符了,既然知道了#是普通字符还是注释符,就能找到哪些是注释内容了。

(还是要排除掉python文件头的#!)

其他的如'''''',/**/等注释符,也都是处理文本,道理一个样把。

posted @ 2018-08-18 12:27  Oler  阅读(5551)  评论(0编辑  收藏  举报