从Python源代码中提取注释

从Python源代码中提取注释,python,python-2.7,Python,Python 2.7,我正试图编写一个程序来提取用户输入的代码中的注释。我试图使用正则表达式,但发现很难写 然后我找到了一个帖子。答案建议使用tokenize.generate_tokens来分析语法,但是: generate_tokens()生成器需要一个参数readline,该参数 必须是可调用对象,该对象提供与 readline()内置文件对象的方法(请参阅“文件对象”一节) 但是字符串对象没有readline方法 然后我找到了另一篇帖子,建议使用StringIO.StringIO来获得readline方法。因

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

然后我找到了一个帖子。答案建议使用
tokenize.generate_tokens
来分析语法,但是:

generate_tokens()
生成器需要一个参数
readline
,该参数 必须是可调用对象,该对象提供与
readline()
内置文件对象的方法(请参阅“文件对象”一节)

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

然后我找到了另一篇帖子,建议使用
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
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
文件“ext.py”,第10行,摘录
对于toktype、tokval、begin、end、line in tokenize.generate_tokens(stringio):
文件“C:\Python27\lib\tokenize.py”,第294行,在generate\u tokens中
line=readline()
AttributeError:StringIO实例没有`\u调用``方法
我知道我可以编写一个新类,但有没有更好的解决方案?

回答更一般的情况(从模块、函数中提取): 模块: 文档规定需要提供一个可调用函数,它公开与内置文件对象的方法相同的接口。这提示:创建一个提供该方法的对象

对于模块,我们可以将一个新模块作为一个普通文件传递给它的
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
并创建一个生成器

(仅在Py3中--注意:Python3需要readline return
bytes
,因此需要在
'rb'
模式下打开文件)返回元素的命名元组,其中包含有关标记元素的信息。下面是一个小演示:

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 
因此,无论位置如何,都会检测到所有注释。当然不包括docstring

功能: 对于我真的想不出的情况,如果不打开
open
,也可以获得类似的结果。尽管如此,为了完整性起见,我将介绍另一种方法。在此场景中,您需要两个附加模块,
Python3
中的):

假设您具有以下功能:

def bar():
    # I am bar
    print "I really am bar"
    # bar bar bar baaaar
    # (bar)
    return "Bar"
您需要一个类似文件的对象,它有一个
readline
方法来与
标记化
一起使用。好的,您可以使用
StringIO.StringIO
str
创建一个类似文件的对象,并且您可以获得一个
str
来表示函数的源代码。代码:

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
创建一种自定义的方法,请看一看。它定义了注释的某些模式(
r'.[^\r\n]*'
)名称等,使用
readline
在行中循环,并在
列表中搜索模式。谢天谢地,在您看了一会儿之后,它并不太复杂:-)


函数
提取的应答
(更新): 您已使用
StringIO
创建了一个对象,该对象提供了接口,但尚未将该接口(
readline
)传递给
tokenize。生成\u tokens
,而是传递了完整对象(
StringIO

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

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 '

此外,正如我稍后提到的,Python3的
io
包含
StringIO
,因此在本例中,谢天谢地,
import
是不需要的。

向我们展示为您提供属性错误的代码。您找到了正确的解决方案。请显示一些代码,以便我们可以帮助您使其工作。(python 3.6)将fileObject.readline直接传递给tokenize.tokenize仅在以“rb”模式打开时才起作用,因为我猜tokenize需要readline()的bytestring。@philoj确实如此。显然在所有Python>3版本中。抢手货