[英]passing a list of strings from python to C through pybind11
在這篇文章之后,我想知道如何通過 Pybind11 將字符串列表從 Python 傳遞到 C(即,使用 C 標頭和語法,而不是 C++)。 我完全知道 Pybind11 是一個 C++ 庫,並且代碼無論如何都必須由 C++ 編譯器編譯。 但是,我很難理解 C++ 實現,例如這里和這里。
在這里,我嘗試通過指針傳遞一個python字符串列表,表示為整數,然后在C中通過long*
接收它們,但是沒有用。
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
示例。 Python 代碼可以是這樣的:
#example.py
import example
print(example.run(["Lorem", "ipsum", "dolor", "sit", "amet"]))
為了避免這樣的誤解此,請考慮以下幾點:
example.cpp
文件的注釋部分所示,可以使用 pybind11 數據類型,然后將它們轉換為 C。但我懷疑純 C 解決方案也可能是可能的。 例如,請參閱此嘗試。下面我重新格式化了之前使用 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();
代碼將只接受實現序列協議的容器,因此可以循環。 這同時涵蓋了兩個最重要的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++ 異常(來自任何輸入對象的自定義轉換器),這會使生活變得更加輕松。std::vector<char*> pv{pyargv.cast<std::vector<char*>>()};
替換所有代碼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)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.