Spec-Zone.ru › Python 3.11

Расширение Python с помощью C или C++

Добавить в Python новые встроенные модули довольно просто, если вы умеете программировать на C. Такие модули расширения могут делать две вещи, которые нельзя выполнить непосредственно в Python: реализовывать новые встроенные типы объектов и вызывать функции библиотек C и системные вызовы.

Для поддержки расширений Python API (интерфейс прикладного программирования) определяет набор функций, макросов и переменных, предоставляющих доступ к большинству аспектов среды выполнения Python. Python API подключается к исходному файлу C с помощью заголовочного файла "Python.h".

Компиляция модуля расширения зависит как от предполагаемого способа его использования, так и от конфигурации вашей системы; подробности приведены в последующих главах.

Примечание

Интерфейс расширений C специфичен для CPython, и модули расширения не работают в других реализациях Python. Во многих случаях можно обойтись без написания расширений C и сохранить переносимость на другие реализации. Например, если вам нужно вызывать функции библиотек C или системные вызовы, рассмотрите возможность использования модуля ctypes или библиотеки cffi вместо написания собственного кода на C. Эти модули позволяют писать код на Python для взаимодействия с кодом на C и обеспечивают большую переносимость между реализациями Python, чем написание и компиляция модуля расширения C.

1.1. Простой пример

Создадим модуль расширения под названием spam (любимое блюдо поклонников Монти Пайтона…) и предположим, что мы хотим создать интерфейс Python для функции библиотеки C system() [1]. Эта функция принимает в качестве аргумента строку символов с нулевым завершением и возвращает целое число. Мы хотим, чтобы эту функцию можно было вызывать из Python следующим образом:

>>> import spam
>>> status = spam.system("ls -l")

Начните с создания файла spammodule.c. (Исторически, если модуль называется spam, файл C с его реализацией называется spammodule.c; если имя модуля очень длинное, например spammify, имя файла модуля может быть просто spammify.c.)

Первые две строки нашего файла могут выглядеть так:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

они подключают Python API (при желании можно добавить комментарий с описанием назначения модуля и уведомление об авторских правах).

Примечание

Поскольку Python может определять некоторые макросы препроцессора, которые влияют на стандартные заголовочные файлы в некоторых системах, Python.h необходимо подключить до подключения любых стандартных заголовочных файлов.

Рекомендуется всегда определять PY_SSIZE_T_CLEAN до подключения Python.h. Описание этого макроса см. в разделе Извлечение параметров в функциях расширения.

Все видимые пользователю символы, определённые в Python.h, имеют префикс Py или PY, за исключением символов, определённых в стандартных заголовочных файлах. Для удобства, а также поскольку интерпретатор Python активно их использует, "Python.h" включает несколько стандартных заголовочных файлов: <stdio.h>, <string.h>, <errno.h> и <stdlib.h>. Если последний заголовочный файл отсутствует в вашей системе, он объявляет функции malloc(), free() и realloc() непосредственно.

Следующее, что мы добавим в файл модуля, — это функция C, которая будет вызываться при вычислении выражения Python spam.system(string) (вскоре мы увидим, как именно она вызывается):

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}

Список аргументов Python (например, единственное выражение "ls -l") напрямую преобразуется в аргументы, передаваемые функции C. У функции C всегда два аргумента, традиционно называемые self и args.

Аргумент self указывает на объект модуля для функций уровня модуля; для метода он указывал бы на экземпляр объекта.

Аргумент args — это указатель на объект-кортеж Python, содержащий аргументы. Каждый элемент кортежа соответствует аргументу в списке аргументов вызова. Аргументы являются объектами Python — чтобы выполнять с ними какие-либо действия в нашей функции C, необходимо преобразовать их в значения C. Функция PyArg_ParseTuple() из Python API проверяет типы аргументов и преобразует их в значения C. Она использует строку формата, чтобы определить требуемые типы аргументов, а также типы переменных C, в которые следует поместить преобразованные значения. Об этом подробнее рассказывается далее.

PyArg_ParseTuple() возвращает истину (ненулевое значение), если все аргументы имеют правильный тип и их компоненты сохранены в переменных, адреса которых переданы функции. Она возвращает ложь (нулевое значение), если был передан недопустимый список аргументов. В последнем случае она также возбуждает соответствующее исключение, чтобы вызывающая функция могла немедленно вернуть NULL (как показано в примере).

1.2. Отступление: ошибки и исключения

Во всём интерпретаторе Python действует важное соглашение: если функция завершается неудачно, она должна установить состояние исключения и вернуть значение ошибки (обычно -1 или указатель NULL). Информация об исключении хранится в трёх полях состояния потока интерпретатора. Если исключения нет, эти поля имеют значение NULL. В противном случае они содержат эквиваленты на C элементов кортежа Python, возвращаемого функцией sys.exc_info(). Это тип исключения, экземпляр исключения и объект трассировки стека. Чтобы понимать, как передаются ошибки, важно знать об этих полях.

Python API определяет ряд функций для установки исключений различных типов.

Чаще всего используется функция PyErr_SetString(). Она принимает объект исключения и строку C. Объект исключения обычно является предопределённым объектом, например PyExc_ZeroDivisionError. Строка C указывает причину ошибки, преобразуется в строковый объект Python и сохраняется как «связанное значение» исключения.

Ещё одна полезная функция — PyErr_SetFromErrno(): она принимает только аргумент-исключение и формирует связанное значение, анализируя глобальную переменную errno. Наиболее универсальная функция — PyErr_SetObject(), принимающая два объектных аргумента: исключение и связанное с ним значение. Для объектов, переданных любой из этих функций, вызывать Py_INCREF() не требуется.

Проверить, установлено ли исключение, не изменяя состояние, можно с помощью функции PyErr_Occurred(). Она возвращает текущий объект исключения или NULL, если исключение не возникло. Обычно вызывать PyErr_Occurred() для проверки ошибки при вызове функции не требуется, поскольку о ней можно судить по возвращаемому значению.

