Документация Python неофициальный перевод
Содержание страницы

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

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

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

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

Простой примерA Simple Example

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

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

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

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

#include <Python.h>

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

Предупреждение

Поскольку Python может определять некоторые макросы препроцессора, влияющие на стандартные заголовки в некоторых системах, необходимо обязательно включать 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 Py_BuildValue("i", sts);
}

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

Аргумент self используется только в том случае, когда C-функция реализует встроенный метод, а не функцию. В примере self всегда будет указателем NULL, поскольку мы определяем функцию, а не метод. (Это сделано для того, чтобы интерпретатору не приходилось разбираться с двумя разными типами C-функций.)

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

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

Интермеццо: ошибки и исключенияIntermezzo: Errors and Exceptions

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

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 для уже созданных объектов), когда возвращаете признак ошибки!

Выбор, какое исключение возбуждать, полностью за вами. Существуют предварительно объявленные 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_INCREF(SpamError);
    PyModule_AddObject(m, "error", SpamError);
    return m;
}

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

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

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

Возвращаясь к примеруBack to the Example

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

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. Это делается с помощью функции Py_BuildValue, которая является чем-то вроде обратной к PyArg_ParseTuple: она принимает строку формата и произвольное количество C-значений и возвращает новый объект Python. Подробнее о Py_BuildValue будет рассказано позже.

return Py_BuildValue("i", sts);

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

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

Py_INCREF(Py_None);
return Py_None;

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

Таблица методов модуля и функция инициализацииThe Module’s Method Table and Initialization Function

Я обещал показать, как 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 *, объявляет все специальные объявления компоновки, необходимые для платформы, и для 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[])
{
    /* Добавить встроенный модуль перед Py_Initialize */
    PyImport_AppendInittab("spam", PyInit_spam);

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

    /* Инициализировать интерпретатор Python. Обязательно. */
    Py_Initialize();

    /* Опционально импортировать модуль; в качестве альтернативы,
       импорт может быть отложен до тех пор, пока встроенный скрипт
       не импортирует его. */
    PyImport_ImportModule("spam");

Пример можно найти в файле Demo/embed/demo.c в дистрибутиве исходного кода Python.

Примечание

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

Более существенный пример модуля включён в дистрибутив исходного кода Python как Modules/xxmodule.c. Этот файл может использоваться как шаблон или просто прочитан в качестве примера. Скрипт modulator.py, входящий в дистрибутив исходного кода или установку Windows, предоставляет простой графический интерфейс для объявления функций и объектов, которые должен реализовать модуль, и может сгенерировать шаблон для заполнения. Скрипт находится в каталоге Tools/modulator/; см. файл README там для получения дополнительной информации.

Компиляция и компоновкаCompilation and Linkage

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

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

spam spammodule.o

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

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

spam spammodule.o -lX11

Вызов функций Python из CCalling Python Functions from 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-функция PyEval_CallObject. У этой функции два аргумента, оба – указатели на произвольные объекты Python: сама функция Python и список аргументов. Список аргументов всегда должен быть объектом-кортежем, длина которого равна числу аргументов. Чтобы вызвать функцию Python без аргументов, передайте NULL или пустой кортеж; чтобы вызвать её с одним аргументом, передайте кортеж из одного элемента. Py_BuildValue возвращает кортеж, если его строка формата состоит из нуля или более кодов формата в круглых скобках. Например:

int arg;
PyObject *arglist;
PyObject *result;
...
arg = 123;
...
/* Вызов колбэка */
arglist = Py_BuildValue("(i)", arg);
result = PyEval_CallObject(my_callback, arglist);
Py_DECREF(arglist);

PyEval_CallObject возвращает указатель на объект Python: это возвращаемое значение функции Python. PyEval_CallObject является «нейтральным к счетчику ссылок» по отношению к своим аргументам. В примере был создан новый кортеж для списка аргументов, который освобождается Py_DECREF сразу после вызова.

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

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

if (result == NULL)
    return NULL; /* Передать ошибку обратно */
...use result...
Py_DECREF(result);

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

PyObject *arglist;
...
arglist = Py_BuildValue("(l)", eventcode);
result = PyEval_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
    return NULL; /* Передать ошибку обратно */
/* Здесь, возможно, используется результат */
Py_DECREF(result);

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

Вы также можете вызвать функцию с именованными аргументами, используя PyEval_CallObjectWithKeywords. Как в примере выше, мы используем Py_BuildValue для построения словаря.

PyObject *dict;
...
dict = Py_BuildValue("{s:i}", "name", val);
result = PyEval_CallObjectWithKeywords(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
    return NULL; /* Передать ошибку обратно */
/* Здесь, возможно, используется результат */
Py_DECREF(result);

Извлечение параметров в функциях расширенияExtracting Parameters in Extension Functions

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

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

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

Обратите внимание, что хотя 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);
    /* Пара int и строка, размер которой также возвращается */
    /* Возможный вызов 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) */
}

Именованные параметры для функций-расширенийKeyword Parameters for Extension Functions

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

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

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

Примечание

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

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

#include "Python.h"

static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
{
    int voltage;
    char *state = "a stiff";
    char *action = "voom";
    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_INCREF(Py_None);

    return Py_None;
}

