python - 从 Python 源代码中提取注释

标签 python python-2.7

我正在尝试编写一个程序来提取用户输入的代码中的注释。我尝试使用正则表达式,但发现很难编写。

然后我发现了一个帖子here .答案建议使用tokenize.generate_tokens来分析语法,但是the documentation says :

The generate_tokens() generator requires one argument, readline, which must be a callable object which provides the same interface as the readline() method of built-in file objects (see section File Objects).

但是字符串对象没有readline方法。

然后我又找到了一个帖子here ,建议使用 StringIO.StringIO 获取 readline 方法。所以我写了下面的代码:

import tokenize
import io
import StringIO

def extract(code):
    res = []
    comment = None
    stringio = StringIO.StringIO(code)
    for toktype, tokval, begin, end, line in tokenize.generate_tokens(stringio):
        # print(toknum,tokval)
        if toktype != tokenize.COMMENT:
            res.append((toktype, tokval))
        else:
            print tokenize.untokenize(toktype)
    return tokenize.untokenize(res)

并输入以下代码:extract('a = 1+2#A Comment')

但是得到了:

Traceback (most recent call last):     
   File "<stdin>", line 1, in <module>     
   File "ext.py", line 10, in extract     
     for toktype, tokval, begin, end, line in tokenize.generate_tokens(stringio):     
   File "C:\Python27\lib\tokenize.py", line 294, in generate_tokens     
     line = readline()     
AttributeError: StringIO instance has no `__call__` method

我知道我可以写一个新类,但有没有更好的解决方案?

最佳答案

回答更一般的情况(从模块、函数中提取):

模块:

文档指定需要提供一个可调用对象,它公开与 readline() 相同的接口(interface)。 内置文件对象的方法。这暗示:创建一个提供该方法的对象。

对于模块,我们可以 open 一个新模块作为普通文件并传入它的 readline 方法。 这是关键,您传递的参数方法readline()

给定一个小的 scrpt.py 文件:

# My amazing foo function.
def foo():
    """ docstring """
    # I will print
    print "Hello"
    return 0   # Return the value

# Maaaaaaain
if __name__ == "__main__":
    # this is main
    print "Main" 

我们将像打开所有文件一样打开它:

fileObj = open('scrpt.py', 'r')

这个文件对象现在有一个名为 readline 的方法(因为它是一个文件对象),我们可以安全地将其传递给 tokenize.generate_tokens 并创建一个生成器。

tokenize.generate_tokens (在 Py3 中只是 tokenize.tokenize -- 注意:Python 3 需要 readline 返回 bytes 所以你需要在 'rb 中打开文件' mode) 返回一个命名的元素元组,其中包含有关标记化元素的信息。这是一个小演示:

for toktype, tok, start, end, line in tokenize.generate_tokens(fileObj.readline):
    # we can also use token.tok_name[toktype] instead of 'COMMENT'
    # from the token module 
    if toktype == tokenize.COMMENT:
        print 'COMMENT' + " " + tok

注意我们如何将 fileObj.readline 方法传递给它。现在将打印:

COMMENT # My amazing foo function
COMMENT # I will print
COMMENT # Return the value
COMMENT # Maaaaaaain
COMMENT # this is main 

因此,所有评论,无论位置如何,都会被检测到。文档字符串当然被排除在外。

函数:

对于我真的想不到的情况,您可以在没有 open 的情况下获得类似的结果。尽管如此,为了完整起见,我将介绍另一种方法。在这种情况下,您将需要两个额外的模块, inspect StringIO ( io.StringIO Python3 中):

假设您有以下功能:

def bar():
    # I am bar
    print "I really am bar"
    # bar bar bar baaaar
    # (bar)
    return "Bar"

您需要一个具有readline 方法的类文件对象,以便将其与tokenize 一起使用。那么,您可以使用 StringIO.StringIOstr 创建一个类似文件的对象,并且您可以获得表示函数源的 strinspect.getsource(func) .在代码中:

funcText = inpsect.getsource(bar)
funcFile = StringIO.StringIO(funcText)

现在我们有一个类似文件的对象,表示具有所需 readline 方法的函数。我们可以重新使用之前执行的循环,将 fileObj.readline 替换为 funcFile.readline。我们现在得到的输出具有类似的性质:

COMMENT # I am bar
COMMENT # bar bar bar baaaar
COMMENT # (bar)

顺便说一句,如果你真的想用 re 创建一个自定义的方法,看看 the source for the tokenize.py module .它为注释定义了某些模式,(r'#[^\r\n]*') 名称等等,使用 readline 循环遍历行并在 line 模式列表。值得庆幸的是,在你看了一会儿之后它并不太复杂 :-)。


函数 extract 的答案(更新):

您已经使用提供接口(interface)的 StringIO 创建了一个对象,但是您是否还没有将该接口(interface) (readline) 传递给 tokenize。 generate_tokens,相反,您传递了完整的对象 (stringio)

此外,在您的 else 子句中将引发 TypeError,因为 untokenize 需要一个可迭代对象作为输入。进行以下更改后,您的函数可以正常工作:

def extract(code):
    res = []
    comment = None
    stringio = StringIO.StringIO(code)
    # pass in stringio.readline to generate_tokens
    for toktype, tokval, begin, end, line in tokenize.generate_tokens(stringio.readline):
        if toktype != tokenize.COMMENT:
            res.append((toktype, tokval))
        else:
            # wrap (toktype, tokval) tupple in list
            print tokenize.untokenize([(toktype, tokval)])
    return tokenize.untokenize(res)

提供 expr = extract('a=1+2#A comment') 形式的输入,该函数将打印出注释并将表达式保留在 expr:

expr = extract('a=1+2#A comment')
#A comment

print expr
'a =1 +2 '

此外,正如我稍后提到的,io 包含用于 Python3 的 StringIO,所以在这种情况下,幸好不需要 import

关于python - 从 Python 源代码中提取注释,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34511673/

相关文章:

python - 如果 pandas 数据框中另一列的值相同,我如何减去 2 列

python - seaborn 热图显示轴标签,但当 df.corr 为 NaN 时没有值

python - 为什么 Django 模型上输入错误的字段名称不会引发异常?

python - scikit-learn.org 的示例代码给出错误

python - 为 Scrapy 安装依赖包

python - 为什么 request.files 中 if request.method == 'POST' 和 'photo'

python - 错误值Error : cannot reindex from a duplicate axis because of concatenating dataframes

Python - 将 numpy 数组分解为正分量和负分量

python-2.7 - 使用命名空间时如何导入函数?

multithreading - 在计算时保持GIF动画运行