Если функция f, вызывающая другую функцию g, обнаруживает, что последняя завершилась неудачно, функция f сама должна вернуть значение ошибки (обычно NULL или -1). Она не должна вызывать одну из функций PyErr_* — такую функцию уже вызвала g. Затем вызывающая функция f также должна вернуть признак ошибки своей вызывающей функции, опять же не вызывая PyErr_*, и так далее: наиболее подробная причина ошибки уже сообщена функцией, которая первой её обнаружила. Когда ошибка достигает главного цикла интерпретатора Python, выполнение текущего кода Python прерывается, после чего интерпретатор пытается найти обработчик исключения, указанный программистом на Python.

(Бывают ситуации, когда модуль действительно может выдать более подробное сообщение об ошибке, вызвав другую функцию PyErr_*; в таких случаях это допустимо. Однако, как правило, в этом нет необходимости, и это может привести к потере сведений о причине ошибки: многие операции могут завершиться неудачно по разным причинам.)

Чтобы проигнорировать исключение, установленное неудачным вызовом функции, необходимо явно очистить состояние исключения вызовом PyErr_Clear(). Код C должен вызывать PyErr_Clear() только тогда, когда он не хочет передавать ошибку интерпретатору, а намерен полностью обработать её самостоятельно (возможно, попробовав что-то ещё или сделав вид, что ничего не произошло).

Каждый неудачный вызов malloc() необходимо преобразовать в исключение — непосредственный вызывающий код malloc() (или realloc()) должен вызвать PyErr_NoMemory() и сам вернуть признак ошибки. Все функции создания объектов (например, PyLong_FromLong()) уже делают это, поэтому данное замечание относится только к тем, кто непосредственно вызывает malloc().

Также обратите внимание: за важным исключением PyArg_ParseTuple() и родственных функций, функции, возвращающие целочисленный код состояния, обычно возвращают положительное значение или ноль при успешном выполнении и -1 при ошибке, как системные вызовы Unix.

Наконец, не забывайте очищать мусор (вызывая Py_XDECREF() или Py_DECREF() для уже созданных объектов), когда возвращаете признак ошибки!

Выбор исключения, которое следует возбудить, полностью за вами. Для всех встроенных исключений Python существуют предварительно объявленные объекты C, например PyExc_ZeroDivisionError, которые можно использовать напрямую. Разумеется, выбирайте исключения разумно: не используйте PyExc_TypeError в значении «не удалось открыть файл» (для этого, вероятно, подойдёт PyExc_OSError). Если проблема связана со списком аргументов, функция PyArg_ParseTuple() обычно возбуждает PyExc_TypeError. Если значение аргумента должно находиться в определённом диапазоне или удовлетворять другим условиям, подойдёт PyExc_ValueError.

Можно также определить новое исключение, уникальное для вашего модуля. Для этого обычно объявляют статическую переменную-объект в начале файла:

static PyObject *SpamError;

и инициализируют её в функции инициализации модуля (PyInit_spam()), создав объект исключения:

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

Обратите внимание, что имя объекта исключения в Python — spam.error. Функция PyErr_NewException() может создать класс, базовым классом которого будет Exception (если вместо NULL не передан другой класс); подробнее см. в разделе Встроенные исключения.

Обратите также внимание: переменная SpamError сохраняет ссылку на только что созданный класс исключения — это сделано намеренно! Поскольку внешний код может удалить исключение из модуля, необходимо сохранить собственную ссылку на класс, чтобы он не был уничтожен и SpamError не превратился в висячий указатель. Если указатель станет висячим, код C, возбуждающий это исключение, может вызвать аварийное завершение программы или другие непредвиденные последствия.

Использование PyMODINIT_FUNC в качестве типа возвращаемого значения функции мы обсудим далее в этом примере.

Исключение spam.error можно возбудить в модуле расширения, вызвав PyErr_SetString(), как показано ниже:

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}

1.3. Возвращаемся к примеру

Вернёмся к нашей функции-примеру. Теперь вам должно быть понятно следующее выражение:

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;

Если в списке аргументов обнаружена ошибка, функция возвращает NULL (признак ошибки для функций, возвращающих указатели на объекты), полагаясь на исключение, установленное функцией PyArg_ParseTuple(). В противном случае строковое значение аргумента копируется в локальную переменную command. Это присваивание указателя, и изменять строку, на которую он указывает, нельзя (поэтому в стандартном C переменную command следовало бы объявить как const char *command).

Следующее выражение — вызов функции Unix system() с передачей строки, которую мы только что получили из PyArg_ParseTuple():

sts = system(command);

Наша функция spam.system() должна вернуть значение sts в виде объекта Python. Для этого используется функция PyLong_FromLong().

return PyLong_FromLong(sts);

В этом случае она вернёт целочисленный объект. (Да, даже целые числа в Python являются объектами в куче!)

Если у вас есть функция C, не возвращающая полезного значения (то есть функция с типом возвращаемого значения void), соответствующая функция Python должна возвращать None. Для этого используйте следующий шаблон (реализованный макросом Py_RETURN_NONE):

Py_INCREF(Py_None);
return Py_None;

Py_None — это имя на C специального объекта Python None. Это полноценный объект Python, а не указатель NULL, который в большинстве контекстов означает «ошибка», как мы уже видели.

1.4. Таблица методов модуля и функция инициализации

Я обещал показать, как spam_system() вызывается из программ Python. Сначала нужно указать её имя и адрес в «таблице методов»:

static PyMethodDef SpamMethods[] = {
    ...
    {"system",  spam_system, METH_VARARGS,
     "Execute a shell command."},
    ...
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

Обратите внимание на третий элемент (METH_VARARGS). Это флаг, сообщающий интерпретатору, какое соглашение о вызовах следует использовать для функции C. Обычно его всегда следует задавать как METH_VARARGS или METH_VARARGS | METH_KEYWORDS; значение 0 означает, что используется устаревший вариант функции PyArg_ParseTuple().

При использовании только METH_VARARGS функция должна ожидать, что параметры уровня Python будут переданы в виде кортежа, который можно обработать с помощью PyArg_ParseTuple(); подробнее об этой функции говорится ниже.

Если функции следует передавать именованные аргументы, в третьем поле можно установить бит METH_KEYWORDS. В этом случае функция C должна принимать третий параметр PyObject *, представляющий собой словарь именованных аргументов. Для разбора аргументов такой функции используйте PyArg_ParseTupleAndKeywords().

На таблицу методов необходимо сослаться в структуре определения модуля:

static struct PyModuleDef spammodule = {
    PyModuleDef_HEAD_INIT,
    "spam",   /* name of module */
    spam_doc, /* module documentation, may be NULL */
    -1,       /* size of per-interpreter state of the module,
                 or -1 if the module keeps state in global variables. */
    SpamMethods
};

Эту структуру, в свою очередь, необходимо передать интерпретатору в функции инициализации модуля. Функция инициализации должна называться PyInit_name(), где name — имя модуля; она должна быть единственным элементом, определённым в файле модуля, который не является static:

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModule_Create(&spammodule);
}

