Часто задаваемые вопросы по расширению/встраиванию

Содержание

Могу ли я создать свои функции на C?

Да, можно создать встроенные модули, содержащие функции, переменные, исключения и даже новые типы в C. Это объясняется в документе Расширение и встраивание интерпретатора Python.

Большинство средних или продвинутых Python книг также охватывают эту тему.

Можно ли создать свои функции на C++?

Да, используя C совместимые функции, имеющиеся в C++. Поместите extern "C" { ... } вокруг Python инклуд файлов и поставьте extern "C" перед каждой функцией, которая будет вызвана Python интерпретатором. Глобальные или статические объекты C++ с конструкторами, вероятно, не очень хорошая идея.

Писать C сложно; есть ли альтернативы?

Существует ряд альтернатив написанию собственных расширений C, в зависимости от того, что вы пытаетесь сделать.

Cython и их относительные Pyrex являются компиляторами, которые принимают слегка измененную форму Python и генерируют соответствующий C код. Cython и Pyrex позволяют писать расширение без необходимости изучения Python C API.

Если требуется интерфейс с какой-либо библиотекой 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 байтов могут содержать пустые байты, поэтому C strlen() не следует использовать.

Чтобы проверить тип объекта, сначала убедитесь, что он не NULL, а затем используйте PyBytes_Check(), PyTuple_Check(), PyList_Check() и т. д.

Существует также высокоуровневый API для Python объектов, который предоставляется так называемым «абстрактным» интерфейсом - более подробную информацию см. Include/abstract.h. Он позволяет взаимодействовать с любым видом последовательности Python, используя вызовы, такие как PySequence_Length(), PySequence_GetItem() и т.д., а также многие другие полезные протоколы, такие как числа (PyNumber_Index() и др.) и отображения в API PyMapping.

Как вызвать метод объекта из 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 сложно; есть ли альтернативы?.

Я добавил модуль с помощью файла установки, но make не работает. Почему? ————————————————————————–1

Программа установки должна заканчиваться новой строкой. Если новой строки нет, процесс сборки завершается неуспешно. (Исправление этого требует некоего уродливого скрипта, и ошибка настолько незначительна, что, кажется, не стоит усилий.)

Как выполнить отладку расширения?

При использовании GDB с динамически загружаемыми расширениями вы не можете установить точку останова в ваше расширение, пока оно не загрузится.

В файле .gdbinit (или в интерактивном режиме) добавьте команду:

br _PyImport_LoadDynamicModule

Затем, при запуске GDB:

$ gdb /local/bin/python
gdb) run myscript.py
gdb) continue # повторять, пока ваше расширение не загрузится
gdb) finish   # чтобы ваше расширение было загружено
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, что означает, что входные данные являются неполными. Вот образец кодового фрагмента, непротестированного, вдохновленного кодом от Alex Farber:

#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)
  /* код должен заканчиваться на \n */
  /* возвращает -1 при ошибке, 0 при неполной, 1 при полной */
{
  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(). В противном случае сохраните введенные данные на потом. Если компиляция не удалась, выясните, является ли она ошибкой или требуется просто больше входных данных - путем извлечения строки сообщения из кортежа исключений и сравнивая его с строкой «unexpected EOF while parsing». Вот полный пример использования библиотеки readline GNU (при вызове readline() можно игнорировать SIGINT):

#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;                          /* длина строки, код */
  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 */
    {
      done = 1;
    }
    else
    {
      i = strlen (line);

      if (i > 0)
        add_history (line);                    /* сохранение непустых строк */

      if (NULL == code)                        /* nв коде пока ничего нет */
        j = 0;
      else
        j = strlen (code);

      code = realloc (code, i + j + 2);
      if (NULL == code)                        /* недостаточно памяти */
        exit (1);

      if (0 == j)                              /* код был пуст, поэтому */
        code[0] = '\0';                        /* держать strncat счастливым */

      strncat (code, line, i);                 /* добавить строку в код */
      code[i + j] = '\n';                      /* добавить '\n' в код */
      code[i + j + 1] = '\0';

      src = Py_CompileString (code, "<stdin>", Py_single_input);

      if (NULL != src)                         /* скомпилировано просто отлично - */
      {
        if (ps1  == prompt ||                  /* ">>> " или */
            '\n' == code[i + j - 1])           /* "... " и двойной '\n' */
        {                                               /* так что выполняй это */
          dum = PyEval_EvalCode (src, glb, loc);
          Py_XDECREF (dum);
          Py_XDECREF (src);
          free (code);
          code = NULL;
          if (PyErr_Occurred ())
            PyErr_Print ();
          prompt = ps1;
        }
      }                                        /* синтаксическая ошибка или E_EOF? */
      else if (PyErr_ExceptionMatches (PyExc_SyntaxError))
      {
        PyErr_Fetch (&exc, &val, &trb);        /* очищает исключение! */

        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                                   /* другая синтаксическая ошибка */
        {
          PyErr_Restore (exc, val, trb);
          PyErr_Print ();
          free (code);
          code = NULL;
          prompt = ps1;
        }
      }
      else                                     /* некоторая несинтаксическая ошибка */
      {
        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 в модулях Python Makefile), и слинкуйте свой модуль расширения, используя g++ (например, g++ -shared -o mymodule.so mymodule.o).

Могу ли я создать объектный класс с некоторыми методами, реализованными на C, а другие на Python (например, посредством наследования)?

Да, вы можете наследовать от встроенных классов, таких как int, list, dict и т.д.

Библиотека Boost Python Library (BPL, http://www.boost.org/libs/python/doc/index.html) предоставляет способ выполнения этой операции с языка C++ (т.е. можно наследовать класс расширения, написанный на языке C++ с помощью BPL).