如何在使用 python c-api 调用 PyErr_Print 后访问 sys.stderr

How to access sys.stderr after calling PyErr_Print with python c-api

我正在尝试通过 dll 导入从 c# 项目中使用 python c-api。

我在导入一些我认为是内置的模块时收到 ModuleNotFoundError。 (注:我自己编译的python)

我现在有点卡住了,但我希望在下面的代码中调用 PyErr_Print() 时能得到一些额外的信息。

代码:

IntPtr modulePtr = NativeInterface.PyImport_ExecCodeModuleEx(moduleName,compiledModule, path);
if (modulePtr == IntPtr.Zero)
{
  NativeInterface.PyErr_Print();
  PythonException exception = PythonException.Query();
  throw exception;
}

PyErr_Print 的文档声明它将用一些错误信息填充 sys.stderr。 从我的 C# 应用程序读取此变量的最简单方法是什么?

我找不到通过 C-Api 访问 sys.stderr 的方法。但我意识到我可以通过 c-api 运行 python 脚本(参见文档中的 PyRun_String )。 所以我现在通过将 sys.path 写入文本文件来进行调试。

import sys
file = open('log.txt','w')
for path in sys.path:
    file.write(i+'\n')

这个答案给出了 C 代码,因为我了解 C 而不是 C#,但我认为它应该是相当可移植的。

默认情况下 sys.stderr 写入某处的某个控制台,因此您无法有意义地尝试从中读取。但是,完全有可能替换它以重定向输出。两个明智的选择包括写入文件和写入稍后可以查询的 StringIO 对象。

运行 的 C 代码基本上等同于:

import sys
from io import StringIO # Python 3
sys.stderr = StringIO()

或在 C:

int setup_stderr() {
    PyObject *io = NULL, *stringio = NULL, *stringioinstance = NULL;

    int success = 0;

    io = PyImport_ImportModule("io");
    if (!io) goto done;
    stringio = PyObject_GetAttrString(io,"StringIO");
    if (!stringio) goto done;
    stringioinstance = PyObject_CallFunctionObjArgs(stringio,NULL);
    if (!stringioinstance) goto done;

    if (PySys_SetObject("stderr",stringioinstance)==-1) goto done;

    success = 1;

    done:
    Py_XDECREF(stringioinstance);
    Py_XDECREF(stringio);
    Py_XDECREF(io);
    return success;
}

你 运行 这一次在你的程序开始时。

要查询 sys.stderr 的内容,您需要执行以下操作:

value = sys.stderr.getvalue()
encoded_value = value.encode() # or you could just handle the unicode output

在 C:

char* get_stderr_text() {
    PyObject* stderr = PySys_GetObject("stderr"); // borrowed reference

    PyObject *value = NULL, *encoded = NULL;

    char* result = NULL;
    char* temp_result = NULL;
    Py_ssize_t size = 0;

    value =  PyObject_CallMethod(stderr,"getvalue",NULL);
    if (!value) goto done;
    // ideally should get the preferred encoding
    encoded = PyUnicode_AsEncodedString(value,"utf-8","strict");
    if (!encoded) goto done;
    if (PyBytes_AsStringAndSize(encoded,&temp_result,&size) == -1) goto done;
    size += 1;

    // copy so we own the memory
    result = malloc(sizeof(char)*size);
    for (int i = 0; i<size; ++i) {
        result[i] = temp_result[i];
    }

    done:
    Py_XDECREF(encoded);
    Py_XDECREF(value);

    return result;

}

复制字符串需要付出一些努力。您可能会考虑直接使用 unicode 并使用 PyUnicode_AsUCS4Copy.

然后您可能想在写入字符串后清除它,只需将 sys.stderr 替换为新的 StringIO 对象即可。