Обратите внимание: PyMODINIT_FUNC объявляет функцию с типом возвращаемого значения PyObject *, задаёт специальные объявления связывания, требуемые платформой, а для C++ объявляет функцию как extern "C".

Когда программа Python впервые импортирует модуль spam, вызывается PyInit_spam(). (Замечания о встраивании Python приведены ниже.) Эта функция вызывает PyModule_Create(), которая возвращает объект модуля и вставляет в созданный модуль объекты встроенных функций на основании таблицы (массива структур PyMethodDef), указанной в определении модуля. Функция PyModule_Create() возвращает указатель на созданный ею объект модуля. При некоторых ошибках она может прервать выполнение с фатальной ошибкой или вернуть NULL, если модуль не удалось успешно инициализировать. Функция инициализации должна вернуть объект модуля вызывающему коду, чтобы он был добавлен в sys.modules.

При встраивании Python функция PyInit_spam() не вызывается автоматически, если только в таблице PyImport_Inittab нет соответствующей записи. Чтобы добавить модуль в таблицу инициализации, используйте PyImport_AppendInittab(), при необходимости дополнив вызов импортом модуля:

int
main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    if (program == NULL) {
        fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
        exit(1);
    }

    /* Add a built-in module, before Py_Initialize */
    if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
        fprintf(stderr, "Error: could not extend in-built modules table\n");
        exit(1);
    }

    /* Pass argv[0] to the Python interpreter */
    Py_SetProgramName(program);

    /* Initialize the Python interpreter.  Required.
       If this step fails, it will be a fatal error. */
    Py_Initialize();

    /* Optionally import the module; alternatively,
       import can be deferred until the embedded script
       imports it. */
    PyObject *pmodule = PyImport_ImportModule("spam");
    if (!pmodule) {
        PyErr_Print();
        fprintf(stderr, "Error: could not import module 'spam'\n");
    }

    ...

    PyMem_RawFree(program);
    return 0;
}

Примечание

Удаление записей из sys.modules или импорт скомпилированных модулей в несколько интерпретаторов в одном процессе (либо выполнение fork() без промежуточного exec()) может вызвать проблемы у некоторых модулей расширения. Авторам модулей расширения следует проявлять осторожность при инициализации внутренних структур данных.

Более содержательный пример модуля входит в состав исходного дистрибутива Python под именем Modules/xxmodule.c. Этот файл можно использовать как шаблон или просто прочитать в качестве примера.

Примечание

В отличие от нашего примера spam, в xxmodule используется многофазная инициализация (появившаяся в Python 3.5): структура PyModuleDef возвращается из PyInit_spam, а создание модуля поручается механизму импорта. Подробнее о многофазной инициализации см. в PEP 489.

1.5. Компиляция и компоновка

Прежде чем использовать новое расширение, нужно выполнить ещё две операции: скомпилировать его и скомпоновать с системой Python. Если используется динамическая загрузка, детали могут зависеть от применяемого в вашей системе способа динамической загрузки; дополнительную информацию см. в главах о сборке модулей расширения (глава Сборка расширений C и C++) и о сборке в Windows (глава Сборка расширений C и C++ в Windows).

Если динамическую загрузку использовать нельзя или если вы хотите сделать модуль постоянной частью интерпретатора Python, придётся изменить конфигурацию и пересобрать интерпретатор. К счастью, в Unix это очень просто: поместите файл (например, spammodule.c) в каталог Modules/ распакованного исходного дистрибутива и добавьте в файл Modules/Setup.local строку с описанием вашего файла:

spam spammodule.o

затем пересоберите интерпретатор, выполнив make в каталоге верхнего уровня. Можно также запустить make в подкаталоге Modules/, но тогда сначала необходимо пересобрать там Makefile, выполнив команду «make Makefile». (Это требуется каждый раз при изменении файла Setup.)

Если для компоновки вашего модуля требуются дополнительные библиотеки, их также можно указать в строке файла конфигурации, например:

spam spammodule.o -lX11

1.6. Вызов функций Python из C

До сих пор мы сосредоточились на том, как сделать функции C доступными для вызова из Python. Полезно и обратное: вызывать функции Python из C. Особенно это актуально для библиотек, поддерживающих так называемые функции «обратного вызова». Если интерфейс C использует обратные вызовы, эквивалентный интерфейс Python часто должен предоставлять программисту на Python механизм обратного вызова; для его реализации потребуется вызывать функции обратного вызова Python из обратного вызова C. Можно представить и другие варианты использования.

К счастью, интерпретатор Python легко вызывать рекурсивно, и существует стандартный интерфейс для вызова функции Python. (Я не буду подробно разбирать, как вызвать синтаксический анализатор Python, передав ему определённую строку; если вас это интересует, ознакомьтесь с реализацией параметра командной строки -c в Modules/main.c из исходного кода Python.)

Вызвать функцию Python несложно. Сначала программа Python должна каким-либо образом передать вам объект функции Python. Для этого следует предоставить функцию (или другой интерфейс). При её вызове сохраните указатель на объект функции Python (не забудьте вызвать для него Py_INCREF()!) в глобальной переменной — или в любом другом подходящем месте. Например, следующая функция может быть частью определения модуля:

static PyObject *my_callback = NULL;

static PyObject *
my_set_callback(PyObject *dummy, PyObject *args)
{
    PyObject *result = NULL;
    PyObject *temp;

    if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
        if (!PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "parameter must be callable");
            return NULL;
        }
        Py_XINCREF(temp);         /* Add a reference to new callback */
        Py_XDECREF(my_callback);  /* Dispose of previous callback */
        my_callback = temp;       /* Remember new callback */
        /* Boilerplate to return "None" */
        Py_INCREF(Py_None);
        result = Py_None;
    }
    return result;
}

