gpt4 book ai didi

python - 通过pybind11将字符串列表从python传递给C

转载 作者:行者123 更新时间:2023-12-04 10:38:11 26 4
gpt4 key购买 nike

关注 this post ,我想知道如何通过 Pybind11 将字符串列表从 Python 传递到 C(即,使用 C header 和语法,而不是 C++)。我完全知道 Pybind11 是一个 C++ 库,并且代码无论如何都必须由 C++ 编译器编译。但是,我很难理解 C++ 的实现,例如 herehere .

Here我试图通过指针传递一个 python 字符串列表,表示为整数,然后通过 long* 接收它们。在 C 中,但它没有用。

C/C++ 代码应该类似于:

// example.cpp
#include <stdio.h>
#include <stdlib.h>

#include <pybind11/pybind11.h>

int run(/*<pure C or pybind11 datatypes> args*/){

// if pybind11 data types are used convert them to pure C :
// int argc = length of args
// char* argv[] = array of pointers to the strings in args, possible malloc

for (int i = 0; i < argc; ++i) {
printf("%s\n", argv[i]);
}

// possible free

return 0;
}

PYBIND11_MODULE(example, m) {

m.def("run", &run, "runs the example");
}

一个简单的 CMakeLists.txt还提供了示例 here . Python 代码可以是这样的:
#example.py
import example

print(example.run(["Lorem", "ipsum", "dolor", "sit", "amet"]))

