Ниже я переформатировал код предыдущего примера , где я использовал конструкции 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
одновременно (хотя и немного медленнее, чем проверка этих типов напрямую, но это сделает код более компактным). Чтобы быть полностью обобщенным c, этот код должен также проверять протокол итератора (например, для генераторов и, вероятно, отклонять объекты 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*));
Далее l oop по последовательности для извлечения отдельных элементов.
for (Py_ssize_t i = 0; i < sz; ++i) {
Это получает один элемент из последовательность. Вызов GetItem эквивалентен Pythons «[i]» или getitem call.
PyObject* item = PySequence_GetItem(pyargv, i);
В Python2 строковые объекты основаны на char *, в Python3, они основаны на Unicode. Отсюда следующий макрос "MyPyText_AsStr ing ", который меняет поведение в зависимости от версии Python, так как нам нужно перейти к C -типу" char * ".
Приведение от const char*
к 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 to NULL
для последующей проверки успеха:
argv = nullptr;
Откажитесь от l oop:
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*>>()};
после #include <pybind11/stl.h>
, но я обнаружил, что это не работает (даже если он компилируется). Ни один из них не использовал std::vector<std::string>
(также компилируется, но не работает во время выполнения).
Просто спросите, все ли что-то неясно.
РЕДАКТИРОВАТЬ : Если вы действительно хотите иметь PyListObject, просто вызовите PyList_Check(pyargv11.ptr())
и, если true, приведите результат: 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 освобождается при исключении).