Основной источник (на английском): Extending and Embedding.
Справочная информация (на английском): Python/C API.
Интерпретатор Python реализован в разделяемой библиотеке, а исполняемый файл интерпретатора python3
является лишь оболочкой для запуска интерпретатора.
Для сборки программы можно использовать CMake, в стандартной поставке которого входит поддержка Python.
find_package(PythonLibs 3.6 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
target_link_libraries(program ${PYTHON_LIBRARIES})
Обратите внимание на то, что необходимо указывать минимальную версию интерпретатора, поскольку в поставку многих дистрибутивах Linux входит две версии Python (2.7 и 3.x), и может возникнуть неоднозначность используемой библиотеки.
Тривиальная реализация своего интерпретатора, с использованием библиотеки python
выглядит следующим образом:
#include <stdio.h>
// В этом заголовочном файле собран почти весь API Python
#include <Python.h>
int main(int argc, char *argv[])
{
// Открытие файла на чтение
FILE* fp = fopen(argv[1], "r");
// Инициализация интерпретатора Python
Py_Initialize();
// Выполнение файла
PyRun_SimpleFile(fp, argv[1]);
// Завершение работы интерпретатора
Py_Finalize();
// Закрытие файла
fclose(fp);
}
Указание имени файла в качестве второго аргумента является желательным по двум причинам: оно используется при генерации сообщении возникающих исключительных ситуаций, и кроме того, используется для определения пути поиска зависимых модулей. Переданный текст доступен через глобальную переменную __file__
и может быть произвольным.
/* Си */
PyRun_SimpleFile(fp, "abrakadabra");
# Python
print(__file__)
# abrakadabra
Скрипт на языке Python может иметь аргументы командной строки, которые доступны через переменную-список sys.argv
. В приведенной выше тривиальной реализации интерпретатора это значение не установлено, поэтому обращение к sys.argv
выдаст ошибку о том, что эта переменная не определена:
AttributeError: module 'sys' has no attribute 'argv'
Перед запуском файла на выполнение можно установить список аргументов с помощью PySys_SetArgv
:
wchar_t* args[] = { L"One", L"Two", L"Аргумент" };
PySys_SetArgv(3, args); // int argc, wchar_t *argv[]
Обратите внимание на то, что строки в Python являются многобайтовыми, поэтому многие функции API подразумевают работу с типом данных wchar_t
. В случае использования однобайтных цепочек символов используется системная локаль, как правило это UTF-8.
Программой может быть не только текст программы, хранящийся в файле, но и произвольная строка текста:
PyRun_SimpleString("a=1\nb=2\nprint(a+b)");
// будет выведено 3
При выполнении текста, с которым не связан никакой файл, глобальная переменная __file__
считается не определенной, а в случае возникновения исключений, в качестве файла-источника будет использован текст <string>
.
PyRun_SimpleString("print(__file__)");
Traceback (most recent call last):
File "<string>", line 1, in <module>
NameError: name '__file__' is not defined
Язык Python содержит реализацию стандартных контейнерных классов, которые являются встроенными типами Python, но их можно использовать и без интерпретации текста программы.
Базовым классом для всех объектов является класс object
, которому в Си API соответствует базовый класс PyObject
. Поскольку интерптетатор реализован на языке Си, который не является объектно-ориентированным, то на пользователя API возлагается ответственноть за контролем используемых типов.
Все классы и методы в PyObject API именуются следующим образом:PyКласс_Метод
, а указатель на объект класса передается в качестве первого аргумента.
Примеры стандартных классов и методов:
PyList_Append(PyObject *list, PyObject *item)
- эквивалентlist.append(item)
PyDict_SetItem(PyObject *p, PyObject *key, PyObject *val)
- эквивалентp[key]=val
Обратите внимание, что для объектов всех классов используется тип PyObject*
, поэтому необходимо предварительно проверять, какой тип имеет переменная с помощью одной из функций вида PyКласс_Check(PyObject *p)
, которая возвращает ненулевое значение в случае принадлежности объекта к классу, и 0
в противном случае.
Соответствие стандартных типов Python префиксам функций PyObject API: list
- PyList_
, tuple
- PyTuple_
, dict
- PyDict_
, str
- PyUnicode_
, bytes
- PyBytes_
, file
- PyFile_
, int
- PyLong_
, float
- PyFloat_
.
Обратите внимание, что тип для строк называется PyUnicode
, а не PyString
. Это связано с тем, что во времена Python 2 строки были двух видов: однобайтные и юникодные, а в Python 3 остались только юникод-строки.
Пример использования API без интерпретатора: разбить текст на лексемы, выделяя целые числа как числа, а остальные слова оставляя строками.
Этому коду соответствует программа на Python:
text = "сейчас 23 59 не время спать"
result = []
tokens = text.split(" ")
for entry in tokens:
try:
number = int(entry)
result += [number]
except:
result += [ entry.upper() ]
print(result)
# ['сейчас', 23, 59, 'не', 'время', 'спать']
int main()
{
// Если не используются wchar_t*, то по умолчанию подразумевается,
// что все однобайтные строки - в кодировке UTF-8
static const char TheText[] = "сейчас 23 59 не время спать";
// Инициализация API Python
Py_Initialize();
// Создание Python-строк из Си-строк
PyObject *py_text = PyUnicode_FromString(TheText);
PyObject *py_space_symbol = PyUnicode_FromString(" ");
// Создание пустого списка
PyObject *py_result = PyList_New(0);
// str.split(py_text, py_space_symbol, maxsplit=-1)
PyObject *py_tokens = PyUnicode_Split(py_text, py_space_symbol, -1);
PyObject *py_entry = NULL;
PyObject *py_number = NULL;
// Цикл по элементам списка. PyList_Size - его размер
for (int i=0; i<PyList_Size(py_tokens); ++i) {
// list.__getitem__(i) - этому методу соответствует оператор []
py_entry = PyList_GetItem(py_tokens, i);
// Попытка создать int из строки, base=10
// В случае не успеха устанавливается ошибка ValueError
py_number = PyLong_FromUnicodeObject(py_entry, 10);
// Проверяем, не возникло ли исключение
if (! PyErr_Occurred()) {
// OK - преобразование int(py_entry) выполнено успешно
PyList_Append(py_result, py_number);
}
else {
// Возникло исключение, оставляем просто текст
PyList_Append(py_result, py_entry);
// Убираем флаг ошибки, так как мы её обработали.
// Если этого не сделать, то это исключение попадет
// в интерпретатор, как только он будет использован
PyErr_Clear();
}
}
// Вывод print(repr(py_result))
// Если последний параметр Py_PRINT_RAW вместо 0,
// то вместо repr() будет использована функция str() для
// преобразования произвольного объекта к строковому виду
PyObject_Print(py_result, stdout, 0);
Py_Finalize();
}
Скрипты на Python, выполняемые интерпретатором, могут использовать любые модули через import
, в этом случае выполняется их поиск в одном из каталогов, перечисленных в списке sys.path
. Некоторые из модулей являются встроенными (built-in), и не загружаются из внешних файлов, а создаются самим интерпретатором.
Для доступа к функциональности приложения, в который встраивается интерпретатор, можно использовать встроенные модули, которые взаимодействуют с самим приложением.
# создадим модуль с названием 'app', который реализует функциональность
import app
# модуль может содержать функции
app.do_something()
# и какие-нибудь глобальные переменные
print(app.some_value)
При выполнении строки import
интерпретатор выполняет поиск модуля, и в случае успеха, создает новый объект модуля с указанным именем, используя его в качестве глобального пространства имен, в котором выполняется инициализация модуля.
Иниализация выполняется ровно один раз, независимо от того, сколько раз импортируется модуль. Для обычных Python-модулей, его инициализация заключается в выполнении текста программы, а для встроенных модулей - в вызове функции, которая возвращает новый модуль.
static PyObject *
create_module() {
// NULL в качестве возвразаемого значения любой функции,
// которая должна возвращать PyObject*, означает
// исключительную ситуацию
PyErr_SetString(PyExc_RuntimeError, "Not implemented yet");
return NULL;
}
int main(int argc, char *argv[]) {
// Добавляем в таблицу имя встроенного модуля
PyImport_AppendInittab("app", create_module);
// Регистрация встроенных модулей должна быть сделана
// раньше, чем PyInitialize
PyInitialize();
...
}
Сам модуль - это объект Python, который инициализируется из структуры-описания PyModuleDef
:
static PyModuleDef moduleDef = {
// ссылка на RTTI, поскольку Си не является ООП-языком
.m_base = PyModuleDef_HEAD_INIT,
// имя модуля
.m_name = "app",
// размер дополнительной памяти для хранения состояния модуля в
// случае использования нескольких интерпретаторов, либо -1,
// если не планируется использование PyModule_GetState
.m_size = -1,
// указатель на список методов (функций) модуля, может быть NULL
.m_methods = methods,
};
PyObject *module = PyModule_Create(&moduleDef);
Список методов модуля - это массив объектов PyMethodDef
, признаком конца которого является "нулевой элемент", - структура заполненная нулями, по аналогии с признаком конца строк в языке Си.
static PyObject *
do_something(PyObject *self, PyObject *args) {
PyErr_SetString(PyExc_RuntimeError, "Not implemented yet");
return NULL;
}
static PyMethodDef methods[] = {
{
// имя Python-функции
.ml_name = "do_something",
// указатель на Си-функцию
.ml_meth = do_something,
// флаги использования Си-функции
.ml_flags = METH_VARARGS,
// строка описания, выдается функцией help()
.ml_doc = "Do something very useful"
},
// признак конца массива описаний методов
{NULL, NULL, 0, NULL}
};
Каждая Си-функция, которая реализует Python-функцию, должна возвращать объект PyObject*
, и принимает минимум один аргумент - указатель на сам объект модуля.
Си-функции могут иметь разные аргументы (включая из количество), в зависимости о того, как допускается вызывать метод. Это поведение определяется флагами в поле ml_flags
.
- С одним аргументом:
(PyObject *self)
- в случае, если функция не принимает никаких аргументов и значение в.ml_flags = METH_NOARGS
- С двумя аргументами:
(PyObject *self, PyObject *argsTuple)
, причем второй аргумент является кортежем (возможно пустым), - в случае если функция принимает переменное количество позиционных аргументов и значение в.ml_flags = METH_VARARGS
- С двумя аргументами:
(PyObject *self, PyObject *argsDict)
, причем второй аргумент является словарем (возможно пустым), - в случае если функция принимает переменное количество именованных аргументов и значение в.ml_flags = METH_KEYWORDS
- С тремя аргументами:
(PyObject *self, PyObject *argsTuple, PyObject *argsDict)
, где второй аргумент - это кортеж из позиционных аргументов, а третий - это словарь именованных аргументов, - при значении.ml_flags = METH_KEYWORDS|METH_VARARGS
Возвращаемое значение NULL
вместо объекта PyObject*
означает исключительную ситуацию. В языке Python любая функция должна возвращать хоть какой-нибудь объект. С точки зрения синтаксиса языка, отсутствие возвращаемого значения означает, что будет возвращен объект типа NoneType
, который называется None
.
Объект типа None
существует в единственном экзепляре на весь интерпретатор, но при этом может много где ипользоваться, и к нему, как и к любому объекту, применяются обычные правила подсчета ссылок.
def a(): pass # функция, которая "ничего не возвращает"
b = a() # b = None, причем вызов a() увеличил счетчик ссылок на None
a() # возращается None, и результат отбрасывается,
# поскольку он ничему не присвоен. В момент вызова a()
# увеличивается счетчик ссылок, при отсутствии левой части
# присваивания счетчик сслок уменьшается
При создании новых объектов из Си-кода, счетчик ссылок устанавливается равным 1, и обычно никаких действий по его увеличению не требуется, если объекты возвращаются интерпретатору:
static PyObject *
func_returning_string(PyObject *self)
{
PyObject *ret = PyUnicode_FromString("Hello"); // ret->ob_refcnt=1
return ret; // OK
}
# из Python:
a = func_returning_string() # ret -> a, refcnt=1
func_returning_string() # del ret, refcnt=1 --> refcnt=0
В случае использования None
, поскольку он существует в единственном экземпляре, нужно увеличить количество ссылок:
static PyObject *
func_returning_none(PyObject *self)
{
// Py_None - это указатель на статический объект _Py_NoneStruct
Py_INCREF(Py_None); // Py_None->ob_refcnt ++
return Py_None;
}
Си-модуль для языка Python - это разделяемая библиотека, которая загружается через механизм dlopen
, и поэтому должна быть скомпилирована в позиционно-независимый код (опция -fPIC
компилятора).
Библиотека имеет не стандартное имя файла:
МОДУЛЬ.so
- для Mac, Linux и *BSD. Обратите внимание на отсутствие префиксаlib
в имени, и кроме того, в Mac используется суффикс.so
вместо.dynlib
МОДУЛЬ.pyd
илиМОДУЛЬd.pyd
- для Windows. Вместо суффикса.dll
используется.pyd
илиd.pyd
(для варианта сборки с отладочной информацией).
Единственная функция, которая обязана быть реализована в библиотеке - это функция:
PyObject* PyInit_МОДУЛЬ();
Функция должна создавать и возвращать объект модуля, по аналогии с расширением интерпретатора встроенным модулем.
Если код модуля реализуется на языке C++, то необходимо отключить преобразование имен с помощью extern "C"
, а в случае с операционной системой Windows и компилятором MSVC - ещё и объявить функцию экспортируемой: __declspec(dllexport)
.
Все платформо-зависимые объявления спрятаны в макрос PyMODINIT_FUNC
, значение которого определяется препроцессором. Таким образом, модуль реализуется функцией:
PyMODINIT_FUNC PyInit_my_great_module() {
static PyModuleDef modDef = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "my_great_module",
....
}
return PyModule_Create(&modDef);
}
Обратите внимание, что имя файла с модулем, имя части функции после PyInit_
и имя самого модуля должны совпадать, иначе интерпретатор не сможет найти и загрузить его.
Все остальные функцие модуля могут быть статическими, а не экспортироваться из библиотеки, поскольку указатели на них явным образом будут присутствовать в объекте, который вернет функция инициализации.
В CMake-пакете PythonLibs
определяется функция для создания цели-модуля:
find_package(PythonLibs 3.6 REQUIRED)
python_add_module(my_great_module module.c)
Эта цель определяет необходимые опции компиляции для сборки позиционно-независимого кода с правильным именем, независимо от используемой операционной системы.
Для загрузки модуля из Python необходимо разместить его в одном из каталогов поиска модулей Python, либо рядом с файлом скрипта, который его использует. Python при этом корректно работает с символическими ссылками.
Отладчик GDB позволяет ставить точки останова в любой части программы, для которой существует отладочная информация. Таким образом, если собрать модуль отдельно с опцией -g
, то вместе с ним можно использовать gdb
даже в том случае, если для самого интерпретатора отладочная информация отсутствует. Целевой программой для gdb
указывается исполняемый файл интерпретатора python3
, а сам тестовый скрипт - в качестве аргумента запуска.
> gdb python3
(gdb) b module.c:112
(gdb) r script.py
Современные версии gdb
(начиная с 7.x) включают поддержку расширений для типов данных Python API, и использование команды отладчика print
вызывает метод repr
языка Python для выводимых объектов, а он, в свою очередь - подразумевает вызов функции PyObject_Repr(PyObject *obj)
для произвольного Python-объекта.
В случае, если переменная не инициализирована, и содержит мусор по указателю, то это может приводить к ошибке нарушения сегментации, причиной которой становится сам отладчик, вызывая print
. При использовании интегрированных сред разработки, эта команда отладчика вызывается очень часто для обновления значений локальных переменных, что может приводить к печальным последствиям.
PyObject* some_function(PyObject *self, PyObject *args) {
// <-- точка останова где-то здесь
....
....
PyObject * value = ...
...
}
В данном примере отладчик инициирует ошибку нарушения сегментации, поскольку локальная переменная value
ещё не инициализирована. Для того, чтобы этого избежать, есть два способа:
-
Отключить использования вызова
repr
для Python-объектов командой отладчикаdisable pretty-printer
(в среде QtCreator это делается автоматически при снятии чекбокса "Use Debugging Helper" в настройках отладчика), - в этом случае все Python-объекты будут отображаться как Си-структуры. -
Реорганизовать код таким образом, чтобы на момент остановки отладчиком все локальные переменные были инициализированы.
PyObject* some_function(PyObject *self, PyObject *args) { PyObject * value = NULL; .... // <-- точка останова после инициализации всех PyObject* .... value = ... ... }