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

时间:2017-08-14 23:07:33

标签: python import abstract-syntax-tree inspect

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

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

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

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

我尝试使用python ast模块到parsedump语法树,但是获取函数源涉及非常hacky技术和/或可疑而且远离可维护的第三方库。我相信我已经彻底搜索了文档和stackoverflow,但未能找到合适的解决方案。我错过了什么吗?

2 个答案:

答案 0 :(得分:0)

一种可能的解决方法是使用自定义函数对: test -| a b | a b + ; 函数进行monkeypatch,该函数从不抛出ImportError并返回虚拟模块:

__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

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

force_import('myModule')
# AttributeError: module 'builtins' has no attribute 'variable'

(有关您可能必须实施的dunder方法的列表,请参阅this question。)

现在,如果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 返回此类的实例(将force_import更改为return builtins),则导入return DummyValue()将会成功。

答案 1 :(得分:0)

所以我更多地环顾四周并迅速使用this dude's answer解决了每个函数的来源。它还没有接近完美,但是如果你感兴趣的话就在这里:

===========================================================================
---Run main --
===========================================================================
./stackoverflow/libcallback_strings/lib/libcallback_strings.exe
Shut up, K-9!
show_string mystring[my test string] gTestStr[my test string]
cbfnString test_str[my test string] gTestStr[my test string]
expect[expect test:1==1]
cbfnExpect format[expect test:%s] format.length[14]
cbfnExpect res[expect test:(null)] gExpectResultStr[expect test:1==1]
**
ERROR:/media/george/SharedData/Projects/Vala/LanguageServer/stackoverflow/libcallback_strings/main.vala:15:cbfnExpect: assertion failed: (res == gExpectResultStr)
stackoverflow/so_examples.mk:252: recipe for target 'libcallback_strings' failed
make: *** [libcallback_strings] Aborted
The terminal process terminated with exit code: 2

输出:

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))