Часто задаваемые вопросы о расширении и встраивании
- Могу ли я создавать собственные функции на C?
- Могу ли я создавать собственные функции на C++?
- Писать на C сложно; есть ли альтернативы?
- Как выполнять произвольные инструкции Python из C?
- Как вычислить произвольное выражение Python из C?
- Как извлечь значения C из объекта Python?
- Как с помощью Py_BuildValue() создать кортеж произвольной длины?
- Как вызвать метод объекта из C?
- Как перехватить вывод PyErr_Print() (или любой другой вывод в stdout/stderr)?
- Как получить доступ из C к модулю, написанному на Python?
- Как взаимодействовать с объектами 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 позволяют писать расширения, не изучая C API Python.
Если вам нужно взаимодействовать с библиотекой C или C++, для которой пока нет расширения Python, можно попробовать обернуть типы данных и функции библиотеки с помощью такого инструмента, как SWIG. Для обёртывания библиотек C++ можно также использовать SIP, CXX Boost или Weave.
Как выполнять произвольные инструкции 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() предоставляет указатель на их значение и длину. Учтите, что объекты bytes в Python могут содержать нулевые байты, поэтому не следует использовать strlen() из C.
Чтобы проверить тип объекта, сначала убедитесь, что это не NULL, а затем используйте PyBytes_Check(), PyTuple_Check(), PyList_Check() и т. д.
Для работы с объектами Python также существует API высокого уровня, предоставляемый так называемым «абстрактным» интерфейсом. Подробнее см. 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!
Как получить доступ из C к модулю, написанному на Python?
Получить указатель на объект модуля можно следующим образом:
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 отсутствуют некоторые файлы, необходимые для компиляции расширений Python.
В Red Hat установите RPM-пакет python3-devel, чтобы получить необходимые файлы.
В Debian выполните apt-get install python3-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 (BPL, https://www.boost.org/libs/python/doc/index.html) позволяет делать это на C++ (то есть с помощью BPL можно наследоваться от класса расширения, написанного на C++).
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/faq/extending.html