下面我重新格式化了我使用 C++ 结构的 previous example code,只使用 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();
代码将只接受实现序列协议的容器,因此可以循环。这同时涵盖了两个最重要的PyTuple 和PyList(尽管比直接检查这些类型要慢一点,但这将使代码更紧凑)。为了完全通用,此代码还应检查迭代器协议(例如,检查生成器并可能拒绝 str 对象,但两者都不太可能。
if (PySequence_Check(pyargv)) {
好的,我们有一个序列;现在得到它的大小。 (这一步是您需要使用 Python 迭代器协议的范围的原因,因为它们的大小通常是未知的(尽管您可以请求提示)。)
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* 到char* 的转换原则上是安全的,但是argv[i] 的内容不能被其他函数修改。 main() 的 argv 参数也是如此,所以我假设是这种情况。
请注意,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.
好的,如果我们到达这里,那么我们有一个argc 和argv,所以现在我们可以使用它们了:
for (int i = 0; i < argc; ++i)
fprintf(stderr, "%s\n", argv[i]);
最后,清理分配的内存。
free(argv);
注意事项:
- 我仍然主张至少使用
std::unique_ptr,因为如果抛出 C++ 异常(来自任何输入对象的自定义转换器),这会使生活变得更加轻松。
- 我最初希望能够在
#include <pybind11/stl.h> 之后用单行代码std::vector<char*> pv{pyargv.cast<std::vector<char*>>()}; 替换所有代码,但我发现这行不通(即使它可以编译)。也没有使用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)。