static PyMethodDef keywdarg_methods[] = {
    /* Приведение функции необходимо, поскольку значения PyCFunction
     * принимают только два параметра PyObject*, а keywdarg_parrot() принимает
     * три.
     */
    {"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
     "Print a lovely skit to standard output."},
    {NULL, NULL, 0, NULL}   /* сторожевое значение */
};
void
initkeywdarg(void)
{
  /* Создать модуль и добавить функции */
  Py_InitModule("keywdarg", keywdarg_methods);
}

Построение произвольных значенийBuilding Arbitrary Values

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

PyObject *Py_BuildValue(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))

Счетчики ссылокReference Counts

В таких языках, как 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 использует традиционную реализацию подсчёта ссылок, он также предлагает детектор циклов, который обнаруживает циклические ссылки. Это позволяет приложениям не беспокоиться о создании прямых или косвенных циклических ссылок; они являются слабым местом сборки мусора, реализованной только с помощью подсчёта ссылок. Циклические ссылки состоят из объектов, которые содержат (возможно, косвенные) ссылки на самих себя, так что каждый объект в цикле имеет ненулевой счётчик ссылок. Типичные реализации подсчёта ссылок не могут освободить память, принадлежащую объектам в циклической ссылке или на которые есть ссылки из объектов в цикле, даже если на сам цикл больше нет ссылок.

Детектор циклов способен обнаруживать мусорные циклы и может освобождать их при условии, что в Python не реализованы финализаторы (методы __del__()). Если такие финализаторы есть, детектор предоставляет доступ к циклам через модуль gc (в частности, переменную garbage в этом модуле). Модуль gc также предоставляет способ запуска детектора (функция collect()), а также интерфейсы конфигурации и возможность отключения детектора во время выполнения. Детектор циклов считается необязательным компонентом; хотя он включён по умолчанию, его можно отключить на этапе сборки с помощью опции --without-cycle-gc скрипта configure на платформах Unix (включая Mac OS X). Если детектор циклов отключён таким образом, модуль gc будет недоступен.

Подсчёт ссылок в PythonReference Counting in 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. Это не влияет на статус владельца, у которого была заимствована ссылка – создается новая принадлежащая ссылка, и на нового владельца возлагаются все обязанности владельца (новый владелец должен правильно избавиться от ссылки, равно как и предыдущий владелец).

Правила владенияOwnership Rules

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

Большинство функций, возвращающих ссылку на объект, передают владение вместе со ссылкой. В частности, все функции, предназначение которых – создавать новый объект, такие как 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 и подобные не принимают владение – они «нормальные».)

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

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

Тонкий лёдThin Ice

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

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

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

    PyList_SetItem(list, 1, PyLong_FromLong(0L));
    PyObject_Print(item, stdout, 0); /* ОШИБКА! */
}

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

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); /* ОШИБКА! */
}

Нулевые указателиNULL Pointers

В общем, функции, принимающие ссылки на объекты в качестве аргументов, не ожидают, что им будут передавать указатели 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.

Написание расширений на C++Writing Extensions in C++

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

Предоставление C API для модуля расширенияProviding a C API for an Extension Module

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

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

Таким образом, переносимость требует не делать никаких предположений о видимости символов. Это означает, что все символы в модулях расширения должны быть объявлены как static, за исключением функции инициализации модуля, чтобы избежать конфликтов имён с другими модулями расширения (как обсуждалось в разделе The Module’s Method Table and Initialization Function). И это означает, что символы, которые должны быть доступны из других модулей расширения, должны экспортироваться иным способом.

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

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

Следующий пример демонстрирует подход, который возлагает основную нагрузку на разработчика экспортирующего модуля, что уместно для часто используемых библиотечных модулей. Он хранит все указатели C API (в примере только один!) в массиве указателей void, который становится значением CObject. Заголовочный файл, соответствующий модулю, предоставляет макрос, который берёт на себя импорт модуля и получение его указателей 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 Py_BuildValue("i", 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;

    /* Создать CObject, содержащий адрес массива указателей API */
    c_api_object = PyCObject_FromVoidPtr((void *)PySpam_API, NULL);

    if (c_api_object != NULL)
        PyModule_AddObject(m, "_C_API", c_api_object);
    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 */
#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 при успехе. */
static int
import_spam(void)
{
    PyObject *module = PyImport_ImportModule("spam");

    if (module != NULL) {
        PyObject *c_api_object = PyObject_GetAttrString(module, "_C_API");
        if (c_api_object == NULL)
            return -1;
        if (PyCObject_Check(c_api_object))
            PySpam_API = (void **)PyCObject_AsVoidPtr(c_api_object);
        Py_DECREF(c_api_object);
    }
    return 0;
}

#endif

#ifdef __cplusplus
}
#endif

#endif /* !defined(Py_SPAMMODULE_H) */

Всё, что должен сделать клиентский модуль для получения доступа к функции PySpam_System – это вызвать функцию (вернее, макрос) import_spam в своей функции инициализации:

PyMODINIT_FUNC
initclient(void)
{
    PyObject *m;

    m = Py_InitModule("client", ClientMethods);
    if (m == NULL)
        return;
    if (import_spam() < 0)
        return;
    /* дополнительная инициализация может быть выполнена здесь */
}

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

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

Сноски

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