Эту функцию необходимо зарегистрировать в интерпретаторе с флагом METH_VARARGS; это описано в разделе Таблица методов модуля и функция инициализации. Функция PyArg_ParseTuple() и её аргументы описаны в разделе Извлечение параметров в функциях расширения.

Макросы Py_XINCREF() и Py_XDECREF() увеличивают и уменьшают счётчик ссылок объекта и безопасны при наличии указателей NULL (но учтите, что в данном контексте temp не будет NULL). Подробнее о них см. в разделе Счётчики ссылок.

Позднее, когда придёт время вызвать функцию, вызовите функцию C PyObject_CallObject(). У неё два аргумента — указатели на произвольные объекты Python: функцию Python и список аргументов. Список аргументов всегда должен быть объектом-кортежем, длина которого равна числу аргументов. Чтобы вызвать функцию Python без аргументов, передайте NULL или пустой кортеж; чтобы вызвать её с одним аргументом, передайте одноэлементный кортеж. Функция Py_BuildValue() возвращает кортеж, если её строка формата состоит из нуля или более кодов формата, заключённых в круглые скобки. Например:

int arg;
PyObject *arglist;
PyObject *result;
...
arg = 123;
...
/* Time to call the callback */
arglist = Py_BuildValue("(i)", arg);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);

PyObject_CallObject() возвращает указатель на объект Python — это возвращаемое значение функции Python. Функция PyObject_CallObject() не изменяет счётчики ссылок своих аргументов. В примере для списка аргументов был создан новый кортеж, вызов Py_DECREF() для которого выполняется сразу после вызова PyObject_CallObject().

Возвращаемое значение PyObject_CallObject() является «новым»: это либо совершенно новый объект, либо существующий объект с увеличенным счётчиком ссылок. Поэтому, если вы не хотите сохранить его в глобальной переменной, результат нужно каким-либо образом освободить с помощью Py_DECREF(), даже (и особенно!) если его значение вас не интересует.

Однако прежде чем это сделать, важно проверить, не равно ли возвращаемое значение NULL. Если равно, функция Python завершилась, возбудив исключение. Если код C, вызвавший PyObject_CallObject(), вызывается из Python, он должен вернуть признак ошибки вызывающей функции Python, чтобы интерпретатор мог вывести трассировку стека или вызывающий код Python мог обработать исключение. Если это невозможно или нежелательно, исключение следует очистить вызовом PyErr_Clear(). Например:

if (result == NULL)
    return NULL; /* Pass error back */
...use result...
Py_DECREF(result);

В зависимости от требуемого интерфейса функции обратного вызова Python, возможно, придётся также передать список аргументов в PyObject_CallObject(). В некоторых случаях список аргументов также предоставляет программа Python через тот же интерфейс, который задаёт функцию обратного вызова. Тогда его можно сохранить и использовать так же, как объект функции. В других случаях список аргументов придётся сформировать в виде нового кортежа. Проще всего сделать это с помощью функции Py_BuildValue(). Например, если требуется передать целочисленный код события, можно использовать следующий код:

PyObject *arglist;
...
arglist = Py_BuildValue("(l)", eventcode);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);

Обратите внимание на расположение Py_DECREF(arglist) сразу после вызова, до проверки ошибки! Также учтите, что этот код, строго говоря, неполон: у функции Py_BuildValue() может закончиться память, и это необходимо проверять.

Функцию с именованными аргументами также можно вызвать с помощью PyObject_Call(), поддерживающей позиционные и именованные аргументы. Как и в примере выше, для создания словаря используем Py_BuildValue().

PyObject *dict;
...
dict = Py_BuildValue("{s:i}", "name", val);
result = PyObject_Call(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);

1.7. Извлечение параметров в функциях расширения

Функция PyArg_ParseTuple() объявлена следующим образом:

int PyArg_ParseTuple(PyObject *arg, const char *format, ...);

Аргумент arg должен быть объектом-кортежем, содержащим список аргументов, переданный из Python в функцию C. Аргумент format должен быть строкой формата, синтаксис которой объясняется в разделе Разбор аргументов и создание значений справочного руководства по API Python/C. Остальные аргументы должны быть адресами переменных, типы которых определяются строкой формата.

Обратите внимание: хотя функция PyArg_ParseTuple() проверяет, что аргументы Python имеют требуемые типы, она не может проверить корректность адресов переменных C, переданных при вызове: если здесь допустить ошибку, код, скорее всего, аварийно завершится или по меньшей мере перезапишет случайные данные в памяти. Поэтому будьте осторожны!

Обратите внимание: любые ссылки на объекты Python, предоставленные вызывающему коду, являются заимствованными; не уменьшайте их счётчик ссылок!

Примеры вызовов:

#define PY_SSIZE_T_CLEAN  /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>
int ok;
int i, j;
long k, l;
const char *s;
Py_ssize_t size;

ok = PyArg_ParseTuple(args, ""); /* No arguments */
    /* Python call: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* A string */
    /* Possible Python call: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
    /* Possible Python call: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
    /* A pair of ints and a string, whose size is also returned */
    /* Possible Python call: f((1, 2), 'three') */
{
    const char *file;
    const char *mode = "r";
    int bufsize = 0;
    ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
    /* A string, and optionally another string and an integer */
    /* Possible Python calls:
       f('spam')
       f('spam', 'w')
       f('spam', 'wb', 100000) */
}
{
    int left, top, right, bottom, h, v;
    ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
             &left, &top, &right, &bottom, &h, &v);
    /* A rectangle and a point */
    /* Possible Python call:
       f(((0, 0), (400, 300)), (10, 10)) */
}
{
    Py_complex c;
    ok = PyArg_ParseTuple(args, "D:myfunction", &c);
    /* a complex, also providing a function name for errors */
    /* Possible Python call: myfunction(1+2j) */
}

1.8. Именованные параметры для функций расширения

Функция PyArg_ParseTupleAndKeywords() объявлена следующим образом:

int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
                                const char *format, char *kwlist[], ...);

