如何在Python中重定向stderr?通过Python C API?

4 投票
2 回答
4545 浏览
提问于 2025-04-15 17:19

这是我最近两个问题的结合:
[1] 在C语言中使用Python实例方法
[2] 如何在Python中重定向错误输出(stderr)?

我想记录一个Python脚本的标准输出(stdout)和错误输出(stderr)。

我想问的是,根据[1]创建一个新类型似乎有点复杂。如果不需要把这个新类型暴露给Python,也就是说它只存在于C语言中,这样会不会简单一些呢?

我的意思是,当Python打印东西的时候,它会去“Objects/fileobject.c”这个文件,在“PyFile_WriteObject”函数里,它会检查是否可以写入它的参数:

writer = PyObject_GetAttrString(f, "write");
if (writer == NULL)
...

另外,可以这样获取标准输出和错误输出:

PyObject* out = PySys_GetObject("stdout");
PyObject* err = PySys_GetObject("stderr");

我的问题是,是否有可能构造一个必要的PyObject,使其满足上面的'PyObject_GetAttrString(f, "write")',并且可以被调用,这样我就可以写:

PySys_SetObject("stdout", <my writer object / class / type / ?>);

http://docs.python.org/c-api/sys.html?highlight=pysys_setobject#PySys_SetObject

这样的话,就不需要把新的“写入类型”暴露给其他Python脚本,所以我觉得写代码可能会简单一些……?

2 个回答

8

根据Alex的回答,这里有一段完整的C代码,不需要Python中的“import aview”,适用于Python 3(所以不需要Py_InitModule),并且包含了标准错误输出的重定向:

#include <functional>
#include <iostream>
#include <string>
#include <Python.h>


PyObject* aview_write(PyObject* self, PyObject* args)
{
    const char *what;
    if (!PyArg_ParseTuple(args, "s", &what))
        return NULL;
    printf("==%s==", what);
    return Py_BuildValue("");
}


PyObject* aview_flush(PyObject* self, PyObject* args)
{
    return Py_BuildValue("");
}


PyMethodDef aview_methods[] =
{
    {"write", aview_write, METH_VARARGS, "doc for write"},
    {"flush", aview_flush, METH_VARARGS, "doc for flush"},
    {0, 0, 0, 0} // sentinel
};


PyModuleDef aview_module =
{
    PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base;
    "aview",               // const char* m_name;
    "doc for aview",       // const char* m_doc;
    -1,                    // Py_ssize_t m_size;
    aview_methods,        // PyMethodDef *m_methods
    //  inquiry m_reload;  traverseproc m_traverse;  inquiry m_clear;  freefunc m_free;
};

PyMODINIT_FUNC PyInit_aview(void) 
{
    PyObject* m = PyModule_Create(&aview_module);
    PySys_SetObject("stdout", m);
    PySys_SetObject("stderr", m);
    return m;
}


int main()
{
    PyImport_AppendInittab("aview", PyInit_aview);
    Py_Initialize();
    PyImport_ImportModule("aview");

    PyRun_SimpleString("print(\'hello to buffer\')");
    PyRun_SimpleString("make a SyntaxException in stderr");

    Py_Finalize();

    return 0;

}

不过要注意,如果你打算使用多个不同的解释器,这样的做法是不够的,因为aview_write无法知道该把内容追加到哪个缓冲区。你需要一些像这样的东西

顺便提一下,这里有一个很棒的参考资料,教你如何添加新的模块和类型。

12

你只需要创建一个模块对象(如果你在使用C API,其实你已经在做这件事了!),然后给它一个合适的 write 函数——这个模块对象就可以作为 PySys_SetObject 的第二个参数。

在我回答你另一个问题时,我提到了 xxmodule.c,这是Python C源代码中的一个示例文件,里面有很多例子,包括各种类型和函数——即使你觉得“创建新类型”这一部分太难,也可以从这里入手;这让我有点困惑,但没关系;-)。

编辑:这里有一个简单的工作示例 (aview.py):

#include "Python.h"
#include <stdio.h>

static PyObject *
aview_write(PyObject *self, PyObject *args)
{
    const char *what;
    if (!PyArg_ParseTuple(args, "s", &what))
        return NULL;
    printf("==%s==", what);
    return Py_BuildValue("");
}

static PyMethodDef a_methods[] = {
    {"write", aview_write, METH_VARARGS, "Write something."},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
initaview(void)
{
    PyObject *m = Py_InitModule("aview", a_methods);
    if (m == NULL) return;
    PySys_SetObject("stdout", m);
}

一旦这个 aview 模块正确安装后:

$ python
Python 2.5.4 (r254:67917, Dec 23 2008, 14:57:27) 
[GCC 4.0.1 (Apple Computer, Inc. build 5363)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import aview
>>> print 'ciao'
==ciao====
==>>> 

...任何输出到标准输出的字符串都会被加上 == 符号包围(而且这个 print 调用 .write 两次:第一次是 'ciao',然后再加上一个换行符)。

撰写回答