从源代码字符串中提取Python函数源文本。
从源代码字符串中提取Python函数源文本。
假设我有一个有效的Python源代码字符串:
code_string = """ # A comment. def foo(a, b): return a + b class Bar(object): def __init__(self): self.my_list = [ 'a', 'b', ] """.strip()
目标:我想获取包含函数定义源代码的行,同时保留空白。对于上面的代码字符串,我想获取以下字符串:
def foo(a, b): return a + b
和
def __init__(self): self.my_list = [ 'a', 'b', ]
或者,同样地,我希望获取代码字符串中函数的行号:foo
跨越第2-3行,__init__
跨越第5-9行。
尝试:
我可以将代码字符串解析为其AST:
code_ast = ast.parse(code_string)
我可以找到FunctionDef
节点,例如:
function_def_nodes = [node for node in ast.walk(code_ast) if isinstance(node, ast.FunctionDef)]
每个FunctionDef
节点的lineno
属性告诉我们该函数的第一行。我们可以通过以下方法估计该函数的最后一行:
last_line = max(node.lineno for node in ast.walk(function_def_node) if hasattr(node, 'lineno'))
但是,当函数以不显示为AST节点的句法元素结尾时,例如__init__
中的最后一个]
,这种方法并不完美。
我怀疑只使用AST就能找到解决方法,因为在像__init__
这样的情况下,AST基本上没有足够的信息。
我不能使用inspect
模块,因为它只适用于“活动对象”,而我只有Python代码的字符串形式。我不能使用eval
来运行代码,因为那将带来巨大的安全隐患。
理论上,我可以编写一个Python解析器,但那似乎太过复杂了。
在评论中提到的一种启发式方法是使用行的前导空白。然而,这对于具有奇怪缩进的奇怪但有效的函数可能会出错,比如:
def baz(): return [ 1, ] class Baz(object): def hello(self, x): return self.hello( x - 1) def my_type_annotated_function( my_long_argument_name: SomeLongArgumentTypeName ) -> SomeLongReturnTypeName: # This function's indentation isn't unusual at all. pass