Параметры arg и format идентичны параметрам функции PyArg_ParseTuple(). Параметр kwdict — это словарь именованных аргументов, полученный от среды выполнения Python в качестве третьего параметра. Параметр kwlist — это список строк, завершающийся NULL, в котором указаны имена параметров; эти имена сопоставляются с информацией о типах из format слева направо. В случае успеха PyArg_ParseTupleAndKeywords() возвращает true, иначе возвращает false и возбуждает соответствующее исключение.

Примечание

Вложенные кортежи нельзя разбирать при использовании именованных аргументов! Передача именованных параметров, отсутствующих в kwlist, приведёт к возбуждению исключения TypeError.

Ниже приведён пример модуля, использующего именованные аргументы и основанного на примере Джеффа Филбрика (philbrick@hks.com):

#define PY_SSIZE_T_CLEAN  /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>

static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
{
    int voltage;
    const char *state = "a stiff";
    const char *action = "voom";
    const char *type = "Norwegian Blue";

    static char *kwlist[] = {"voltage", "state", "action", "type", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,
                                     &voltage, &state, &action, &type))
        return NULL;

    printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
           action, voltage);
    printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);

    Py_RETURN_NONE;
}

static PyMethodDef keywdarg_methods[] = {
    /* The cast of the function is necessary since PyCFunction values
     * only take two PyObject* parameters, and keywdarg_parrot() takes
     * three.
     */
    {"parrot", (PyCFunction)(void(*)(void))keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
     "Print a lovely skit to standard output."},
    {NULL, NULL, 0, NULL}   /* sentinel */
};

static struct PyModuleDef keywdargmodule = {
    PyModuleDef_HEAD_INIT,
    "keywdarg",
    NULL,
    -1,
    keywdarg_methods
};

PyMODINIT_FUNC
PyInit_keywdarg(void)
{
    return PyModule_Create(&keywdargmodule);
}

1.9. Создание произвольных значений

Эта функция является аналогом PyArg_ParseTuple(). Она объявлена следующим образом:

PyObject *Py_BuildValue(const char *format, ...);

Функция распознаёт набор единиц формата, похожий на тот, который распознаёт PyArg_ParseTuple(), однако аргументы (которые являются входными, а не выходными для функции) должны быть не указателями, а значениями. Функция возвращает новый объект Python, пригодный для возврата из функции C, вызванной из Python.

Отличие от PyArg_ParseTuple() состоит в следующем: если последней для работы требуется, чтобы её первый аргумент был кортежем (поскольку списки аргументов Python всегда представлены внутри в виде кортежей), то Py_BuildValue() создаёт кортеж не всегда. Кортеж создаётся, только если строка формата содержит две или более единицы формата. Если строка формата пуста, функция возвращает None; если она содержит ровно одну единицу формата, функция возвращает объект, описанный этой единицей формата. Чтобы принудительно вернуть кортеж размера 0 или 1, заключите строку формата в круглые скобки.

Примеры (слева — вызов, справа — результирующее значение Python):

Py_BuildValue("")                        None
Py_BuildValue("i", 123)                  123
Py_BuildValue("iii", 123, 456, 789)      (123, 456, 789)
Py_BuildValue("s", "hello")              'hello'
Py_BuildValue("y", "hello")              b'hello'
Py_BuildValue("ss", "hello", "world")    ('hello', 'world')
Py_BuildValue("s#", "hello", 4)          'hell'
Py_BuildValue("y#", "hello", 4)          b'hell'
Py_BuildValue("()")                      ()
Py_BuildValue("(i)", 123)                (123,)
Py_BuildValue("(ii)", 123, 456)          (123, 456)
Py_BuildValue("(i,i)", 123, 456)         (123, 456)
Py_BuildValue("[i,i]", 123, 456)         [123, 456]
Py_BuildValue("{s:i,s:i}",
              "abc", 123, "def", 456)    {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",
              1, 2, 3, 4, 5, 6)          (((1, 2), (3, 4)), (5, 6))

1.10. Счётчики ссылок

В таких языках, как C или C++, программист отвечает за динамическое выделение и освобождение памяти в куче. В C для этого используются функции malloc() и free(). В C++ для той же цели используются операторы new и delete, и далее мы ограничимся рассмотрением языка C.

Каждый блок памяти, выделенный с помощью malloc(), в конечном итоге должен быть возвращён в пул доступной памяти ровно одним вызовом free(). Важно вызывать free() в нужное время. Если адрес блока забыт, но для него не был вызван free(), занимаемую им память нельзя будет повторно использовать до завершения программы. Это называется утечкой памяти. С другой стороны, если программа вызывает free() для блока, а затем продолжает его использовать, возникает конфликт с повторным использованием блока при другом вызове malloc(). Это называется использованием освобождённой памяти. Последствия столь же пагубны, как и при обращении к неинициализированным данным: аварийные дампы, неверные результаты, загадочные сбои.

Часто утечки памяти возникают из-за необычных путей выполнения кода. Например, функция может выделить блок памяти, выполнить некоторые вычисления, а затем освободить блок. Позже изменение требований к функции может привести к добавлению в вычисления проверки, обнаруживающей ошибочное условие и позволяющей досрочно выйти из функции. При таком досрочном выходе легко забыть освободить выделенный блок памяти, особенно если эту проверку добавили позднее. Такие утечки после появления часто долго остаются незамеченными: выход по ошибке выполняется лишь в небольшой доле всех вызовов, а у большинства современных компьютеров много виртуальной памяти, поэтому утечка становится заметной только в длительно работающем процессе, который часто вызывает функцию с утечкой. Поэтому важно предотвращать утечки, используя соглашение или стратегию программирования, позволяющие свести к минимуму подобные ошибки.

Поскольку Python активно использует malloc() и free(), ему также нужна стратегия предотвращения утечек памяти и использования освобождённой памяти. Выбранный метод называется подсчётом ссылок. Принцип прост: каждый объект содержит счётчик, который увеличивается при сохранении где-либо ссылки на объект и уменьшается при удалении ссылки на него. Когда счётчик достигает нуля, последняя ссылка на объект удалена, и объект освобождается.

