我编写了一个库,使用 justast
和inspect
库来解析和发出 [在 Python < 3.9 上使用astor ] 内部 Python 构造。
刚刚意识到我真的需要保留评论。最好不要求助于RedBaron或LibCST;因为我只需要发出未更改的评论;是否有一种简洁的方式来保留注释解析/发出 Python 源代码,仅使用 stdlib?
我最终做的是编写一个简单的解析器,在 339 个源代码行中没有元语言: https ://github.com/offscale/cdd-python/blob/master/cdd/cst_utils.py
if line.lstrip().startswith("#") or line not endswith('\\') and balanced_parens(line)
else 继续咀嚼,直到该条件为真……加上多行字符串等周围的一些边缘情况;namedtuple
具有value
属性的列表。ast
ast
将要修改(而不是删除)的节点限制为: { ClassDef
、AsyncFunctionDef
、FunctionDef
} 文档字符串(第一个正文元素Constant
| Str
)Assign
和AnnAssign
;cst_idx, cst_node = find_cst_at_ast(cst_list, _node)
;maybe_replace_doc_str_in_function_or_class(_node, cst_idx, cst_list)
cst_list
仅包含对上述节点的更改,并且仅当更改超过空格时,才能创建为"".join(map(attrgetter("value"), cst_list))
用于输出eval
或直接输出到源文件的字符串(例如,就地覆盖)。match
/case
是支持的,但如果引入了新语法,它 [尚未?] 支持......至少不会自动支持);可以通过使用标记器捕获它们将它们合并回生成的源代码来保留注释。
给定程序变量中的玩具程序,我们可以演示注释如何在 AST 中丢失:
import ast
program = """
# This comment lost
p1v = 4 + 4
p1l = ['a', # Implicit line joining comment for a lost
'b'] # Ending comment for b lost
def p1f(x):
"p1f docstring"
# Comment in function p1f lost
return x
print(p1f(p1l), p1f(p1v))
"""
tree = ast.parse(program)
print('== Full program code:')
print(ast.unparse(tree))
输出显示所有评论都消失了:
== Full program code:
p1v = 4 + 4
p1l = ['a', 'b']
def p1f(x):
"""p1f docstring"""
return x
print(p1f(p1l), p1f(p1v))
但是,如果我们使用分词器扫描评论,我们可以使用它来合并评论:
from io import StringIO
import tokenize
def scan_comments(source):
""" Scan source code file for relevant comments
"""
# Find token for comments
for k,v in tokenize.tok_name.items():
if v == 'COMMENT':
comment = k
break
comtokens = []
with StringIO(source) as f:
tokens = tokenize.generate_tokens(f.readline)
for token in tokens:
if token.type != comment:
continue
comtokens += [token]
return comtokens
comtokens = scan_comments(program)
print('== Comment after p1l[0]\n\t', comtokens[1])
输出(编辑为分割长线):
== Comment after p1l[0]
TokenInfo(type=60 (COMMENT),
string='# Implicit line joining comment for a lost',
start=(4, 12), end=(4, 54),
line="p1l = ['a', # Implicit line joining comment for a lost\n")
使用稍微修改的版本ast.unparse()
,替换方法maybe_newline()
和traverse()
修改版本,您应该能够使用来自评论扫描器(开始变量)的位置信息,结合来自 AST 的位置信息,在它们的大致位置合并所有评论; 大多数节点都有一个lineno
属性。
不完全是。参见例如列表变量赋值。源代码分为两行,但ast.unparse()
只生成一行(参见第二个代码段中的输出)。
此外,您需要确保ast.increment_lineno()
在添加代码后使用更新 AST 中的位置信息。
似乎
maybe_newline()
库代码(或其替换)中可能需要更多调用。