获取源脚本详细信息,类似于 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
模块用于 parse
和 dump
语法树,但是获取函数源涉及非常复杂的技术 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": []
}
}
我正在尝试获取 python 脚本中函数的源代码、被调用者列表、默认值、关键字、参数和可变参数。
目前,我正在导入模块并使用 python inspect
模块的 getmembers
函数并传递 isfunction
参数,如下所示:
members = inspect.getmembers(myModule, inspect.isfunction)
但是,如果 myModule
的导入对我不可用(因为必须先导入 myModule
),此方法无效。
我尝试将 python ast
模块用于 parse
和 dump
语法树,但是获取函数源涉及非常复杂的技术 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": []
}
}