获取源脚本详细信息,类似于 inspect.getmembers() 而无需导入脚本

Get source script details, similar to inspect.getmembers() without importing the script

我正在尝试获取 python 脚本中函数的源代码、被调用者列表、默认值、关键字、参数和可变参数。

目前,我正在导入模块并使用 python inspect 模块的 getmembers 函数并传递 isfunction 参数,如下所示:

members = inspect.getmembers(myModule, inspect.isfunction)

但是,如果 myModule 的导入对我不可用(因为必须先导入 myModule),此方法无效。

我尝试将 python ast 模块用于 parsedump 语法树,但是获取函数源涉及非常复杂的技术 and/or有问题并且远非可维护的第三方库。我相信我已经非常彻底地搜索了文档和 Whosebug,但未能找到合适的解决方案。我错过了什么吗?

一个可能的解决方法是用一个永远不会抛出 ImportError 的自定义函数和 returns 一个虚拟模块来代替 __import__ 函数:

import builtins

def force_import(module):
    original_import = __import__

    def fake_import(*args):
        try:
            return original_import(*args)
        except ImportError:
            return builtins
    builtins.__import__ = fake_import

    module = original_import(module)

    builtins.__import__ = original_import
    return module

这将允许您导入 myModule,即使其依赖项无法导入。然后你可以像往常一样使用 inspect.getmembers:

myModule = force_import('myModule')
members = inspect.getmembers(myModule, inspect.isfunction)

此解决方案的一个问题是它只能解决导入失败的问题。如果 myModule 尝试访问导入模块的任何成员,其导入将失败:

# myModule.py

import this_module_doesnt_exist # works

print(this_module_doesnt_exist.variable) # fails
force_import('myModule')
# AttributeError: module 'builtins' has no attribute 'variable'

为了解决这个问题,您可以创建一个永远不会抛出 AttributeError 的虚拟 class:

class DummyValue:
    def __call__(self, *args, **kwargs):
        return self

    __getitem__ = __setitem__ = __delitem__ = __call__
    __len__ = __length_hint__ = __bool__ = __call__
    __iter__ = __next__ = __call__
    __getattribute__ = __call__
    __enter__ = __leave__ = __call__
    __str__ = __repr__ = __format__ = __bytes__ = __call__
    # etc

(请参阅 the data model documentation 以获取您可能必须实施的双下注方法列表。)

现在如果 force_import returns 这个 class 的实例(将 return builtins 更改为 return DummyValue()),导入 myModule 将会成功。

所以我环顾四周,很快就找到了 Frankenstein'd 使用 this dude's answer 获取每个函数源的解决方案。它还远未接近完美,但如果您有兴趣,请看这里:

import ast
import re
import json


st = open('filename.py').read()
tree = ast.parse(st)
functions_info = {}


def parse(function):
    global st
    global functions_info
    fn_info = {}
    fn_info['Args'] = []
    fn_info['Source'] = []
    fn_info['Callees'] = []

    print(function.name)

    for arg in function.args.args:
        fn_info['Args'].append(arg.arg)

    lastBody = function.body[-1]

    while isinstance (lastBody,(ast.For,ast.While,ast.If)):
        lastBody = lastBody.Body[-1]
    lastLine = lastBody.lineno

    if isinstance(st,str):
        st = st.split("\n")
    for i , line in enumerate(st,1):
        if i in range(function.lineno,lastLine+1):
            # print(line)
            fn_info['Source'].append(line)

    for line in fn_info['Source']:
        if not line.lstrip().startswith('#'):
            fn_pattern = r'(\w+)\('
            match = re.search(fn_pattern, line)
            if match:
                callee = match.group(1)
                fn_info['Callees'].append(callee)

    functions_info[function.name] = fn_info

for obj in tree.body:
    if isinstance(obj, ast.ClassDef):
        for func in obj.body:
            if isinstance(func, (ast.FunctionDef)):
                parse(func)

    if isinstance(obj, ast.FunctionDef):
        parse(obj)

print(json.dumps(functions_info, indent=4))

输出:

{
    "displayWonder": {
        "Source": [
            "    def displayWonder(self):",
            "        return \"Hello \" + str(self.displayGreeting())"
        ],
        "Args": [
            "self"
        ],
        "Callees": []
    },
    "displayGreeting": {
        "Source": [
            "    def displayGreeting(self):",
            "        string = \"Greetings \" + self.myName",
            "        return string"
        ],
        "Args": [
            "self"
        ],
        "Callees": []
    },
    "myStatic": {
        "Source": [
            "    @staticmethod",
            "    def myStatic():",
            "        return \"I am static\""
        ],
        "Args": [],
        "Callees": []
    },
    "displayHello": {
        "Source": [
            "    def displayHello(self):",
            "        return \"Hello \" + self.myName"
        ],
        "Args": [
            "self"
        ],
        "Callees": []
    },
    "__init__": {
        "Source": [
            "    def __init__(self):",
            "        self.myName = 'Wonder?'"
        ],
        "Args": [
            "self"
        ],
        "Callees": []
    },
    "main": {
        "Source": [
            "def main():",
            "    hello = Hello(\"Wonderful!!!\")",
            "    # name = unicode(raw_input(\"Enter name: \"), 'utf8')",
            "    # print(\"User specified:\", name)",
            "    print(hello.displayGreeting())"
        ],
        "Args": [],
        "Callees": []
    }
}