从C调用IPython.embed()方法时,“ValueError:调用堆栈不够深”

时间:2017-03-23 11:00:46

标签: python c api ipython

我想从Python C API调用Python模块中定义的函数。例如,我想致电IPython.embed()。以下C代码导致运行时错误(安装了IPython的Miniconda 3)。

#include <Python.h>

int
main(int argc, char *argv[])
{
  Py_Initialize();
  PyObject *ipython = PyImport_ImportModule("IPython");
  if (ipython == NULL) {
    PyErr_Print();
    return 1;
  }
  PyObject *embed = PyUnicode_FromString("embed");
  if (embed == NULL) {
    PyErr_Print();
    return 1;
  }
  PyObject *result = PyObject_CallMethodObjArgs(ipython, embed);
  if (result == NULL) {
    PyErr_Print();
    return 1;
  }
  return 0;
}

观察到错误:

Traceback (most recent call last):
  File "[...]/miniconda3/lib/python3.5/site-packages/IPython/terminal/embed.py", line 381, in embed
    frame = sys._getframe(1)
ValueError: call stack is not deep enough

但是,如果我将IPythonembed替换为对其中定义了test函数的简单hello模块,则上述代码可以正常工作。< / p>

另一方面,以下代码按预期工作(即,它运行IPython REPL),但不如上面的代码灵活,不适合我的需要。

PyRun_SimpleString("\n\
  from IPython import embed\n\
  embed()\n\
");

以下代码也按预期工作,我最初提供的代码现在位于使用PyRun_SimpleString调用的回调中。

static PyObject *
callback_f(PyObject *obj, PyObject *args)
{
  PyObject *ipython = PyImport_ImportModule("IPython");
  if (ipython == NULL) {
    PyErr_Print();
    exit(1);
  }
  PyObject *embed = PyUnicode_FromString("embed");
  if (embed == NULL) {
    PyErr_Print();
    exit(1);
  }
  PyObject *result = PyObject_CallMethodObjArgs(ipython, embed);
  if (result == NULL) {
    PyErr_Print();
    exit(1);
  }
  Py_RETURN_NONE;
}

int
main(int argc, char *argv[])
{
  Py_Initialize();
  PyObject *module = PyImport_AddModule("test");
  if (module == NULL) {
    PyErr_Print();
    return 1;
  }
  PyMethodDef method_def;
  method_def.ml_name = "callback";
  method_def.ml_meth = callback_f;
  method_def.ml_flags = 1;
  method_def.ml_doc = NULL;
  PyObject *callback_obj = PyCFunction_New(&method_def, NULL);
  if (callback_obj == NULL) {
    PyErr_Print();
    return 1;
  }
  if (PyObject_SetAttrString(module, "callback", callback_obj) != 0) {
    PyErr_Print();
    return 1;
  }
  PyRun_SimpleString("\n\
  from test import callback\n\
  callback()\n\
");
}

因此,我认为PyRun_SimpleString执行与调用IPython.embed()所需的堆栈帧相关的初始化,但我看不到它的记录位置。

1 个答案:

答案 0 :(得分:1)

解决方案是插入使用PyFrame_New创建的新框架,但它不在文档化的Python C API中。

#include <Python.h>
#include <frameobject.h>

int
main(int argc, char *argv[])
{
  Py_Initialize();
  PyThreadState *tstate = PyThreadState_GET();
  if (tstate == NULL) {
    PyErr_Print();
    exit(1);
  }
  PyObject *main_module = PyImport_AddModule("__main__");
  if (main_module == NULL) {
    PyErr_Print();
    exit(1);
  }
  PyObject *main_dict = PyModule_GetDict(main_module);
  if (main_dict == NULL) {
    PyErr_Print();
    exit(1);
  }
  PyCodeObject *code_object = PyCode_NewEmpty("foo.py", "f", 0);
  if (code_object == NULL) {
    PyErr_Print();
    exit(1);
  }
  PyFrameObject *root_frame = PyFrame_New(tstate, code_object, main_dict, main_dict);
  if (root_frame == NULL) {
    PyErr_Print();
    exit(1);
  }
  tstate->frame = root_frame;
  PyObject *ipython = PyImport_ImportModule("IPython");
  if (ipython == NULL) {
    PyErr_Print();
    exit(1);
  }
  PyObject *embed = PyUnicode_FromString("embed");
  if (embed == NULL) {
    PyErr_Print();
    exit(1);
  }
  PyObject *result = PyObject_CallMethodObjArgs(ipython, embed);
  if (result == NULL) {
    PyErr_Print();
    exit(1);
  }
}