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

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

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

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

Примечание

Интерфейс расширения C специфичен для CPython, и модули расширения не работают в других реализациях Python. Во многих случаях можно избежать записи расширений C и сохранить переносимость на другие реализации. Например, если используется вызов функций библиотеки C или системных вызовов, вместо записи пользовательского кода C следует использовать модуль ctypes или библиотеку cffi. Эти модули позволяют писать 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() в API Python, проверяет типы аргументов и преобразует их в C значения. Он использует строку шаблона для определения требуемых типов аргументов, а также типов переменных C, в которых будет храниться преобразованный значения. Подробнее об этом позже.

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

1.2. Интермеццо: ошибки и исключения

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

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

Самый распространенный - 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() вызовы уже созданных объектов) при возвращении индикатора ошибки!

Выбор, какое исключение поднять, полностью зависит от вас. Существуют предварительно объявленные объекты C, соответствующие всем встроенным исключениям Python, например, PyExc_ZeroDivisionError, которые можно использовать напрямую. Конечно, вы должны выбирать исключения разумно — не использовать PyExc_TypeError, чтобы означать, что файл не может быть открыт (вероятно, это должен быть PyExc_IOError). Если со списком аргументов что-то не так, функция 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}        /* Страж */
};

Обратите внимание на третью запись (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",   /* имя модуля */
    spam_doc, /* документация модуля, может иметь значение NULL */
    -1,       /* размер состояния модуля для каждого интерпретатора, или -1, если
                 модуль сохраняет состояние в глобальных переменных. */
    SpamMethods
};

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

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

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

Когда программа Python впервые импортирует spam модуль, вызывается PyInit_spam(). (См. ниже комментарии о встраивании Python.) он вызывает PyModule_Create(), который возвращает объект модуля, и вставляет объекты встроенных функций в только что созданный модуль на основе таблицы (массива PyMethodDef структур), найденной в определении модуля. PyModule_Create() возвращает указатель на создаваемый объект модуля. Он может прерваться с неустранимой ошибкой для определенных ошибок или возвращает NULL, если модуль не может быть инициализирован удовлетворительно. Функция init должна возвращать объект модуля вызывающему, чтобы затем он был вставлен в 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);
    }

    /* Добавить встроенный модуль перед Py_Initialize */
    if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
        fprintf(stderr, "Error: could not extend in-built modules table\n");
        exit(1);
    }

    /* Передайть argv[0] интерпретатору Python */
    Py_SetProgramName(program);

    /* Инициализировать Python интерпретатор. Необходимое.
       Если этот шаг завершится неудачно, это будет фатальная ошибка. */
    Py_Initialize();

    /* При необходимости импортировать модуль; кроме того,
       импорт можно отложить до тех пор, пока его не будет
       импортирован встроенный сценарий. */
    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), где структура PyModureDef возвращенный от 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);         /* Добавить ссылку на новый обратный вызов */
        Py_XDECREF(my_callback);  /* Удалить предыдущий обратный вызов */
        my_callback = temp;       /* Запомнить новый обратный вызов */
        /* Шаблон для возврата "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;
...
/* Время для вызова обратного вызова */
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; /* Вернуть ошибку */
...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; /* Вернуть ошибку */
/* Здесь может быть использован результат */
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; /* Вернуть ошибку */
/* Здесь может быть использован результат */
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  /* Сделайте так, чтобы "s#" использовал Py_ssize_t, а не int. */
#include <Python.h>
int ok;
int i, j;
long k, l;
const char *s;
Py_ssize_t size;

ok = PyArg_ParseTuple(args, ""); /* Без аргументов */
    /* Вызов Python: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* Строка */
    /* Возможный вызов Python: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Две длинные и строка */
    /* Возможный вызов Python: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
    /* Пара целых чисел и строка, размер которой также возвращается */
    /* Возможный вызов Python: f((1, 2), 'three') */
{
    const char *file;
    const char *mode = "r";
    int bufsize = 0;
    ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
    /* Строка и, возможно, другая строка и целое число */
    /* Возможные вызовы Python:
       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);
    /* Прямоугольник и точка */
    /* Возможные вызовы Python:
       f(((0, 0), (400, 300)), (10, 10)) */
}
{
    Py_complex c;
    ok = PyArg_ParseTuple(args, "D:myfunction", &c);
    /* комплекс, также предоставляющий имя функции для ошибок */
    /* Возможный вызов Python: 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  /* Сделать "s#" используя Py_ssize_t, а не 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[] = {
    /* Приведение функции необходимо, поскольку PyCFunction значения
     * принимают только два параметра PyObject* и keywdarg_parrot() -
     * три.
     */
    {"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 или один кортеж, введя строку формата в скобках.

Примеры (слева от вызова, справа от результирующего 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()), а также интерфейсы конфигурации и возможность отключения детектора во время выполнения. Детектор цикла считается необязательным компонентом; хотя он включен по умолчанию, его можно отключить во время сборки с помощью опции --without-cycle-gc для configure скрипта на платформах Unix (включая Mac OS X). Если детектор цикла отключен таким образом, модуль gc будет недоступен.

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__().

Так как он записан в Python, метод __del__() может выполнять произвольный 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. Обычно это происходит при блокировке вызовов I/O, чтобы другие потоки могли использовать процессор в ожидании завершения I/O. Очевидно, что следующая функция имеет ту же проблему, что и предыдущая:

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, но иногда код в модуле расширения может быть полезным для других модулей расширения. Например, модуль расширения может реализовать тип «collection», который работает как списки без заказа. Так же, как стандартный тип списка Python имеет C API, который позволяет модулям расширения создавать списки и управлять ими, этот новый тип сбора должен иметь набор функций C для прямого управления от других модулей расширения.

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

Поэтому переносимость требует не делать никаких предположений относительно видимости символов. Это означает, что все символы в модулях расширения должны объявляться 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(), которая, конечно, делает что-то более сложное в реальности (например, добавление «спама» к каждой команде). Этот функциональный 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;

    /* Инициализировать массив указателей C API */
    PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;

    /* Создать капсулу, содержащую адрес массива указателей API */
    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

/* Заголовочный файл для spammodule */

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

/* Общее количество указателей C API */
#define PySpam_API_pointers 1


#ifdef SPAM_MODULE
/* Этот раздел используется при компиляции spammodule.c */

static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;

#else
/* Этот раздел используется в модулях, использующих API spammodule. */

static void **PySpam_API;

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

/* Возвращает -1 при ошибке, 0 при успехе.
 * PyCapsule_Import установит исключение в случае ошибки.
 */
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;
    /* здесь может произойти дополнительная инициализация */
    return m;
}

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

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

Сноски

[1]Интерфейс для этой функции уже существует в стандартном os модуле – он был выбран в качестве простого и понятного примера.
[2]Метафора «заимствование» ссылки не совсем верна: владелец по-прежнему имеет копию ссылки.
[3]Проверка, что ссылочное количество по крайней мере 1 не работает — сам счетчик ссылок может находиться в освобожденной памяти и, таким образом, может быть повторно использован для другого объекта!
[4]Эти гарантии не действуют, когда вы используете «старое» соглашение о вызовах — это все еще встречается во многих существующих кодах.