Альтернативная стратегия называется автоматической сборкой мусора. (Иногда подсчёт ссылок тоже называют стратегией сборки мусора, поэтому я использую слово «автоматическая», чтобы различать эти два подхода.) Главное преимущество автоматической сборки мусора заключается в том, что пользователю не нужно явно вызывать free(). (Ещё одно заявленное преимущество — повышение скорости или снижение потребления памяти, однако это не установленный факт.) Недостаток состоит в том, что для C не существует полностью переносимого автоматического сборщика мусора, тогда как подсчёт ссылок можно реализовать переносимым способом (если доступны функции malloc() и free(), наличие которых гарантировано стандартом C). Возможно, когда-нибудь для C появится достаточно переносимый автоматический сборщик мусора. А пока нам придётся обходиться подсчётом ссылок.

Хотя Python использует традиционную реализацию подсчёта ссылок, в нём также есть детектор циклов, предназначенный для обнаружения циклических ссылок. Благодаря этому приложениям не нужно беспокоиться о создании прямых или косвенных циклических ссылок — это слабое место сборки мусора, основанной только на подсчёте ссылок. Циклические ссылки образованы объектами, содержащими (возможно, косвенные) ссылки на самих себя, так что счётчик ссылок каждого объекта в цикле ненулевой. Типичные реализации подсчёта ссылок не способны освободить память, занятую объектами в цикле ссылок или объектами, на которые ссылаются объекты цикла, даже если на сам цикл больше нет ссылок.

Детектор циклов способен обнаруживать циклы мусора и освобождать их. Модуль gc предоставляет возможность запускать детектор (функция collect()), а также интерфейсы настройки и возможность отключить детектор во время выполнения.

1.10.1. Подсчёт ссылок в Python

Для увеличения и уменьшения счётчика ссылок предназначены два макроса: Py_INCREF(x) и Py_DECREF(x). Макрос Py_DECREF() также освобождает объект, когда счётчик достигает нуля. Для большей гибкости он не вызывает free() напрямую, а обращается к функции через указатель на функцию в объекте типа. Для этой и других целей каждый объект также содержит указатель на свой объект типа.

Теперь остаётся главный вопрос: когда использовать Py_INCREF(x) и Py_DECREF(x)? Сначала введём несколько терминов. Никто не «владеет» объектом, однако можно владеть ссылкой на объект. Счётчик ссылок объекта определяется как количество принадлежащих кому-либо ссылок на него. Владелец ссылки отвечает за вызов Py_DECREF(), когда ссылка больше не нужна. Владение ссылкой можно передать. Есть три способа распорядиться принадлежащей вам ссылкой: передать её дальше, сохранить или вызвать Py_DECREF(). Если не распорядиться принадлежащей вам ссылкой, возникнет утечка памяти.

Также можно заимствовать ссылку на объект [2]. Тот, кто заимствует ссылку, не должен вызывать Py_DECREF(). Заимствованная ссылка не должна храниться дольше, чем ссылка у владельца, у которого она была заимствована. Использование заимствованной ссылки после того, как владелец распорядился ею, может привести к использованию освобождённой памяти, поэтому этого следует полностью избегать [3].

Преимущество заимствования ссылки перед владением ею состоит в том, что не нужно заботиться об освобождении ссылки на всех возможных путях выполнения кода — иными словами, при досрочном выходе заимствованная ссылка не создаёт риска утечки. Недостаток заимствования по сравнению с владением заключается в том, что в некоторых неочевидных ситуациях даже в коде, который кажется корректным, заимствованная ссылка может быть использована после того, как владелец, у которого она была заимствована, уже распорядился ею.

Заимствованную ссылку можно преобразовать в принадлежащую вам ссылку, вызвав Py_INCREF(). Это не влияет на положение владельца, у которого ссылка была заимствована: создаётся новая принадлежащая вам ссылка, вместе с которой вы принимаете на себя все обязанности владельца (новый владелец должен правильно распорядиться ссылкой, как и прежний владелец).

1.10.2. Правила владения

При каждой передаче ссылки на объект в функцию или из неё спецификация интерфейса функции определяет, передаётся ли вместе со ссылкой владение.

Большинство функций, возвращающих ссылку на объект, передают вместе со ссылкой и владение. В частности, все функции, предназначенные для создания нового объекта, например PyLong_FromLong() и Py_BuildValue(), передают владение получателю. Даже если объект на самом деле не новый, вы всё равно получаете владение новой ссылкой на этот объект. Например, функция PyLong_FromLong() хранит кэш часто используемых значений и может вернуть ссылку на элемент из кэша.

Многие функции, извлекающие объекты из других объектов, также передают владение вместе со ссылкой, например PyObject_GetAttrString(). Однако здесь всё не так однозначно, поскольку некоторые распространённые функции являются исключениями: PyTuple_GetItem(), PyList_GetItem(), PyDict_GetItem() и PyDict_GetItemString() возвращают ссылки, заимствованные у кортежа, списка или словаря.

Функция PyImport_AddModule() также возвращает заимствованную ссылку, даже если фактически может создать возвращаемый объект: это возможно, поскольку принадлежащая кому-либо ссылка на объект хранится в sys.modules.

Когда вы передаёте ссылку на объект другой функции, обычно функция заимствует её у вас; если ей потребуется сохранить эту ссылку, она вызовет Py_INCREF(), чтобы стать независимым владельцем. Из этого правила есть ровно два важных исключения: PyTuple_SetItem() и PyList_SetItem(). Эти функции принимают на себя владение переданным им элементом — даже если завершаются неудачей! (Обратите внимание, что PyDict_SetItem() и аналогичные функции не принимают владение — они работают «обычным» образом.)

При вызове функции C из Python она заимствует ссылки на аргументы у вызывающего кода. Вызывающий код владеет ссылкой на объект, поэтому срок жизни заимствованной ссылки гарантирован до возврата из функции. Если такую заимствованную ссылку необходимо сохранить или передать дальше, её нужно преобразовать в принадлежащую функции ссылку вызовом Py_INCREF().

Ссылка на объект, возвращаемая функцией C, вызванной из Python, должна принадлежать функции — функция передаёт владение вызывающему коду.

1.10.3. Тонкий лёд

Есть несколько ситуаций, когда кажущееся безобидным использование заимствованной ссылки может привести к проблемам. Все они связаны с неявными вызовами интерпретатора, из-за которых владелец ссылки может распорядиться ею.

