Часто задаваемые вопросы по расширению/встраиванию
- Можно ли создавать свои функции в C?
- Можно ли создавать свои функции в C++?
- Запись на C сложная; есть ли альтернативы?
- Как выполнить произвольные операторы Python из C?
- Как оценить произвольное выражение Python из C?
- Как извлечь значения C из объекта Python?
- Как использовать Py_BuildValue() для создания кортежа произвольной длины?
- Как вызвать метод объекта из C?
- Как перехватить вывод из PyErr_Print() (или чего-либо, что выводит на stdout/stderr)?
- Как получить доступ к модулю, написанному на Python, из C?
- Как взаимодействовать с объектами C++ из Python?
- Я добавил модуль с помощью файла Setup, а make завершается ошибкой; почему?
- Как отладить расширение?
- Я хочу скомпилировать модуль Python на моей Linux-системе, но некоторые файлы отсутствуют. Почему?
- Как отличить «неполный ввод» от «некорректного ввода»?
- Как найти неопределённые символы g++ __builtin_new или __pure_virtual?
- Можно ли создать класс объекта с некоторыми методами, реализованными на C, и другими на Python (например, через наследование)?
Можно ли создавать свои функции в C?
Да, вы можете создавать встроенные модули, содержащие функции, переменные, исключения и даже новые типы в C. Это объясняется в документе Расширение и встраивание интерпретатора Python.
Большинство средних или продвинутых учебников по Python также будут охватывать эту тему.
Можно ли создавать свои функции в C++?
Да, используя возможности совместимости с C в C++. Поместите extern "C" {
... } вокруг файлов включения Python и поместите extern "C" перед каждой функцией, которая будет вызываться интерпретатором Python. Глобальные или статические C++ объекты с конструкторами, вероятно, не лучшая идея.
Запись на C сложная; есть ли альтернативы?
Есть ряд альтернатив написанию собственных расширений C, в зависимости от того, что вы пытаетесь сделать.
Cython и его аналог Pyrex — это компиляторы, которые принимают слегка изменённую форму Python и генерируют соответствующий код C. Cython и Pyrex позволяют написать расширение, не изучая API C языка Python.
Если вам нужно взаимодействовать с какой-либо библиотекой C или C++, для которой в настоящее время нет расширения Python, вы можете попробовать обернуть типы данных и функции библиотеки с помощью инструмента, такого как SWIG. SIP, CXX Boost или Weave также являются альтернативами для обертывания библиотек C++.
Как выполнить произвольные операторы Python из C?
Самая высокая функция для этого — PyRun_SimpleString(), которая принимает один строковый аргумент, который будет выполняться в контексте модуля __main__ и возвращает 0 для успеха и -1 при возникновении исключения (включая SyntaxError). Если вам нужен больший контроль, используйте PyRun_String(); см. исходный код PyRun_SimpleString() в Python/pythonrun.c.
Как оценить произвольное выражение Python из C?
Вызовите функцию PyRun_String() из предыдущего вопроса с начальным символом Py_eval_input; он анализирует выражение, оценивает его и возвращает его значение.
Как извлечь значения C из объекта Python?
Это зависит от типа объекта. Если это кортеж, PyTuple_Size() возвращает его длину, а PyTuple_GetItem() возвращает элемент в указанном индексе. У списков есть аналогичные функции, PyListSize() и PyList_GetItem().
Для байтов PyBytes_Size() возвращает его длину, а PyBytes_AsStringAndSize() предоставляет указатель на его значение и его длину. Обратите внимание, что объекты Python bytes могут содержать нулевые байты, поэтому C’s strlen() использовать не следует.
Чтобы проверить тип объекта, сначала убедитесь, что он не NULL, а затем используйте PyBytes_Check(), PyTuple_Check(), PyList_Check() и т.д.
Также существует высокоуровневый API для объектов Python, который предоставляется так называемым «абстрактным» интерфейсом — прочитайте Include/abstract.h для получения более подробной информации. Он позволяет взаимодействовать с любым типом последовательности Python с помощью вызовов, таких как PySequence_Length(), PySequence_GetItem() и т.д., а также многие другие полезные протоколы, такие как числа (PyNumber_Index() и др.) и отображения в API PyMapping.
Как использовать Py_BuildValue() для создания кортежа произвольной длины?
Вы не можете. Используйте PyTuple_Pack() вместо этого.
Как вызвать метод объекта из C?
Функция PyObject_CallMethod() может использоваться для вызова произвольного метода объекта. Параметрами являются объект, имя вызываемого метода, строка формата, подобная той, что используется с Py_BuildValue(), и значения аргументов:
PyObject *
PyObject_CallMethod(PyObject *object, const char *method_name,
const char *arg_format, ...);
Это работает для любых объектов, имеющих методы — встроенные или пользовательские. Вы сами несёте ответственность за последующее Py_DECREF() возвращаемого значения.
Чтобы вызвать, например, метод «seek» объекта файла с аргументами 10, 0 (предполагая, что указатель на объект файла — «f»):
res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
if (res == NULL) {
... an exception occurred ...
}
else {
Py_DECREF(res);
}
Обратите внимание, что поскольку PyObject_CallObject() всегда хочет кортеж для списка аргументов, чтобы вызвать функцию без аргументов, передайте «()» для формата, а чтобы вызвать функцию с одним аргументом, заключите аргумент в скобки, например, «(i)».
Как перехватить вывод из PyErr_Print() (или чего-либо, что выводит на stdout/stderr)?
В коде Python определите объект, который поддерживает метод write(). Присвойте этот объект sys.stdout и sys.stderr. Вызовите print_error или просто позвольте механизму стандартного трассировки работать. Тогда вывод будет перенаправлен туда, куда ваш метод write() направляет его.
Самый простой способ сделать это — использовать класс io.StringIO:
>>> import io, sys
>>> sys.stdout = io.StringIO()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(sys.stdout.getvalue())
foo
hello world!
Пользовательский объект для того же будет выглядеть следующим образом:
>>> import io, sys
>>> class StdoutCatcher(io.TextIOBase):
... def __init__(self):
... self.data = []
... def write(self, stuff):
... self.data.append(stuff)
...
>>> import sys
>>> sys.stdout = StdoutCatcher()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(''.join(sys.stdout.data))
foo
hello world!
Как получить доступ к модулю, написанному на Python, из C?
Вы можете получить указатель на объект модуля следующим образом:
module = PyImport_ImportModule("<modulename>");
Если модуль еще не был импортирован (т.е. он еще не присутствует в sys.modules), это инициализирует модуль; в противном случае просто возвращает значение sys.modules["<modulename>"]. Обратите внимание, что это не добавляет модуль в какое-либо пространство имен — оно только гарантирует, что он был инициализирован и сохранен в sys.modules.
Затем вы можете получить доступ к атрибутам модуля (т.е. к любому имени, определенному в модуле) следующим образом:
attr = PyObject_GetAttrString(module, "<attrname>");
Вызов PyObject_SetAttrString() для присвоения переменным в модуле также работает.
Как взаимодействовать с объектами C++ из Python?
В зависимости от ваших потребностей, существуют многие подходы. Для этого вручную начните с чтения документа «Расширение и встраивание». Поймите, что для системы выполнения Python особой разницы между C и C++ нет — поэтому стратегия создания нового типа Python вокруг типа структуры C (указателя) также будет работать для объектов C++.
Для библиотек C++, см. Сложно ли писать на C? Есть ли альтернативы?.
Я добавил модуль с помощью файла Setup, но make завершается ошибкой; почему?
Файл Setup должен заканчиваться символом новой строки. Если новой строки нет, процесс сборки завершается ошибкой. (Исправление этой ошибки требует некоторых уродливых трюков с командными скриптами оболочки, и эта ошибка настолько незначительна, что, кажется, не стоит усилий.)
Как отладить расширение?
При использовании GDB с динамически загружаемыми расширениями, вы не можете установить точку останова в своем расширении до тех пор, пока оно не загрузится.
В вашем файле .gdbinit (или интерактивно) добавьте команду:
br _PyImport_LoadDynamicModule
Затем, когда вы запускаете GDB:
$ gdb /local/bin/python gdb) run myscript.py gdb) continue # repeat until your extension is loaded gdb) finish # so that your extension is loaded gdb) br myfunction.c:50 gdb) continue
Я хочу скомпилировать модуль Python на моей Linux системе, но некоторые файлы отсутствуют. Почему?
Большинство упакованных версий Python не включают каталог /usr/lib/python2.x/config/, который содержит различные файлы, необходимые для компиляции расширений Python.
Для Red Hat установите RPM python-devel, чтобы получить необходимые файлы.
Для Debian выполните apt-get install python-dev.
Как отличить «неполный ввод» от «некорректного ввода»?
Иногда нужно эмулировать поведение интерактивного интерпретатора Python, где он выдает приглашение для продолжения, когда ввод неполный (например, вы набрали начало оператора «if» или не закрыли скобки или тройные строковые кавычки), но сразу выдает сообщение об ошибке синтаксиса, когда ввод некорректный.
В Python вы можете использовать модуль codeop, который достаточно точно моделирует поведение парсера. Например, IDLE использует его.
Самый простой способ сделать это на C — вызвать PyRun_InteractiveLoop() (возможно, в отдельном потоке) и позволить интерпретатору Python обработать ввод за вас. Вы также можете установить PyOS_ReadlineFunctionPointer() на указатель вашей пользовательской функции ввода. Подробнее см. Modules/readline.c и Parser/myreadline.c.
Однако иногда вам нужно запустить встроенный интерпретатор Python в том же потоке, что и остальная ваша программа, и вы не можете позволить PyRun_InteractiveLoop() остановиться во время ожидания пользовательского ввода. Единственное решение — вызвать PyParser_ParseString() и проверить, что e.error равно E_EOF, что означает, что ввод неполный. Вот пример фрагмента кода, не протестированного, вдохновленного кодом Алекса Фарбера:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <node.h>
#include <errcode.h>
#include <grammar.h>
#include <parsetok.h>
#include <compile.h>
int testcomplete(char *code)
/* code should end in \n */
/* return -1 for error, 0 for incomplete, 1 for complete */
{
node *n;
perrdetail e;
n = PyParser_ParseString(code, &_PyParser_Grammar,
Py_file_input, &e);
if (n == NULL) {
if (e.error == E_EOF)
return 0;
return -1;
}
PyNode_Free(n);
return 1;
}
Другое решение — попытаться скомпилировать полученную строку с помощью Py_CompileString(). Если компиляция прошла без ошибок, попробуйте выполнить полученный объект кода, вызвав PyEval_EvalCode(). В противном случае сохраните ввод для последующей обработки. Если компиляция завершилась ошибкой, определите, является ли это ошибкой или требуется дополнительный ввод — извлеките строку сообщения из кортежа исключения и сравните ее со строкой «неожиданный конец ввода во время разбора». Вот полный пример с использованием библиотеки GNU readline (возможно, вам захочется игнорировать SIGINT при вызове readline()):
#include <stdio.h>
#include <readline.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <object.h>
#include <compile.h>
#include <eval.h>
int main (int argc, char* argv[])
{
int i, j, done = 0; /* lengths of line, code */
char ps1[] = ">>> ";
char ps2[] = "... ";
char *prompt = ps1;
char *msg, *line, *code = NULL;
PyObject *src, *glb, *loc;
PyObject *exc, *val, *trb, *obj, *dum;
Py_Initialize ();
loc = PyDict_New ();
glb = PyDict_New ();
PyDict_SetItemString (glb, "__builtins__", PyEval_GetBuiltins ());
while (!done)
{
line = readline (prompt);
if (NULL == line) /* Ctrl-D pressed */
{
done = 1;
}
else
{
i = strlen (line);
if (i > 0)
add_history (line); /* save non-empty lines */
if (NULL == code) /* nothing in code yet */
j = 0;
else
j = strlen (code);
code = realloc (code, i + j + 2);
if (NULL == code) /* out of memory */
exit (1);
if (0 == j) /* code was empty, so */
code[0] = '\0'; /* keep strncat happy */
strncat (code, line, i); /* append line to code */
code[i + j] = '\n'; /* append '\n' to code */
code[i + j + 1] = '\0';
src = Py_CompileString (code, "<stdin>", Py_single_input);
if (NULL != src) /* compiled just fine - */
{
if (ps1 == prompt || /* ">>> " or */
'\n' == code[i + j - 1]) /* "... " and double '\n' */
{ /* so execute it */
dum = PyEval_EvalCode (src, glb, loc);
Py_XDECREF (dum);
Py_XDECREF (src);
free (code);
code = NULL;
if (PyErr_Occurred ())
PyErr_Print ();
prompt = ps1;
}
} /* syntax error or E_EOF? */
else if (PyErr_ExceptionMatches (PyExc_SyntaxError))
{
PyErr_Fetch (&exc, &val, &trb); /* clears exception! */
if (PyArg_ParseTuple (val, "sO", &msg, &obj) &&
!strcmp (msg, "unexpected EOF while parsing")) /* E_EOF */
{
Py_XDECREF (exc);
Py_XDECREF (val);
Py_XDECREF (trb);
prompt = ps2;
}
else /* some other syntax error */
{
PyErr_Restore (exc, val, trb);
PyErr_Print ();
free (code);
code = NULL;
prompt = ps1;
}
}
else /* some non-syntax error */
{
PyErr_Print ();
free (code);
code = NULL;
prompt = ps1;
}
free (line);
}
}
Py_XDECREF(glb);
Py_XDECREF(loc);
Py_Finalize();
exit(0);
}
Как найти неопределенные символы g++ __builtin_new или __pure_virtual?
Для динамической загрузки модулей расширений g++, необходимо перекомпилировать Python, пересцеплять его с помощью g++ (изменить LINKCC в Makefile модулей Python) и скомпилировать ваш модуль расширения с помощью g++ (например, g++ -shared -o mymodule.so mymodule.o).
Можно ли создать класс объекта с некоторыми методами, реализованными на C, и другими на Python (например, с помощью наследования)?
Да, вы можете наследовать от встроенных классов, таких как int, list, dict и т.д.
Библиотека Boost Python (BPL, http://www.boost.org/libs/python/doc/index.html) предоставляет способ сделать это из C++ (т.е. вы можете наследовать от класса расширения, написанного на C++, с помощью BPL).
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/faq/extending.html