避免误会如 this ,请考虑以下几点:
  • 这不是 XY 问题,因为已经使用 C++ 头文件/标准库和语法(上面的链接)以正确/规范的方式解决了假定的 Y 问题。这个问题的目的纯粹是好奇。以我熟悉的语法解决问题将帮助我了解 pybind11 数据类型和功能的基本性质。请不要试图找出 Y 问题并解决它。
  • 我完全知道 pybind11 是一个 C++ 库,无论如何必须使用 C++ 编译器编译代码。
  • 如果您在对我的问题进行必要编辑的评论中咨询我,而不是自己做,我将不胜感激。我知道您想提供帮助,但我已尝试尽可能好地构建我的问题以避免混淆。
  • 如果您尽可能避免更改我的 C/C++ 和 python 代码中没有注释的部分,我将不胜感激。
  • 我知道使用术语“C/C++”是错误的。我使用这个术语来指代用 C 语法编写并使用 C 头文件的 C++ 代码。很抱歉,我不知道更好的称呼方式。
  • example.cpp 的评论部分文件表明,可以使用 pybind11 数据类型,然后将它们转换为 C。但我怀疑纯 C 解决方案也可能是可能的。例如,参见 this attempt .
  • 最佳答案

    下面我重新格式化了 previous example code我使用 C++ 构造的地方,只使用 C 和 pybind11 构造。

    #include <pybind11/pybind11.h>
    #include <stdio.h>

    #if PY_VERSION_HEX < 0x03000000
    #define MyPyText_AsString PyString_AsString
    #else
    #define MyPyText_AsString PyUnicode_AsUTF8
    #endif

    namespace py = pybind11;

    int run(py::object pyargv11) {
    int argc = 0;
    char** argv = NULL;

    PyObject* pyargv = pyargv11.ptr();
    if (PySequence_Check(pyargv)) {
    Py_ssize_t sz = PySequence_Size(pyargv);
    argc = (int)sz;

    argv = (char**)malloc(sz * sizeof(char*));
    for (Py_ssize_t i = 0; i < sz; ++i) {
    PyObject* item = PySequence_GetItem(pyargv, i);
    argv[i] = (char*)MyPyText_AsString(item);
    Py_DECREF(item);
    if (!argv[i] || PyErr_Occurred()) {
    free(argv);
    argv = nullptr;
    break;
    }
    }
    }

    if (!argv) {
    //fprintf(stderr, "argument is not a sequence of strings\n");
    //return;

    if (!PyErr_Occurred())
    PyErr_SetString(PyExc_TypeError, "could not convert input to argv");
    throw py::error_already_set();
    }

    for (int i = 0; i < argc; ++i)
    fprintf(stderr, "%s\n", argv[i]);

    free(argv);

    return 0;
    }

    PYBIND11_MODULE(example, m) {
    m.def("run", &run, "runs the example");
    }


    下面我将对其进行大量注释,以解释我在做什么以及为什么。

    在 Python2 中,字符串对象是 char*基于,在 Python3 中,它们是基于 Unicode 的。因此下面的宏 MyPyText_AsString这会根据 Python 版本改变行为,因为我们需要使用 C 风格的“char*”。

    #if PY_VERSION_HEX < 0x03000000
    #define MyPyText_AsString PyString_AsString
    #else
    #define MyPyText_AsString PyUnicode_AsUTF8
    #endif

    pyargv11 py::object是 Python C-API 句柄对象上的细句柄;由于以下代码使用了 Python C-API,因此更容易处理底层 PyObject*直接地。

    void closed_func_wrap(py::object pyargv11) {
    int argc = 0; // the length that we'll pass
    char** argv = NULL; // array of pointers to the strings

    // convert input list to C/C++ argc/argv :

    PyObject* pyargv = pyargv11.ptr();

    代码将只接受实现序列协议(protocol)的容器,因此可以循环。这涵盖了两个最重要的 PyTuplePyList同时(虽然比直接检查这些类型要慢一点,但这会使代码更紧凑)。为了完全通用,这段代码还应该检查迭代器协议(protocol)(例如,对于生成器和可能拒绝 str 对象,但两者都不太可能。

        if (PySequence_Check(pyargv)) {

    好的,我们有一个序列;现在得到它的大小。 (这一步是您需要使用 Python 迭代器协议(protocol)的范围的原因,因为它们的大小通常是未知的(尽管您可以请求提示)。)

            Py_ssize_t sz = PySequence_Size(pyargv);

    一部分,大小做完了,存放在可以传递给其他函数的变量中。

            argc = (int)sz;

    现在将指针数组分配给 char* (技术上是 const char* ,但这并不重要,因为我们将把它扔掉)。

            argv = (char**)malloc(sz * sizeof(char*));

    接下来,循环遍历序列以检索单个元素。

            for (Py_ssize_t i = 0; i < sz; ++i) {

    这从序列中获取单个元素。 GetItem 调用等效于 Python 的“[i]”或 getitem 称呼。

                PyObject* item = PySequence_GetItem(pyargv, i);

    在 Python2 中,字符串对象是基于 char* 的,在 Python3 中,它们是基于 unicode 的。因此,下面的宏“MyPyText_AsString”会根据 Python 版本改变行为,因为我们需要使用 C 风格的“char*”。

    来自 const char* 的 Actor 阵容至 char*这里原则上是安全的,但 argv[i] 的内容不得被其他函数修改。 argv也是如此 main() 的参数,所以我假设是这样。

    请注意,不复制 C 字符串。原因是在 Py2 中,您只需访问底层数据,而在 Py3 中,转换后的字符串作为 Unicode 对象的数据成员保留,Python 将进行内存管理。在这两种情况下,我们保证它们的生命周期至少与输入 Python 对象 (pyargv11) 的生命周期一样长,因此至少在此函数调用的持续时间内是这样。如果其他函数决定保留指针,则需要副本。

                argv[i] = (char*)MyPyText_AsString(item);
    PySequence_GetItem的结果是一个新的引用,所以现在我们已经完成了它,删除它:

                Py_DECREF(item);

    输入数组可能不只包含 Python str 对象。在这种情况下,转换将失败,我们需要检查这种情况,否则“closed_function”可能会出现段错误。

                if (!argv[i] || PyErr_Occurred()) {

    清理之前分配的内存。

                    free(argv);

    将 argv 设置为 NULL稍后成功检查:

                    argv = nullptr;

    放弃循环:

                    break;

    如果给定的对象不是序列,或者序列的元素之一不是字符串,那么我们就没有 argv所以我们保释:

        if (!argv) {

    以下内容有点懒惰,但如果您只想查看 C 代码,可能会更好地理解。

            fprintf(stderr,  "argument is not a sequence of strings\n");
    return;

    您真正应该做的是检查是否已经设置了错误(例如转换问题的 b/c),如果没有则设置一个。然后将其通知 pybind11。这将在调用方的一端为您提供一个干净的 Python 异常。事情是这样的:

            if (!PyErr_Occurred())
    PyErr_SetString(PyExc_TypeError, "could not convert input to argv");
    throw py::error_already_set(); // by pybind11 convention.

    好吧,如果我们到了这里,那么我们就有了 argcargv ,所以现在我们可以使用它们:

        for (int i = 0; i < argc; ++i)
    fprintf(stderr, "%s\n", argv[i]);

    最后,清理分配的内存。

        free(argv);

    笔记:
  • 我仍然主张至少使用 std::unique_ptr因为如果抛出 C++ 异常(来自任何输入对象的自定义转换器),这会让生活变得更加轻松。
  • 我原本希望能够用单行代码替换所有代码 std::vector<char*> pv{pyargv.cast<std::vector<char*>>()};之后 #include <pybind11/stl.h> ,但我发现这不起作用(即使它确实可以编译)。也没有使用 std::vector<std::string> (也编译,但在运行时也失败)。

  • 只是问是否还有不清楚的地方。

    编辑 :如果你真的只想拥有一个 PyListObject,只需拨打 PyList_Check(pyargv11.ptr())如果为真,则转换结果: PyListObject* pylist = (PyListObject*)pyargv11.ptr() .现在,如果您想使用 py::list ,您还可以使用以下代码:
    #include <pybind11/pybind11.h>
    #include <stdio.h>

    #if PY_VERSION_HEX < 0x03000000
    #define MyPyText_AsString PyString_AsString
    #else
    #define MyPyText_AsString PyUnicode_AsUTF8
    #endif

    namespace py = pybind11;

    int run(py::list inlist) {
    int argc = (int)inlist.size();
    char** argv = (char**)malloc(argc * sizeof(char*));

    for (int i = 0; i < argc; ++i)
    argv[i] = (char*)MyPyText_AsString(inlist[i].ptr());

    for (int i = 0; i < argc; ++i)
    fprintf(stderr, "%s\n", argv[i]);

    free(argv);

    return 0;
    }

    PYBIND11_MODULE(example, m) {
    m.def("run", &run, "runs the example");
    }

    这段代码更短,只有 b/c 它具有较少的功能:它只接受列表,并且在错误处理方面也更加笨拙(例如,如果由于 pybind11 抛出异常,它会在传入整数列表时泄漏;要解决这个问题,在第一个示例代码中使用 unique_ptr,以便在异常时释放 argv)。

    关于python - 通过pybind11将字符串列表从python传递给C,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60067092/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com