FAQ по удлинению/присоединению

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

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

В большинстве книг по Python для среднего и продвинутого уровня эта тема также рассматривается.

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

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

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

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

Cython и его родственник Pyrex - это компиляторы, которые принимают слегка измененную форму Python и генерируют соответствующий код на C. Cython и Pyrex позволяют написать расширение без необходимости изучать C API 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() - элемент по указанному индексу. У списков есть похожие функции, PyList_Size() и PyList_GetItem().

Для байта PyBytes_Size() возвращает его длину, а PyBytes_AsStringAndSize() предоставляет указатель на его значение и длину. Обратите внимание, что объекты байтов Python могут содержать нулевые байты, поэтому не следует использовать strlen() из языка C.

Чтобы проверить тип объекта, сначала убедитесь, что он не 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?

В зависимости от ваших требований существует множество подходов. Чтобы сделать это вручную, начните с чтения the «Extending and Embedding» document. Поймите, что для системы времени выполнения Python нет особой разницы между C и C++ - поэтому стратегия создания нового типа Python на основе типа структуры (указателя) C будет работать и для объектов C++.

О библиотеках C++ смотрите Писать на C сложно; есть ли альтернативы?.

Я добавил модуль с помощью файла Setup, но make не работает; почему?

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

Как отладить расширение?

При использовании 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.

Как найти неопределенные символы 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 Library (BPL, https://www.boost.org/libs/python/doc/index.html) предоставляет возможность сделать это из C++ (то есть вы можете наследовать от класса расширения, написанного на C++, используя BPL).