从 .py 文件中提取 Python 函数名称、主体和文档类型

Extracting Python function names, bodies, and doctypes from a .py file

我已经使用 Python 编程多年,但是,我刚刚了解到您可以使用如下命令:

<function>.__doc__

到return Python 函数的文档字符串!但是,这仍然不足以完成我想要完成的任务。我需要能够提取特定 .py 文件中定义的每个函数的名称,并提取函数名称文档字符串和主体。例如,假设我有以下 .py 文件:

import numpy as np

def get_palindrome(string):
  """Returns the palindrome of the string argument"""
  return string[::-1]

def break_my_computer():
  """Destroys your RAM"""
  a = []
  while True:
    a.append(1)

这样应该可以return以下信息:

info = {1: {
            'name': 'get_palindrome',
            'docstring': 'Returns the palindrome of the string argument',
            'body': 'return string[::-1]'
            },
        2: {
            'name': 'break_my_computer',
            'docstring': 'Destroys your RAM',
            'body': 'a = []\nwhile True:\n  a.append(1)'
        }   }

在Python中获取此信息的最简单方法是什么(我最好不想使用正则表达式库或进行任何文本解析和匹配)。

注意:当遇到多行文档字符串或函数体时,\n(换行)命令应该出现在相应的输出中;制表符应由命令或空格表示。

正确答案是 - 不要这样做!

无论如何我都会去做。

你的问题的前两部分很简单,所以我先把它弄清楚:

import inspect

import module_name as module
# This bit is for if you want to load the module from a file by path, mutually exclusive with previous line
# import importlib
#
# spec = importlib.util.spec_from_file_location("module_name", "/path/to/module_name.py")
# module = importlib.util.module_from_spec(spec)
# spec.loader.exec_module(module)

funcs = []
for name, value in vars(module).items():
    if name.startswith("_") or not callable(value):
        continue
    doc = inspect.getdoc(value)
    code = marshal.dumps(value.__code__)
    funcs.append({"name": name, "docstring": doc, "body": code})

现在我们到了难点 - body。无法直接阅读源代码,因为 Python 根本不存储它。您当然可以使用 getsource 读取文件,但这不适用于自 python 加载后在磁盘上修改的模块。如果您想采用这种方法,请遵循以下代码:

import inspect

import module_name as module
# This bit is for if you want to load the module from a file by path, mutually exclusive with previous line
# import importlib
#
# spec = importlib.util.spec_from_file_location("module_name", "/path/to/module_name.py")
# module = importlib.util.module_from_spec(spec)
# spec.loader.exec_module(module)

funcs = []
for name, value in vars(module).items():
    if name.startswith("_") or not callable(value):
        continue
    doc = inspect.getdoc(value)
    code = inspect.getsource(value).split(":", maxsplit=1)[1]
    funcs.append({"name": name, "docstring": doc, "body": code})
print(funcs)

解决这个问题最好的办法就是根本不存储源代码。您应该改为使用 marshal 模块来序列化代码。这 在 python 的主要版本之间中断,并且有点丑陋。如果有任何方法可以避免存储函数代码,请这样做,因为它存在安全风险。

包括编组的完整代码:

import inspect
import marshal
import types

import module_name as module
# This bit is for if you want to load the module from a file by path, mutually exclusive with previous line
# import importlib
#
# spec = importlib.util.spec_from_file_location("module_name", "/path/to/module_name.py")
# module = importlib.util.module_from_spec(spec)
# spec.loader.exec_module(module)

funcs = []
for name, value in vars(module).items():
    if name.startswith("_") or not callable(value):
        continue
    doc = inspect.getdoc(value)
    code = marshal.dumps(value.__code__)
    funcs.append({"name": name, "docstring": doc, "body": code})

for value in funcs:
    name = value["name"]
    doc = value["docstring"]
    code = value["body"]
    # invoke all the functions
    func = types.FunctionType(marshal.loads(code), globals(), name)
    func.__doc__ = doc
    func()