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

Python正则表达式问题:剥离多行注释但保留换行符,python,regex,parsing,comments,Python,Regex,Parsing,Comments,我正在分析一个源代码文件,我想删除所有行注释(即以“/”开头)和多行注释(即/…../)。但是,如果多行注释中至少有一个换行符(\n),我希望输出正好有一个换行符 例如,代码: qwe /* 123 456 789 */ asd 应该变成: qwe asd 而不是“qweasd”或: 这样做的最佳方式是什么? 谢谢 编辑: 测试的示例代码: comments_test = "hello // comment\n"+\ "line 2 /* a commen

我正在分析一个源代码文件,我想删除所有行注释(即以“/”开头)和多行注释(即/…../)。但是,如果多行注释中至少有一个换行符(\n),我希望输出正好有一个换行符

例如,代码:

qwe /* 123
456 
789 */ asd
应该变成:

qwe
asd
而不是“qweasd”或:

这样做的最佳方式是什么? 谢谢


编辑: 测试的示例代码:

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 ??

这就是你要找的吗

>>> print(s)
qwe /* 123
456
789 */ asd
>>> print(re.sub(r'\s*/\*.*\n.*\*/\s*', '\n', s, flags=re.S))
qwe
asd
这将只适用于那些超过一行的评论,但不会影响其他评论

这个怎么样:

re.sub(r'\s*/\*(.|\n)*?\*/\s*', '\n', s, re.DOTALL).strip()
它攻击前导空格、
/*
、任何文本和换行符,直到第一个
*\
,然后攻击其后的任何空格

这是对sykora的例子的一个小小的扭曲,但它在内部也是非贪婪的。您也可能需要查看多行选项。

参见——如果考虑嵌套注释,正则表达式不是解决方案。

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)
  • (^)?
    如果注释开始于行的开头,则只要使用了
    多行
    -标志,注释将匹配
  • [^\S\n]
    将匹配除换行符以外的任何空白字符。如果评论是从自己的行开始的,我们不想匹配换行符
  • /\*(.*)\*/
    将匹配多行注释并捕获内容。惰性匹配,因此我们不匹配两个或多个注释
    DOTALL
    -标志使
    匹配换行符
  • /[^\n]
    将匹配单行注释。无法使用
    ,因为有
    DOTALL
    -标志
  • ($)?
    如果注释在一行末尾停止,则只要使用了
    多行
    -标志,注释将匹配
示例:

>>> 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 ??"
编辑:

  • 更新至新规范
  • 增加了另一个例子

    • 事实上,你甚至必须问这个问题,而且我们可以说,给出的解决方案不太容易理解:-)这应该是一个很好的迹象,表明REs不是这个问题的真正答案

      从可读性的角度来看,您最好将其编写为一个相对简单的解析器


      很多时候,人们试图用REs来表现“聪明”(我并不是说用贬低的方式),认为一行字是优雅的,但他们最终得到的只是一个无法维护的角色泥沼。我希望有一个能在瞬间理解的20行完整注释的解决方案。

      谢谢。实际上,我还需要删除多行表单的单行注释(例如“/*注释*/”)。我可以用一个单独的正则表达式来实现这一点,但是你能把它添加到你的正则表达式中吗?我认为用一个单独的正则表达式,比如r'/*.**/'会更简单,因为re.S标志(请参阅)和不同的替换是有意义的('\n'vs.')。此外,我相信sykora的正则表达式应该有\s*,而不是\s+,而且我会担心
      *
      的贪婪性。我几乎总是使用
      *?
      。例如,如果在同一行上有两个单行注释,则greedyness可能会清除中间的所有内容。您是对的,它应该是\s*而不是\s+。匹配相同开始/结束分隔符但只跨一行的注释最好使用单独的模式完成,尝试将其合并到同一个模式中并不能真正完成任何操作,替换也会很棘手。首先,我认为他指的是正则表达式,而不是正则表达式。其次,对于一个不需要完美的简单应用程序(在数百万行源代码中,有多少行代码嵌套了/**/comments),regex是一个可行的解决方案,它比真正的下推自动机更简单。我所知道的任何使用/**、/comments的语言都是以非嵌套方式进行的。第一个/注释一直到第一个*/。然而,您确实提出了一个有效的观点,基本正则表达式无法处理平衡/嵌套,因为它们没有足够的内存。幸运的是,这不是其中的一种情况。@Matthew,如果正则表达式不是正则表达式,它是什么?我相信这会导致多行注释恰好占用一行,而Rax希望它们消失。@Pax:我之所以瞄准正则表达式,是因为我认为它会更有效。我有数百万代码行要分析,我正在努力消除性能瓶颈。目前我有“可读”的代码来做这项工作,我想我可以通过使用正则表达式来提高性能。你不同意这个逻辑吗?谢谢。RE的效率永远不会比编译语言中编写良好的解析器更高。这是因为您可以在编写解析器时使用领域知识(速度更快),但RE引擎必须能够处理所有问题。在Python的情况下(除非它有JIT),RE可能会更快,因为RE引擎将是机器语言,而解释的解析器将是解释的。尽管如此,我还是更喜欢可读性而不是速度。计算时间(运行代码)比人工时间(维护代码)便宜得多。所以不,我不反对,但你需要知道你在牺牲什么。我用了这个,因为它工作得很好,所以我没有尝试其余的。因此,对于其他回答正确的人,我深表歉意。@MizardX:如果您能看到我对问题的编辑和澄清,我将不胜感激,谢谢。
      >>> 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 ??"