Первый и самый важный случай, о котором следует знать, — вызов Py_DECREF() для постороннего объекта при заимствовании ссылки на элемент списка. Например:

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    PyList_SetItem(list, 1, PyLong_FromLong(0L));
    PyObject_Print(item, stdout, 0); /* BUG! */
}

Сначала эта функция заимствует ссылку на list[0], затем заменяет list[1] значением 0 и наконец выводит заимствованную ссылку. Выглядит безобидно, не так ли? Но это не так!

Проследим ход выполнения до PyList_SetItem(). Список владеет ссылками на все свои элементы, поэтому при замене элемента 1 он должен распорядиться прежним элементом 1. Предположим, что прежний элемент 1 был экземпляром класса, определённого пользователем, и что в этом классе был определён метод __del__(). Если счётчик ссылок этого экземпляра класса равен 1, при его удалении будет вызван метод __del__().

Поскольку метод __del__() написан на Python, он может выполнять произвольный код Python. Может ли он сделать что-нибудь, чтобы сделать недействительной ссылку на item в bug()? Ещё бы! Если список, переданный в bug(), доступен методу __del__(), тот может выполнить оператор вида del list[0]. Если это была последняя ссылка на объект, память, связанная с ним, будет освобождена, что сделает item недействительной.

Если известен источник проблемы, решение простое: временно увеличьте счётчик ссылок. Правильная версия функции выглядит так:

void
no_bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    Py_INCREF(item);
    PyList_SetItem(list, 1, PyLong_FromLong(0L));
    PyObject_Print(item, stdout, 0);
    Py_DECREF(item);
}

Это реальная история. В более старой версии Python были варианты этой ошибки, и одному человеку пришлось потратить немало времени на отладку в отладчике C, чтобы понять, почему его методы __del__() не работают…

Вторая проблема с заимствованной ссылкой — вариант, связанный с потоками. Обычно несколько потоков в интерпретаторе Python не мешают друг другу, поскольку всё пространство объектов Python защищено глобальной блокировкой. Однако эту блокировку можно временно освободить с помощью макроса Py_BEGIN_ALLOW_THREADS, а затем снова захватить с помощью Py_END_ALLOW_THREADS. Это часто делается вокруг блокирующих вызовов ввода-вывода, чтобы другие потоки могли использовать процессор во время ожидания завершения ввода-вывода. Очевидно, что следующая функция подвержена той же проблеме, что и предыдущая:

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);
    Py_BEGIN_ALLOW_THREADS
    ...some blocking I/O call...
    Py_END_ALLOW_THREADS
    PyObject_Print(item, stdout, 0); /* BUG! */
}

1.10.4. Указатели NULL

Как правило, функции, принимающие ссылки на объекты в качестве аргументов, не предполагают, что вы передадите им указатели NULL, и завершатся аварийно (или вызовут аварийное завершение позднее), если вы так поступите. Функции, возвращающие ссылки на объекты, обычно возвращают NULL только в случае возникновения исключения. Аргументы NULL не проверяются, потому что функции часто передают полученные объекты другим функциям: если бы каждая функция проверяла наличие NULL, возникало бы множество избыточных проверок, а код работал бы медленнее.

Проверять NULL лучше только в «источнике» — там, где получен указатель, который может быть NULL, например из malloc() или функции, способной возбудить исключение.

Макросы Py_INCREF() и Py_DECREF() не проверяют указатели NULL, однако их варианты Py_XINCREF() и Py_XDECREF() выполняют такую проверку.

Макросы проверки типа объекта (Pytype_Check()) не проверяют указатели NULL. Кроме того, часто код вызывает несколько таких макросов подряд, чтобы проверить объект на соответствие разным ожидаемым типам, и проверки указателя приводили бы к избыточным проверкам. Вариантов с проверкой NULL не существует.

Механизм вызова функций C гарантирует, что список аргументов, передаваемый функциям C (args в примерах), никогда не будет NULL — более того, он всегда будет кортежем [4].

Серьёзной ошибкой будет допустить, чтобы указатель NULL «утёк» к пользователю Python.

1.11. Написание расширений на C++

Модули расширения можно писать на C++. При этом действуют некоторые ограничения. Если основная программа (интерпретатор Python) скомпилирована и скомпонована компилятором C, нельзя использовать глобальные или статические объекты с конструкторами. Если основная программа скомпонована компилятором C++, этой проблемы нет. Функции, вызываемые интерпретатором Python (в частности, функции инициализации модулей), должны быть объявлены с использованием extern "C". Заключать заголовочные файлы Python в extern "C" {...} не требуется: в них эта конструкция уже используется, если определён символ __cplusplus (все современные компиляторы C++ определяют этот символ).

1.12. Предоставление C API для модуля расширения

Многие модули расширения просто предоставляют новые функции и типы для использования в Python, но иногда код модуля расширения может быть полезен и другим модулям расширения. Например, модуль расширения может реализовать тип «коллекция», работающий подобно спискам, но без порядка элементов. Подобно тому, как стандартный тип Python list имеет C API, позволяющий модулям расширения создавать списки и управлять ими, этот новый тип коллекции должен иметь набор функций C для непосредственного управления им из других модулей расширения.

На первый взгляд это кажется простым: достаточно написать функции (разумеется, не объявляя их static), предоставить соответствующий заголовочный файл и задокументировать C API. И действительно, это бы сработало, если бы все модули расширения всегда статически компоновались с интерпретатором Python. Однако при использовании модулей в виде динамических библиотек символы, определённые в одном модуле, могут быть недоступны другому. Особенности видимости зависят от операционной системы: в некоторых системах для интерпретатора Python и всех модулей расширения используется одно глобальное пространство имён (например, в Windows), тогда как в других при компоновке модуля требуется явный список импортируемых символов (один из примеров — AIX), либо предлагается выбор между разными стратегиями (в большинстве Unix-подобных систем). И даже если символы глобально видимы, модуль, функции которого требуется вызвать, может быть ещё не загружен!

Следовательно, для переносимости нельзя делать никаких предположений о видимости символов. Это означает, что все символы в модулях расширения должны объявляться static, за исключением функции инициализации модуля, чтобы избежать конфликтов имён с другими модулями расширения (как обсуждалось в разделе Таблица методов модуля и функция инициализации). А это также означает, что символы, которые должны быть доступны из других модулей расширения, необходимо экспортировать другим способом.

