代码之家  ›  专栏  ›  技术社区  ›  Anurag Uniyal

用于删除Python注释/文档字符串的脚本

  •  8
  • Anurag Uniyal  · 技术社区  · 16 年前

    是否有可以从Python源代码中删除注释和docstring的Python脚本或工具?

    """
    aas
    """
    def f():
        m = {
            u'x':
                u'y'
            } # faake docstring ;)
        if 1:
            'string' >> m
        if 2:
            'string' , m
        if 3:
            'string' > m
    

    最后,我提出了一个简单的脚本,它使用tokenize模块并删除注释标记。它似乎工作得很好,只是我不能在所有情况下删除docstring。看看是否可以改进它以删除docstring。

    import cStringIO
    import tokenize
    
    def remove_comments(src):
        """
        This reads tokens using tokenize.generate_tokens and recombines them
        using tokenize.untokenize, and skipping comment/docstring tokens in between
        """
        f = cStringIO.StringIO(src)
        class SkipException(Exception): pass
        processed_tokens = []
        last_token = None
        # go thru all the tokens and try to skip comments and docstrings
        for tok in tokenize.generate_tokens(f.readline):
            t_type, t_string, t_srow_scol, t_erow_ecol, t_line = tok
    
            try:
                if t_type == tokenize.COMMENT:
                    raise SkipException()
    
                elif t_type == tokenize.STRING:
    
                    if last_token is None or last_token[0] in [tokenize.INDENT]:
                        # FIXEME: this may remove valid strings too?
                        #raise SkipException()
                        pass
    
            except SkipException:
                pass
            else:
                processed_tokens.append(tok)
    
            last_token = tok
    
        return tokenize.untokenize(processed_tokens)
    

    6 回复  |  直到 9 年前
        1
  •  25
  •   Community Mohan Dere    9 年前

    我是这本书的作者” 天哪,他用正则表达式写了一个python解释器。。。 “(即Pyminifer)提到 at that link below =).

    您会很高兴地注意到,代码不再那么依赖正则表达式,而是使用标记化器,效果非常好。不管怎么说,这是答案 remove_comments_and_docstrings()
    (注意:它适用于以前发布代码中断的边缘案例):

    import cStringIO, tokenize
    def remove_comments_and_docstrings(source):
        """
        Returns 'source' minus comments and docstrings.
        """
        io_obj = cStringIO.StringIO(source)
        out = ""
        prev_toktype = tokenize.INDENT
        last_lineno = -1
        last_col = 0
        for tok in tokenize.generate_tokens(io_obj.readline):
            token_type = tok[0]
            token_string = tok[1]
            start_line, start_col = tok[2]
            end_line, end_col = tok[3]
            ltext = tok[4]
            # The following two conditionals preserve indentation.
            # This is necessary because we're not using tokenize.untokenize()
            # (because it spits out code with copious amounts of oddly-placed
            # whitespace).
            if start_line > last_lineno:
                last_col = 0
            if start_col > last_col:
                out += (" " * (start_col - last_col))
            # Remove comments:
            if token_type == tokenize.COMMENT:
                pass
            # This series of conditionals removes docstrings:
            elif token_type == tokenize.STRING:
                if prev_toktype != tokenize.INDENT:
            # This is likely a docstring; double-check we're not inside an operator:
                    if prev_toktype != tokenize.NEWLINE:
                        # Note regarding NEWLINE vs NL: The tokenize module
                        # differentiates between newlines that start a new statement
                        # and newlines inside of operators such as parens, brackes,
                        # and curly braces.  Newlines inside of operators are
                        # NEWLINE and newlines that start new code are NL.
                        # Catch whole-module docstrings:
                        if start_col > 0:
                            # Unlabelled indentation means we're inside an operator
                            out += token_string
                        # Note regarding the INDENT token: The tokenize module does
                        # not label indentation inside of an operator (parens,
                        # brackets, and curly braces) as actual indentation.
                        # For example:
                        # def foo():
                        #     "The spaces before this docstring are tokenize.INDENT"
                        #     test = [
                        #         "The spaces before this string do not get a token"
                        #     ]
            else:
                out += token_string
            prev_toktype = token_type
            last_col = end_col
            last_lineno = end_line
        return out
    
        2
  •  10
  •   Ned Batchelder    16 年前

    这就是工作:

    """ Strip comments and docstrings from a file.
    """
    
    import sys, token, tokenize
    
    def do_file(fname):
        """ Run on just one file.
    
        """
        source = open(fname)
        mod = open(fname + ",strip", "w")
    
        prev_toktype = token.INDENT
        first_line = None
        last_lineno = -1
        last_col = 0
    
        tokgen = tokenize.generate_tokens(source.readline)
        for toktype, ttext, (slineno, scol), (elineno, ecol), ltext in tokgen:
            if 0:   # Change to if 1 to see the tokens fly by.
                print("%10s %-14s %-20r %r" % (
                    tokenize.tok_name.get(toktype, toktype),
                    "%d.%d-%d.%d" % (slineno, scol, elineno, ecol),
                    ttext, ltext
                    ))
            if slineno > last_lineno:
                last_col = 0
            if scol > last_col:
                mod.write(" " * (scol - last_col))
            if toktype == token.STRING and prev_toktype == token.INDENT:
                # Docstring
                mod.write("#--")
            elif toktype == tokenize.COMMENT:
                # Comment
                mod.write("##\n")
            else:
                mod.write(ttext)
            prev_toktype = toktype
            last_col = ecol
            last_lineno = elineno
    
    if __name__ == '__main__':
        do_file(sys.argv[1])
    

    我将存根注释放在docstring和注释的位置,因为它简化了代码。如果你完全移除它们,你还必须去除它们之前的压痕。

        3
  •  5
  •   Basj    5 年前

    这里是对 Dan's solution 要使其在Python3+上运行,请删除空行并使其准备就绪:

    import io, tokenize, re
    def remove_comments_and_docstrings(source):
        io_obj = io.StringIO(source)
        out = ""
        prev_toktype = tokenize.INDENT
        last_lineno = -1
        last_col = 0
        for tok in tokenize.generate_tokens(io_obj.readline):
            token_type = tok[0]
            token_string = tok[1]
            start_line, start_col = tok[2]
            end_line, end_col = tok[3]
            ltext = tok[4]
            if start_line > last_lineno:
                last_col = 0
            if start_col > last_col:
                out += (" " * (start_col - last_col))
            if token_type == tokenize.COMMENT:
                pass
            elif token_type == tokenize.STRING:
                if prev_toktype != tokenize.INDENT:
                    if prev_toktype != tokenize.NEWLINE:
                        if start_col > 0:
                            out += token_string
            else:
                out += token_string
            prev_toktype = token_type
            last_col = end_col
            last_lineno = end_line
        out = '\n'.join(l for l in out.splitlines() if l.strip())
        return out
    with open('test.py', 'r') as f:
        print(remove_comments_and_docstrings(f.read()))
    
        4
  •  2
  •   SurpriseDog    7 年前

    我发现使用ast和astunparse模块(可从pip获得)实现这一点更简单。它将代码文本转换为语法树,然后astunparse模块再次打印代码,不带注释。我不得不用一个简单的匹配去掉docstring,但它似乎有效。我一直在查看输出,到目前为止,这种方法唯一的缺点是它会从代码中删除所有换行符。

    import ast, astunparse
    
    with open('my_module.py') as f:
        lines = astunparse.unparse(ast.parse(f.read())).split('\n')
        for line in lines:
            if line.lstrip()[:1] not in ("'", '"'):
                print(line)
    
        5
  •  1
  •   Denis Otkidach    16 年前

    尝试测试以换行符结尾的每个标记块。然后纠正docstring的模式(包括它用作注释但未指定给注释的情况) __doc__ )我认为是(假设从换行后的文件开始执行匹配):

    ( DEDENT+ | INDENT? ) STRING+ COMMENT? NEWLINE
    

        6
  •  0
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

    我刚刚使用了Dan McDougall给出的代码,发现了两个问题。

    1. 有太多空的新行,所以我决定每次我们有两个连续的新行时删除该行
    2. 处理Python代码时,所有空格都丢失了(缩进除外),因此“import Anything”等内容变为“importAnything”,从而导致了问题。我在保留的Python单词之后和之前添加了空格,这需要完成。我希望我没有犯任何错误。

    我想我已经解决了这两个问题,添加了几行(返回之前):

    # Removing unneeded newlines from string
    buffered_content = cStringIO.StringIO(content) # Takes the string generated by Dan McDougall's code as input
    content_without_newlines = ""
    previous_token_type = tokenize.NEWLINE
    for tokens in tokenize.generate_tokens(buffered_content.readline):
        token_type = tokens[0]
        token_string = tokens[1]
        if previous_token_type == tokenize.NL and token_type == tokenize.NL:
            pass
        else:
            # add necessary spaces
            prev_space = ''
            next_space = ''
            if token_string in ['and', 'as', 'or', 'in', 'is']:
                prev_space = ' '
            if token_string in ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global', 'or', 'with', 'assert', 'if', 'yield', 'except', 'import', 'print', 'class', 'exec', 'in', 'raise', 'is', 'return', 'def', 'for', 'lambda']:
                next_space = ' '
            content_without_newlines += prev_space + token_string + next_space # This will be our new output!
        previous_token_type = token_type
    
    推荐文章