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

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

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

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

Примечание

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

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

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

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

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

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

#define PY_SSIZE_T_CLEAN
#include <Python.h>

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

Примечание

Поскольку 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() напрямую.

Следующее, что мы добавляем в наш файл модуля, это функция языка Си, которая будет вызываться, когда выражение 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") в аргументы, передаваемые в функцию Си. Функция Си всегда имеет два аргумента, условно названные self и args.

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

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

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

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

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

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(). Единственный случай, когда код на языке Си должен вызывать 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 станет висячим указателем. Если он станет висячим указателем, код на языке Си, который вызывает исключение, может вызвать дамп ядра или другие непредвиденные побочные эффекты.

Мы обсудим использование 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 даже целые числа являются объектами на куче!)

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

Py_INCREF(Py_None);
return Py_None;

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

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

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

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

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

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

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

Таблица методов должна быть указана в структуре определения модуля:

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

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

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

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

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

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

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

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

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

    ...

    PyMem_RawFree(program);
    return 0;
}

Примечание

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

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

Примечание

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

1.5. Компиляция и связывание

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

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

spam spammodule.o

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

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

spam spammodule.o -lX11

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

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

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

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

static PyObject *my_callback = NULL;

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

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

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

Макросы Py_XINCREF() и Py_XDECREF() увеличивают/уменьшают счетчик ссылок на объект и безопасны при наличии указателей NULL (но обратите внимание, что temp не будет NULL в этом контексте). Более подробная информация о них в разделе Контрольные подсчеты.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Некоторые примеры вызовов:

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

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

1.8. Параметры ключевых слов для функций расширения

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

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

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

Примечание

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

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

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

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

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

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

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

    Py_RETURN_NONE;
}

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

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

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

1.9. Построение произвольных значений

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

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

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

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

Примеры (слева - вызов, справа - результирующее значение 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++, программист отвечает за динамическое выделение и деаллокацию памяти на куче. В языке Си это делается с помощью функций malloc() и free(). В C++ операторы new и delete используются по сути с тем же значением, и мы ограничимся рассмотрением случая C.

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

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

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

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

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

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

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

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

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

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

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 не существует.

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

Это грубая ошибка - когда-либо позволять указателю NULL «убежать» к пользователю Python.

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

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

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

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

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

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

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

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

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

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

modulename.attributename

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

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

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

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

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

Функция spam_system() модифицируется тривиальным образом:

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

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

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

#include <Python.h>

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

#define SPAM_MODULE
#include "spammodule.h"

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

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

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

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

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

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

    return m;
}

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

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

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

/* Header file for spammodule */

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

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


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

static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;

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

static void **PySpam_API;

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

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

#endif

#ifdef __cplusplus
}
#endif

#endif /* !defined(Py_SPAMMODULE_H) */

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

PyMODINIT_FUNC
PyInit_client(void)
{
    PyObject *m;

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

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

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

Сноски

1

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

2

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

3

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

4

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

Вернуться на верх