Spec-Zone.ru › Python 3.13

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

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

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

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

Примечание

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

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

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

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

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

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

#define PY_SSIZE_T_CLEAN
#include <Python.h>

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

Примечание

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

#define PY_SSIZE_T_CLEAN использовалось для указания того, что Py_ssize_t следует использовать в некоторых API вместо int. Это не требуется начиная с Python 3.13, но мы сохраняем его здесь для обратной совместимости. См. Строки и буферы для описания этого макроса.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

static PyObject *SpamError;

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

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

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

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

    return m;
}

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

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

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

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

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

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

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

Возвращаясь к нашей примерной функции, теперь вы должны понимать это утверждение:

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

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

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

sts = system(command);

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

return PyLong_FromLong(sts);

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

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

Py_INCREF(Py_None);
return Py_None;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyStatus status;
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    /* 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 */
    status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
    if (PyStatus_Exception(status)) {
        goto exception;
    }

    /* Initialize the Python interpreter.  Required.
       If this step fails, it will be a fatal error. */
    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    PyConfig_Clear(&config);

    /* 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");
    }

    // ... use Python C API here ...

    return 0;

  exception:
     PyConfig_Clear(&config);
     Py_ExitStatusException(status);
}

Примечание

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

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

Примечание

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

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

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

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

spam spammodule.o

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

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

spam spammodule.o -lX11

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

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

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

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

static PyObject *my_callback = NULL;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#define PY_SSIZE_T_CLEAN
#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 * const *kwlist, ...);

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

Примечание

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

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

#define PY_SSIZE_T_CLEAN
#include <Python.h>

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

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

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

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

    Py_RETURN_NONE;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1.10.1. Счётчики ссылок в Python

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

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

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

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

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

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

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

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

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

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

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

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

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

1.10.3. Тонкий лед

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

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

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

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

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

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

Поскольку он написан на 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 нет.

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

Это серьезная ошибка, если указатель NULL «утекает» к пользователю Python.

1.11. Создание расширений на C++

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

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

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

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

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

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

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

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

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

modulename.attributename

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

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

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

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

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

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

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

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

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

#include <Python.h>

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

#define SPAM_MODULE
#include "spammodule.h"

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

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

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

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

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

    if (PyModule_Add(m, "_C_API", c_api_object) < 0) {
        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 довольно сложен. Однако базовая структура одинакова для каждой экспортируемой функции, поэтому её нужно изучить только один раз.

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

Примечания

[1]

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

[2]

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

[3]

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

[4]

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

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

Spec-Zone.ru

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