Python предоставляет специальный механизм для передачи информации уровня C (указателей) из одного модуля расширения в другой: капсулы. Капсула — это тип данных Python, в котором хранится указатель (void*). Создавать капсулы и обращаться к ним можно только через их C API, но их можно передавать так же, как и любые другие объекты Python. В частности, их можно присваивать именам в пространстве имён модуля расширения. Затем другие модули расширения могут импортировать этот модуль, получать значение по этому имени, а после этого извлекать указатель из капсулы.

Существует много способов использовать капсулы для экспорта C API модуля расширения. Для каждой функции можно создать отдельную капсулу либо сохранить все указатели C API в массиве, адрес которого публикуется в капсуле. Кроме того, различные задачи по сохранению и получению указателей можно по-разному распределить между модулем, предоставляющим код, и клиентскими модулями.

Какой бы способ вы ни выбрали, важно правильно именовать капсулы. Функция PyCapsule_New() принимает параметр имени (const char*); допускается передать имя NULL, но мы настоятельно рекомендуем указывать имя. Правильно именованные капсулы обеспечивают определённую проверку типов во время выполнения: надёжного способа отличить одну безымянную капсулу от другой не существует.

В частности, капсулам, используемым для предоставления C API, следует давать имена в соответствии со следующим соглашением:

modulename.attributename

Вспомогательная функция PyCapsule_Import() упрощает загрузку C API, предоставленного через капсулу, но только если имя капсулы соответствует этому соглашению. Такое поведение даёт пользователям C API высокую степень уверенности в том, что загруженная ими капсула содержит нужный C API.

В следующем примере показан подход, при котором основная часть работы возлагается на автора экспортирующего модуля, что уместно для широко используемых библиотечных модулей. Все указатели C API (в примере он всего один!) сохраняются в массиве указателей void, который становится значением капсулы. Соответствующий модулю заголовочный файл предоставляет макрос, который выполняет импорт модуля и получение указателей C API; клиентским модулям достаточно вызвать этот макрос перед обращением к C API.

Экспортирующий модуль представляет собой модификацию модуля spam из раздела Простой пример. Функция spam.system() не вызывает напрямую библиотечную функцию C system(), а обращается к функции PySpam_System(), которая, разумеется, в реальном приложении выполняла бы что-нибудь более сложное (например, добавляла бы «spam» к каждой команде). Эта функция PySpam_System() также экспортируется для других модулей расширения.

Функция PySpam_System() — обычная функция C, объявленная static, как и всё остальное:

static int
PySpam_System(const char *command)
{
    return system(command);
}

Функция spam_system() изменяется самым простым образом:

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = PySpam_System(command);
    return PyLong_FromLong(sts);
}

В начале модуля, сразу после строки

#include <Python.h>

необходимо добавить ещё две строки:

#define SPAM_MODULE
#include "spammodule.h"

Макрос #define используется, чтобы сообщить заголовочному файлу, что он включается в экспортирующий модуль, а не в клиентский модуль. Наконец, функция инициализации модуля должна позаботиться об инициализации массива указателей C API:

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;
    static void *PySpam_API[PySpam_API_pointers];
    PyObject *c_api_object;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    /* Initialize the C API pointer array */
    PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;

    /* Create a Capsule containing the API pointer array's address */
    c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);

    if (PyModule_AddObject(m, "_C_API", c_api_object) < 0) {
        Py_XDECREF(c_api_object);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

Обратите внимание, что PySpam_API объявлена static; иначе массив указателей исчез бы при завершении PyInit_spam()!

Основная часть работы выполняется в заголовочном файле spammodule.h, который выглядит так:

#ifndef Py_SPAMMODULE_H
#define Py_SPAMMODULE_H
#ifdef __cplusplus
extern "C" {
#endif

/* Header file for spammodule */

/* C API functions */
#define PySpam_System_NUM 0
#define PySpam_System_RETURN int
#define PySpam_System_PROTO (const char *command)

/* Total number of C API pointers */
#define PySpam_API_pointers 1


#ifdef SPAM_MODULE
/* This section is used when compiling spammodule.c */

static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;

#else
/* This section is used in modules that use spammodule's API */

static void **PySpam_API;

#define PySpam_System \
 (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])

/* Return -1 on error, 0 on success.
 * PyCapsule_Import will set an exception if there's an error.
 */
static int
import_spam(void)
{
    PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);
    return (PySpam_API != NULL) ? 0 : -1;
}

#endif

#ifdef __cplusplus
}
#endif

#endif /* !defined(Py_SPAMMODULE_H) */

Чтобы получить доступ к функции PySpam_System(), клиентскому модулю достаточно вызвать функцию (точнее, макрос) import_spam() в функции инициализации:

PyMODINIT_FUNC
PyInit_client(void)
{
    PyObject *m;

    m = PyModule_Create(&clientmodule);
    if (m == NULL)
        return NULL;
    if (import_spam() < 0)
        return NULL;
    /* additional initialization can happen here */
    return m;
}

Главный недостаток этого подхода заключается в том, что файл spammodule.h довольно сложен. Однако основная структура одинакова для каждой экспортируемой функции, поэтому её достаточно изучить один раз.

В заключение следует отметить, что капсулы предоставляют дополнительные возможности, особенно полезные для выделения и освобождения памяти указателя, хранящегося в капсуле. Подробности описаны в справочном руководстве по API Python/C в разделе Капсулы, а также в реализации капсул (файлы Include/pycapsule.h и Objects/pycapsule.c в дистрибутиве исходного кода Python).

Сноски

[1]

Интерфейс для этой функции уже существует в стандартном модуле os — его выбрали в качестве простого и понятного примера.

[2]

Метафора «заимствования» ссылки не совсем точна: у владельца по-прежнему остаётся копия ссылки.

[3]

Проверка того, что счётчик ссылок не меньше 1, не работает — сам счётчик ссылок может находиться в освобождённой памяти, которая впоследствии может быть использована другим объектом!

[4]

Эти гарантии не действуют при использовании «старого» соглашения о вызовах, которое до сих пор встречается во многих существующих программах.

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/extending/extending.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API