У меня есть несколько модулей Cython. Некоторые из них будут динамически генерировать/преобразовывать исходный код, а некоторые будут генерировать/преобразовывать байт-код. Мне нужно иметь возможность конвертировать между каждым типом как можно больше (возможно, за исключением декомпиляции). На данный момент я просто работаю на уровне Python (т. е. только с исходным кодом и байт-кодом Python. Я не имею дело с C). Я хочу как можно лучше охватить все крайние случаи (поэтому, если я не смогу получить исходный код, я соглашусь на байт-код).
Обновление: текущий исходный код с пометкой ошибки. Я почти уверен, что ожидаемое поведение заключается в рекурсивном импорте и самоанализе до тех пор, пока не произойдет сбой, но когда импортируются динамически генерируемые модули, самоанализ завершается неудачно, и модуль не может быть получен.
Обновление: проблема, которую я пытаюсь решить:
- introspect() любой модуль, обрабатывающий все угловые случаи, чтобы получить исходный код или байт-код, и т. д.;
- затем выполните преобразование между исходным кодомASTCodeTypeбайт-кодом;
- затем загрузите новый модуль в процесс пространство.
import ast
from ast import AST
import dis
from io import StringIO
from inspect import currentframe, getmodule, getsource
import marshal
import sys
from types import ModuleType, FrameType, CodeType
from typing import Optional, Union
from bytecode import Bytecode
from uncompyle6.main import decompile
class IntrospectionError(Exception):
""" cannot answer: 'what am I?' """
def disassemble(module:ModuleType)->str:
result:StringIO = StringIO()
dis.dis(module, file=result) # TODO ModuleType to (MethodType | FunctionType | CodeType | type | Callable[..., Any] | str | bytes | bytearray | None)
return result.getvalue()
#print('disassemble', disassemble(getmodule(currentframe()))) # FIXME see import_code
def _introspect(cf:Optional[FrameType])->str:
if (cf is None):
raise IntrospectionError("Running on an implementation without Python stack frame support?")
cm:Optional[ModuleType] = getmodule(cf)
if (cm is None):
raise IntrospectionError("module is None. Dynamically generated?")
try:
return getsource(cm)
except OSError as e:
return disassemble(cm)
def introspect()->str:
""" returns the source code for the currently running module """
cf:Optional[FrameType] = currentframe()
return _introspect(cf)
print('introspect', introspect())
def compiler(code_string:Union[str,AST])->CodeType:
"""
compiles the code string to a code object
https://stackoverflow.com/questions/617 ... ode-object
"""
return compile(code_string, '', 'exec') # FIXME str | Buffer | Module | Expression | Interactive
AnalyzerSource :str = introspect()
AnalyzerCode :CodeType = compiler(AnalyzerSource) # source to code (compile)
AnalyzerAST :AST = ast.parse(AnalyzerSource) # source to AST (parse)
AnalyzerSource1:str = ast.unparse(AnalyzerAST) # AST to source (transpile)
AnalyzerCode1 :CodeType = compiler(AnalyzerAST) # AST to code (compile)
#AnalyzerAST1 :AST = ???(AnalyzerCode) # code to AST (decompile) # TODO
#AnalyzerSource2:str = ???(AnalyzerCode) # code to source (decompile) # TODO
#AnalyzerSource3:str = codegen.to_source(AnalyzerAST) # code to source (decompile) # FIXME AttributeError: 'Call' object has no attribute 'starargs'
AnalyzerBytes :Bytecode = Bytecode.from_code(AnalyzerCode) # code to bytecode (assemble?)
AnalyzerBytes1 :Bytecode = Bytecode.from_code(AnalyzerCode1) # code to bytecode (sanity check)
AnalyzerCode2 :CodeType = AnalyzerBytes .to_code() # bytecode to code (disassemble?)
AnalyzerCode3 :CodeType = AnalyzerBytes1.to_code() # bytecode to code (sanity check)
AnalyzerBytes2 :Bytecode = Bytecode.from_code(compiler(AnalyzerSource)) # source to bytecode (compile)
AnalyzerBytes3 :Bytecode = Bytecode.from_code(compiler(AnalyzerAST)) # AST to bytecode (compile)
#marshal.dumps(AnalyzerCode) # sanity check
#AnalyzerAST2 :AST = TODO # bytecode to AST (decompile) # TODO
#AnalyzerSource4:str = TODO # bytecode to source (decompile) # TODO
# code_to_bytecode() defined here: https://gist.github.com/stecman/3751ac4 ... id=3505634
#print(decompile(code_to_bytecode(marshal.dumps(AnalyzerCode), time.time()))) # FIXME does not smell like code
if sys.version_info >= (3,7): header_size = 16
elif sys.version_info >= (3,2): header_size = 12
else: header_size = 8
print(decompile(marshal.dumps(AnalyzerCode)[header_size:])) # FIXME also does not smell like code
#print(decompile(marshal.dumps(AnalyzerBytes))) # FIXME unmarshallable object
def import_code(code:Union[str,AST], module_name:str, type_checked:bool=True)->ModuleType: # FIXME KeyError for generated code
module:ModuleType = ModuleType(module_name)
module.__file__ = None
if type_checked:
# with install_import_hook(module_name): # FIXME different system different problem ???
exec(code, module.__dict__) # FIXME Argument 1 to "exec" has incompatible type "str | AST"; expected "str | Buffer | CodeType"
else:
exec(code, module.__dict__) # FIXME ^^^
sys.modules[module_name] = module
return module
#import_code(AnalyzerCode, 'analyzer2') # FIXME (with disassemble test) RuntimeError: no last traceback to disassemble
# FIXME (sans disassemble test) analyzer2.IntrospectionError: module is None...
#import_code(AnalyzerBytes, 'analyzer3') # FIXME TypeError: exec() arg 1 must be a string, bytes or code object
Подробнее здесь: https://stackoverflow.com/questions/787 ... ted-module