Получите детали исходного скрипта, аналогично inspect.getmembers(), без импорта скрипта
Я пытаюсь получить источник, список вызываемых, значения по умолчанию, ключевые слова, аргументы и переменные функций в скрипте Python.
В настоящее время я импортирую модуль и использую питон inspect
модуля getmembers
функция и прохождение isfunction
параметр вроде так:
members = inspect.getmembers(myModule, inspect.isfunction)
Однако этот метод не работает, если myModule
импорт не доступен для меня (так как myModule
должен быть импортирован первым).
Я пробовал использовать питон ast
модуль для parse
а также dump
синтаксическое дерево, но получение источника функции включало очень хакерские методы и / или сомнительные и далекие от поддерживаемых сторонних библиотек. Я полагаю, что я тщательно изучил документацию и стекопоток и не смог найти подходящего решения. Я что-то пропустил?
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))
Выход:
{
"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": []
}
}
Возможный обходной путь состоит в том, чтобы обезьяна __import__
функция с пользовательской функцией, которая никогда не выдает ImportError и возвращает вместо этого фиктивный модуль:
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 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()
), импорт myModule
преуспеет.