最近一直在搗鼓Python,老想學別人在自己的SolidMCP之類搞一個Python Interpreter。
起初覺得很高深,就直接把將某開源軟體基於Qt的Python Console實現剝離開來,成功移植到SolidMCP內。
在這個過程中發現,其實寫一個蹩腳的Python Interpreter並不是太難,Piaoger決定沉下心來,研究一把,並把研究成果記錄下來如下:
>> 如何寫一個Python Interpreter
可以先用Python來寫一個Prototype,再用C++翻譯過來就是啦。
import sysimport osimport coderedirectedOutput = r'C:\RedirectedPythonConsole.txt'# Redirected Channelclass MyConsole: def write(self, input): myFile = file(redirectedOutput, 'a') output = '\nPython>' + input myFile.write(output) myFile.close()# Redirect stdout to MyConsoledefaultStdOut = sys.stdoutmyConsole = MyConsole()sys.stdout = myConsoleif os.path.exists(redirectedOutput): os.remove(redirectedOutput) # Compile input code to code object# Run Code ObjectinputCode = '2+3'interpreter = code.InteractiveInterpreter()codeObject = interpreter.compile(inputCode)interpreter.runcode(codeObject)sys.stdout = myConsole
>>C++版本原型
// Demonstrates how to use Interactive Interpreter and Interactive Console void testInterpreter() { Py_Initialize(); // #load code module to use InteractiveInterpreter and InteractiveConsole // # Utilities used to emulate Python's interactive interpreter // # InteractiveConsole closely emulates the behavior of the interactive Python interpreter // # InteractiveConsole builds on InteractiveInterpreter // import code module PyObject* module = PyImport_ImportModule("code"); // Construct InteractiveInterpreter from code module PyObject* func = PyObject_GetAttrString(module, "InteractiveInterpreter"); PyObject* args = Py_BuildValue("()"); PyObject* interpreter = PyEval_CallObject(func,args); // Provide input as code const char* source = "2+3"; PyObject* sourceArgs = Py_BuildValue("(s)", source); PyObject* compilefunc = PyObject_GetAttrString(interpreter, "compile"); PyObject* codeObject = PyEval_CallObject(compilefunc, sourceArgs); // run compiled bytecode PyObject* mainModule = PyImport_AddModule("__main__"); PyObject* dict = PyModule_GetDict(mainModule); PyObject* presult = PyEval_EvalCode((PyCodeObject*)codeObject, dict, dict); // Finally, release everything by decrementing their reference counts. // Py_DECREF(mainModule); Py_DECREF(presult); Py_DECREF(dict); Py_DECREF(codeObject); Py_DECREF(compilefunc); Py_DECREF(sourceArgs); Py_DECREF(interpreter); Py_DECREF(args); Py_DECREF(func); Py_Finalize(); }
基本上原理就是這樣的,其餘的事情不外乎也搞一個sys.stdout的重新導向,然後就是處理TextEdit控制項的事件啦。
>> 利用InteractiveConsole直接用Python寫
Python提供了一個InteractiveConsole幫我們來幹這個事情,這玩意其實也是繼承自InteractiveInterpreter.
那些基於PyQt的Python Console大抵就是這樣乾的。
# -----------------------------------------------------------------------------------------------
# Copy from ActiveState Code Recipes
# http://code.activestate.com/recipes/355319-using-codeinteractiveconsole-to-embed-a-python-she/
# -----------------------------------------------------------------------------------------------
import sysimport codefrom code import InteractiveConsoleclass FileCacher: "Cache the stdout text so we can analyze it before returning it" def __init__(self): self.reset() def reset(self): self.out = [] def write(self,line): self.out.append(line) def flush(self): output = '\n'.join(self.out) self.reset() return outputclass Shell(InteractiveConsole): "Wrapper around Python that can filter input/output to the shell" def __init__(self): self.stdout = sys.stdout self.cache = FileCacher() InteractiveConsole.__init__(self) return def get_output(self):
sys.stdout = self.cache
def return_output(self):
sys.stdout = self.stdout def push(self,line): self.get_output() # you can filter input here by doing something like # line = filter(line) InteractiveConsole.push(self,line) self.return_output() output = self.cache.flush() # you can filter the output here by doing something like # output = filter(output) print output # or do something else with it return if __name__ == '__main__': sh = Shell() sh.interact()