从源代码字符串中提取Python函数源文本。

9 浏览
0 Comments

从源代码字符串中提取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

0