如何重新初始化嵌入式 Python 解释器?

Posted

技术标签:

【中文标题】如何重新初始化嵌入式 Python 解释器?【英文标题】:How to reinitialise an embedded Python interpreter? 【发布时间】:2011-12-06 23:05:51 【问题描述】:

我正在努力将 Python 嵌入到我们的测试套件应用程序中。目的是使用 Python 运行多个测试脚本来收集数据并制作测试报告。一个测试运行的多个测试脚本可以创建可在下一个脚本中使用的全局变量和函数。

应用程序还提供了嵌入解释器中导入的扩展模块,用于与应用程序交换一些数据。

但用户也可以进行多次测试运行。我不想在多个测试运行之间共享这些全局变量、导入和交换数据。我必须确保以真正的状态重新启动以控制测试环境并获得相同的结果。

我应该如何重新初始化解释器?

我使用了 Py_Initialize() 和 Py_Finalize(),但在第二次初始化我提供给解释器的扩展模块时,在第二次运行时出现异常。 还有文档warns against using it more than once。

使用sub-interpreters 似乎与扩展模块初始化有相同的注意事项。

我怀疑我的扩展模块初始化有问题,但我担心第 3 方扩展模块也会出现同样的问题。

也许可以通过在它自己的进程中启动解释器来让它工作,以确保所有内存都被释放。

顺便说一句,我正在使用 boost-python,它也使用 Py_Finalize 警告 AGAINST!

有什么建议吗?

谢谢

【问题讨论】:

【参考方案1】:

这是我发现的另一种实现我想要的方法,从解释器中的一个干净的开始。

我可以控制我用来执行代码的全局和本地命名空间:

// get the dictionary from the main module
// Get pointer to main module of python script
object main_module = import("__main__");
// Get dictionary of main module (contains all variables and stuff)
object main_namespace = main_module.attr("__dict__");

// define the dictionaries to use in the interpreter
dict global_namespace;
dict local_namespace;

// add the builtins
global_namespace["__builtins__"] = main_namespace["__builtins__"];

然后我可以使用命名空间来执行pyCode中包含的代码:

exec( pyCode, global_namespace, lobaca_namespace );

当我想运行我的测试的新实例时,我可以通过清理字典来清理命名空间:

// empty the interpreters namespaces
global_namespace.clear();
local_namespace.clear();        

// Copy builtins to new global namespace
global_namespace["__builtins__"] = main_namespace["__builtins__"];

根据我想要执行的级别,我可以使用 global = local

【讨论】:

【参考方案2】:

code.IteractiveInterpreter怎么样?

应该这样做:

#include <boost/python.hpp>
#include <string>
#include <stdexcept>

using namespace boost::python;

std::string GetPythonError()

    PyObject *ptype = NULL, *pvalue = NULL, *ptraceback = NULL;
    PyErr_Fetch(&ptype, &pvalue, &ptraceback);
    std::string message("");
    if(pvalue && PyString_Check(pvalue)) 
        message = PyString_AsString(pvalue);
    
    return message;


// Must be called after Py_Initialize()
void RunInterpreter(std::string codeToRun)

    object pymodule = object(handle<>(borrowed(PyImport_AddModule("__main__"))));
    object pynamespace = pymodule.attr("__dict__");

    try 
        // Initialize the embedded interpreter
        object result = exec(   "import code\n"
                                "__myInterpreter = code.InteractiveConsole() \n", 
                                pynamespace);
        // Run the code
        str pyCode(codeToRun.c_str());
        pynamespace["__myCommand"] = pyCode;
        result = eval("__myInterpreter.push(__myCommand)", pynamespace);
     catch(error_already_set) 
        throw std::runtime_error(GetPythonError().c_str());
    

【讨论】:

所以基本上,我应该实例化一次我的 Python 解释器,并使用这个解释器来启动子解释器,它们都有自己的命名空间?看起来是一个可行的解决方案,如果这些子解释器不像 Py_NewInterpreter 那样受到同样的警告。我将对此进行详细研究并进行试验。谢谢! 你明白了。每次实例化 InteractiveInterpreter 都会为您提供一个全新的环境。我不确定从父解释器继承的规则是什么,但这应该很容易控制。 似乎在做我想做的事。请注意,它对于模块初始化具有相同的缺点(它们只初始化一次)。但是清理命名空间效果很好。谢谢!【参考方案3】:

我会编写另一个 shell 脚本,每次都使用新的 python 实例执行测试脚本序列。或者像python这样写

# run your tests in the process first
# now run the user scripts, each in new process to have virgin env
for script in userScript:
    subprocess.call(['python',script])

【讨论】:

以上是关于如何重新初始化嵌入式 Python 解释器?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 tkinter 在 python 中嵌入 python 解释器框架?

python - C++ 嵌入式解释器和对象

从 C++ 嵌入式解释器捕获 python 窗口输出

使用 c++ 对象的全局实例扩展嵌入式 python 解释器

Matplotlib 和 C++ 中嵌入式 python 的子解释器

使用 pybind11 通过预先存在的嵌入式 python 解释器公开 C++ 功能