来自 Python BufferedIO 对象的 C `FILE` 流

C `FILE` stream from Python BufferedIO object

我正在为需要 FILE * 句柄作为输入的 C 库函数编写 Python 绑定。

我希望 Python 调用者将一个打开的 io.BufferedReader 对象传递给函数,以保留对句柄的控制,例如:

with open(fname, 'rb') as fh:
    my_c_function(fh)

因此,我不想在C函数内部传递文件名和打开句柄。

我的 C 包装器大致如下所示:

PyObject *my_c_function (PyObject *self, PyObject *args)
{
    FILE *fh;
    if (! PyArgs_ParseTuple (args, "?", &fh)) return NULL;
    my_c_lib_function (fh);
    // [...]
}

显然我无法弄清楚我应该为 "?" 使用什么符号,或者我是否应该使用与 PyArgs_ParseTuple 不同的方法。 Python C API 文档似乎没有提供任何关于如何处理缓冲 IO 对象的示例(据我了解,Buffer 协议适用于字节对象和 co.... 对吗?)

似乎我可以在我的 C 包装器中查看 Python 句柄对象的文件描述符(就像调用 fileno())并使用 [=18 从中创建一个 C 文件句柄=].

几个问题:

  1. 这是最方便的方式吗?还是Python C API中有我没有看到的内置方法?
  2. fileno() 文档提到:“Return 流的底层文件描述符(整数)(如果存在)。如果 IO 对象不使用文件描述符,则会引发 OSError。 “在什么情况下会发生这种情况?如果我传递由 open() 以外的人在 Python 中创建的文件句柄怎么办?
  3. 在Python打开的只读fd上打开一个只读的C句柄似乎很安全,应该保证在C函数之后关闭句柄;但是,有人能想到这种方法有什么缺陷吗?

不确定这是否是最合理的方式,但我在 Linux 中通过以下方式解决了它:

static PyObject *
get_fh_from_python_fh (PyObject *self, PyObject *args)
{
    PyObject *buf, *fileno_fn, *fileno_obj, *fileno_args;
    if (! PyArg_ParseTuple (args, "O", &buf)) return NULL;

    // Get the file descriptor from the Python BufferedIO object.
    // FIXME This is not sure to be reliable. See
    // https://docs.python.org/3/library/io.html#io.IOBase.fileno
    if (! (fileno_fn = PyObject_GetAttrString (buf, "fileno"))) {
        PyErr_SetString (PyExc_TypeError, "Object has no fileno function.");
        return NULL;
    }
    fileno_args = PyTuple_New(0);
    if (! (fileno_obj = PyObject_CallObject (fileno_fn, fileno_args))) {
        PyErr_SetString (PyExc_SystemError, "Error calling fileno function.");
        return NULL;
    }
    int fd = PyLong_AsSize_t (fileno_obj);

    /*
     * From the Linux man page:
     *
     * > The file descriptor is not dup'ed, and will be closed when the stream
     * > created by fdopen() is closed. The result of applying fdopen() to a
     * > shared memory object is undefined.
     *
     * This handle must not be closed. Leave open for the Python caller to
     * handle it.
     */
    FILE *fh = fdopen (fd, "r");

    // rest of the code...
}

这只考虑了 Linux,但到目前为止它做了它需要做的事情。更好的方法是深入了解 BufferedReader 对象,甚至可能在其中找到 FILE *;但如果这不是 Python API 的一部分,它可能会在未来的版本中被破坏。