Python正则表达式问题:去除多行注释但保留换行符

4
我正在解析源代码文件,我想删除所有的行注释(即以“//”开头的注释)和多行注释(即/..../)。但是,如果多行注释中至少有一个换行符(\n),则输出应该只有一个换行符。
例如,以下代码:
qwe /* 123
456 
789 */ asd

should turn exactly into:

qwe
asd

并且不是“qweasd”或:

qwe

asd

什么是最好的方法呢? 谢谢。
编辑: 测试用例代码:
comments_test = "hello // comment\n"+\
                "line 2 /* a comment */\n"+\
                "line 3 /* a comment*/ /*comment*/\n"+\
                "line 4 /* a comment\n"+\
                "continuation of a comment*/ line 5\n"+\
                "/* comment */line 6\n"+\
                "line 7 /*********\n"+\
                "********************\n"+\
                "**************/\n"+\
                "line ?? /*********\n"+\
                "********************\n"+\
                "********************\n"+\
                "********************\n"+\
                "********************\n"+\
                "**************/\n"+\
                "line ??"

预期结果:

hello 
line 2 
line 3  
line 4
line 5
line 6
line 7
line ??
line ??
5个回答

12
comment_re = re.compile(
    r'(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?',
    re.DOTALL | re.MULTILINE
)

def comment_replacer(match):
    start,mid,end = match.group(1,2,3)
    if mid is None:
        # single line comment
        return ''
    elif start is not None or end is not None:
        # multi line comment at start or end of a line
        return ''
    elif '\n' in mid:
        # multi line comment with line break
        return '\n'
    else:
        # multi line comment without line break
        return ' '

def remove_comments(text):
    return comment_re.sub(comment_replacer, text)
  • (^)?将匹配以行开头的注释,只要使用了MULTILINE标志。
  • [^\S\n]将匹配除换行符外的任何空白字符。如果注释在自己的行上,则不想匹配换行符。
  • /\*(.*?)\*/将匹配多行注释并捕获其中的内容。使用懒惰匹配,因此不会匹配两个或两个以上的注释。DOTALL标志使.匹配换行符。
  • //[^\n]将匹配单行注释。不能使用.,因为使用了DOTALL标志。
  • ($)?将匹配以行结尾的注释,只要使用了MULTILINE标志。

示例:

>>> s = ("qwe /* 123\n"
         "456\n"
         "789 */ asd /* 123 */ zxc\n"
         "rty // fgh\n")
>>> print '"' + '"\n"'.join(
...     remove_comments(s).splitlines()
... ) + '"'
"qwe"
"asd zxc"
"rty"
>>> comments_test = ("hello // comment\n"
...                  "line 2 /* a comment */\n"
...                  "line 3 /* a comment*/ /*comment*/\n"
...                  "line 4 /* a comment\n"
...                  "continuation of a comment*/ line 5\n"
...                  "/* comment */line 6\n"
...                  "line 7 /*********\n"
...                  "********************\n"
...                  "**************/\n"
...                  "line ?? /*********\n"
...                  "********************\n"
...                  "********************\n"
...                  "********************\n"
...                  "********************\n"
...                  "**************/\n")
>>> print '"' + '"\n"'.join(
...     remove_comments(comments_test).splitlines()
... ) + '"'
"hello"
"line 2"
"line 3 "
"line 4"
"line 5"
"line 6"
"line 7"
"line ??"
"line ??"

编辑:

  • 更新为新规范。
  • 添加了另一个例子。

我使用了这个,因为它运行良好,所以我没有尝试其他的。对于回答正确的其他人,我表示歉意。 - Roee Adler
@MizardX:如果您能看一下我的编辑(对于问题)和澄清,我将不胜感激,谢谢。 - Roee Adler

5
你不得不问这个问题,而且给出的解决方案可以说是不够易读的 :-) 这表明正则表达式并不是这个问题的真正答案。
从可读性的角度来看,你最好实际编写一个相对简单的解析器。
经常有人试图使用正则表达式来“聪明”地解决问题(我不是指贬义),认为一行代码就很优雅,但最终结果只是一堆难以维护的字符。我宁愿有一个完全注释了的、20行代码的解决方案,我能在一瞬间理解它。

@Pax:我选择正则表达式的原因是我认为它会更高效。我有数百万行代码需要分析,我正在尝试消除性能瓶颈。目前我有“可读性”代码在工作,我认为通过转向正则表达式可以提高性能。你对这个逻辑持不同意见吗?谢谢。 - Roee Adler
1
正则表达式在编译型语言中通常不如精心编写的解析器高效。这是因为编写解析器时可以利用领域知识(更快速),但是正则表达式引擎必须能够处理所有情况。在Python中,除非使用JIT编译器,否则正则表达式会更快,因为正则表达式引擎将使用机器语言,而解释器则是解释执行的。尽管如此,我仍然更喜欢可读性。计算时间(运行代码)比人力成本(维护代码)便宜得多。所以我并不反对,但你需要知道你在牺牲什么。 - paxdiablo

1

这是你要找的吗?

>>> print(s)
qwe /* 123
456
789 */ asd
>>> print(re.sub(r'\s*/\*.*\n.*\*/\s*', '\n', s, flags=re.S))
qwe
asd

这仅适用于超过一行的注释,但不会触及其他注释。


谢谢。我实际上还需要移除多行形式的单行注释(例如"/comment/")。 我可以用一个单独的正则表达式来完成,但你能把它加入到你的正则表达式里吗? - Roee Adler
我认为使用单独的正则表达式会更简单,例如r'/*.**/',因为它具有re.S标志(请参见http://docs.python.org/library/re.html#re.S),并且不同的替换是有意义的('\n' vs. '')。 - Matthew Flaschen
此外,我认为sykora的正则表达式应该使用\s*而不是\s+。 - Matthew Flaschen
1
我也会担心.*的贪婪性。我几乎总是使用.*?。例如,如果同一行有两个单行注释,贪婪性可能会抹掉它们之间的所有内容。 - Joseph Pecoraro
我也同意Joseph关于在这里使用 .? 而不是 . 的观点(两种情况都是如此)。 - Matthew Flaschen
显示剩余2条评论

1
这样怎么样:
re.sub(r'\s*/\*(.|\n)*?\*/\s*', '\n', s, re.DOTALL).strip()

它攻击前导空格,/*,任何文本和换行符,直到第一个*\,然后是之后的任何空格。

这是对sykora示例的小变化,但在内部也是非贪婪的。您还可能需要查看多行选项。


我认为这会导致多行注释占据一行变为空白行,而 Rax 希望它们消失。 - Matthew Flaschen

0

首先,我认为他真正想表达的是正则表达式,而不是常规表达式。其次,对于一个简单的应用程序,在不需要完美的情况下(在数百万行源代码中,有多少个嵌套的/* */注释),正则表达式是一个可行的解决方案,比真正的推入自动机更简单。 - Matthew Flaschen
我所知道的任何使用//注释的语言都是非嵌套式的。第一个/从一直到第一个/。然而,你提出了一个有效的观点,基本的正则表达式无法处理平衡/嵌套,因为它们没有足够的内存。幸运的是,这不是那种情况。 - Joseph Pecoraro
1
@Matthew,如果regex不是正则表达式,那它是什么? - paxdiablo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接