Spec-Zone.ru › Python 3.11

Расширение 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 перед включением любых стандартных заголовков.

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

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

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

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

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

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

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

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

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

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

Важным соглашением в интерпретаторе Python является следующее: при сбое функции она должна установить состояние исключения и вернуть значение ошибки (обычно -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);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

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

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

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

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

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

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

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

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

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

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

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

sts = system(command);

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

return PyLong_FromLong(sts);

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

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

Py_INCREF(Py_None);
return Py_None;

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

END_OF_DOCUMENT_MARKER

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ...

    PyMem_RawFree(program);
    return 0;
}

Примечание

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

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

Примечание

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

1.5. Компиляция и связь

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

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

spam spammodule.o

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

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

spam spammodule.o -lX11

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

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

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

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

static PyObject *my_callback = NULL;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

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

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

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

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

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

    Py_RETURN_NONE;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1.10.3. Тонкий лёд

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

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

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

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

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

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

Так как он написан на 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_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 довольно сложен. Однако базовая структура одинакова для каждой экспортируемой функции, поэтому её нужно изучить только один раз.

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

Примечания

1

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

2

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

3

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

4

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

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

Spec-Zone.ru

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