Расширение Python на C: создание быстрых нативных модулей
Предварительные требования для написания расширений
Любой код, написанный на компилируемых языках (C, C++, Java), может быть интегрирован или импортирован в другой Python-скрипт. Такой код считается «расширением».
Модуль расширения Python — это обычная C-библиотека. На Unix-машинах такие библиотеки обычно заканчиваются на .so (shared object). На Windows — .dll (dynamically linked library).
Для начала написания расширения нужны заголовочные файлы Python.
На Unix-машинах обычно требуется установка пакета для разработчиков, например
python2.5-dev.Пользователи Windows получают эти заголовки как часть пакета при использовании бинарного установщика Python.
Кроме того, предполагается, что у вас есть хорошие знания C или C++ для написания расширений Python на C.
Первый взгляд на расширение Python
Для первого взгляда на модуль расширения Python нужно сгруппировать код в четыре части:
Заголовочный файл
Python.h.C-функции, которые вы хотите открыть как интерфейс из вашего модуля.
Таблица сопоставления имен функций (как их видят Python-разработчики) с C-функциями внутри модуля расширения.
Функция инициализации.
Заголовочный файл Python.h
Нужно включить Python.h в ваш C-файл, что дает доступ к внутреннему Python API для подключения модуля к интерпретатору.
Убедитесь, что Python.h включен перед любыми другими заголовками. После включений следуют функции, которые вы хотите вызывать из Python.
C-функции
Подписи C-реализации ваших функций всегда принимают одну из трех форм:
static PyObject *MyFunction(PyObject *self, PyObject *args);
static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *MyFunctionWithNoArgs(PyObject *self);
Каждое из объявлений возвращает объект Python. В Python нет функций void, как в C. Если функция не должна возвращать значение, верните C-эквивалент Python'овского None. Заголовочные файлы Python определяют макрос Py_RETURN_NONE для этого.
Имена ваших C-функций могут быть любыми, так как они никогда не видны вне модуля расширения. Они определяются как static функции.
Обычно C-функции называют, комбинируя имя Python-модуля и имя функции:
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Ваш код здесь */
Py_RETURN_NONE;
}
Это Python-функция func внутри модуля module. Указатели на ваши C-функции помещаются в таблицу методов модуля, которая обычно идет дальше в исходном коде.
Таблица сопоставления методов
Эта таблица — простой массив структур PyMethodDef. Структура выглядит так:
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
Описание членов:
ml_name — имя функции, представленное Python-интерпретатором при использовании в Python-программах.
ml_meth — должен быть адресом функции с одной из сигнатур, описанных выше.
ml_flags — говорит интерпретатору, какую из трех сигнатур использует
ml_meth.- Обычно значение
METH_VARARGS. - Может быть побитовым ИЛИ с
METH_KEYWORDSдля разрешения именованных аргументов. - Может быть
METH_NOARGS, если аргументы не нужны.
- Обычно значение
ml_doc — строка документации для функции, может быть
NULL.
Таблица должна заканчиваться записью-стражем (sentinel), состоящей из NULL и 0 для соответствующих членов.
Пример
Для вышеуказанной функции таблица методов:
static PyMethodDef module_methods[] = {
{"func", (PyCFunction)module_func, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
Функция инициализации
Последняя часть модуля расширения — функция инициализации. Она вызывается Python-интерпретатором при загрузке модуля. Функция должна называться initModule, где Module — имя модуля.
Функция инициализации должна быть экспортирована из библиотеки, которую вы будете строить. Заголовочные файлы Python определяют PyMODINIT_FUNC для включения соответствующих инструкций для конкретной среды компиляции. Нужно просто использовать её при определении функции.
Общая структура C-функции инициализации:
PyMODINIT_FUNC initModule() {
Py_InitModule3("func", module_methods, "docstring...");
}
Описание Py_InitModule3:
func — экспортируемая функция.
module_methods — имя таблицы сопоставления, определенной выше.
docstring — комментарий для расширения.
Собрав всё вместе:
#include
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Ваш код */
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{"func", (PyCFunction)module_func, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initModule() {
Py_InitModule3("func", module_methods, "docstring...");
}
Пример: простое расширение "helloworld"
#include
static PyObject* helloworld(PyObject* self) {
return Py_BuildValue("s", "Hello, Python extensions!!");
}
static char helloworld_docs[] = "helloworld(): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
{"helloworld", (PyCFunction)helloworld, METH_NOARGS, helloworld_docs},
{NULL}
};
void inithelloworld(void) {
Py_InitModule3("helloworld", helloworld_funcs, "Extension module example!");
}
Здесь функция Py_BuildValue используется для построения Python-значения. Сохраните код в hello.c. Далее увидим, как скомпилировать и установить модуль для вызова из Python-скрипта.
Сборка и установка расширений
Пакет distutils упрощает распространение Python-модулей (как чистых Python, так и расширений) стандартным способом. Модули распространяются в исходной форме и собираются/устанавливаются через скрипт настройки, обычно называемый setup.py.
Для вышеуказанного модуля нужен следующий setup.py:
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',
ext_modules=[Extension('helloworld', ['hello.c'])])
Теперь выполните команду, которая выполнит все необходимые шаги компиляции и линковки с правильным компилятором, линкером, командами и флагами, и скопирует полученную динамическую библиотеку в соответствующую директорию:
$ python setup.py install
На Unix-системах, скорее всего, нужно запускать эту команду от root для прав записи в директорию site-packages. На Windows это обычно не проблема.
Импорт расширений
После установки расширения вы сможете импортировать и вызывать его в Python-скрипте:
#!/usr/bin/python
import helloworld
print(helloworld.helloworld())
Результат:
Hello, Python extensions!!
Передача параметров функциям
Скорее всего, вы захотите определять функции, принимающие аргументы. Можно использовать другие сигнатуры для C-функций. Например, функция, принимающая некоторое количество параметров:
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Разбор args и интересные действия */
Py_RETURN_NONE;
}
Таблица методов с записью для новой функции:
static PyMethodDef module_methods[] = {
{"func", (PyCFunction)module_func, METH_NOARGS, NULL},
{"func", module_func, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
Для извлечения аргументов из единственного указателя PyObject, переданного в вашу C-функцию, можно использовать API-функцию PyArg_ParseTuple.
Первый аргумент PyArg_ParseTuple — это аргумент args. Это объект, который вы будете «разбирать». Второй аргумент — строка формата, описывающая аргументы так, как вы их ожидаете. Каждый аргумент представлен одним или несколькими символами в строке формата.
static PyObject *module_func(PyObject *self, PyObject *args) {
int i;
double d;
char *s;
if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
return NULL;
}
/* Интересные действия */
Py_RETURN_NONE;
}
Компиляция новой версии модуля и импорт позволяют вызывать новую функцию с любым количеством аргументов любого типа:
module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)
Функция PyArg_ParseTuple
Стандартная сигнатура:
int PyArg_ParseTuple(PyObject* tuple, char* format, ...)
Возвращает 0 при ошибках, значение ≠ 0 при успехе. tuple — это PyObject*, второй аргумент C-функции. format — C-строка, описывающая обязательные и опциональные аргументы.
Список кодов формата для PyArg_ParseTuple:
| Код | C-тип | Значение |
|---|---|---|
| c | char | Python-строка длины 1 становится C char. |
| d | double | Python float становится C double. |
| f | float | Python float становится C float. |
| i | int | Python int становится C int. |
| l | long | Python int становится C long. |
| L | long long | Python int становится C long long. |
| O | PyObject* | Получает не-NULL заимствованную ссылку на Python-аргумент. |
| s | char* | Python-строка без встроенных null в C char*. |
| s# | char*+int | Любая Python-строка в C адрес и длину. |
| t# | char*+int | Read-only single-segment buffer в C адрес и длину. |
| u | Py_UNICODE* | Python Unicode без встроенных null в C. |
| u# | Py_UNICODE*+int | Любой Python Unicode в C адрес и длину. |
| w# | char*+int | Read/write single-segment buffer в C адрес и длину. |
| z | char* | Как s, также принимает None (устанавливает C char* в NULL). |
| z# | char*+int | Как s#, также принимает None. |
| (...) | as per ... | Python-последовательность считается одним аргументом на элемент. |
| | | Следующие аргументы опциональны. | |
| : | Конец формата, за которым следует имя функции для сообщений об ошибках. | |
| ; | Конец формата, за которым следует полный текст сообщения об ошибке. |
Возврат значений
Py_BuildValue принимает строку формата, как PyArg_ParseTuple. Вместо адресов значений, которые вы строите, передаете сами значения. Пример реализации функции сложения:
static PyObject *foo_add(PyObject *self, PyObject *args) {
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("i", a + b);
}
На Python это выглядело бы так:
def add(a, b):
return (a + b)
Можно вернуть два значения из функции — в Python они будут захвачены списком:
static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("ii", a + b, a - b);
}
На Python:
def add_subtract(a, b):
return (a + b, a - b)
Функция Py_BuildValue
Стандартная сигнатура:
PyObject* Py_BuildValue(char* format, ...)
format — C-строка, описывающая Python-объект для построения. Последующие аргументы — C-значения, из которых строится результат. Результат PyObject* — новая ссылка.
Часто используемые коды строк:
| Код | C-тип | Значение |
|---|---|---|
| c | char | C char становится Python-строкой длины 1. |
| d | double | C double становится Python float. |
| f | float | C float становится Python float. |
| i | int | C int становится Python int. |
| l | long | C long становится Python int. |
| N | PyObject* | Передает Python-объект и крадет ссылку. |
| O | PyObject* | Передает Python-объект и INCREF как обычно. |
| O& | convert+void* | Произвольное преобразование. |
| s | char* | C 0-terminated char* в Python-строку, или NULL в None. |
| s# | char*+int | C char* и длина в Python-строку, или NULL в None. |
| u | Py_UNICODE* | C-wide null-terminated строка в Python Unicode, или NULL в None. |
| u# | Py_UNICODE*+int | C-wide строка и длина в Python Unicode, или NULL в None. |
| w# | char*+int | Read/write single-segment buffer в C адрес и длину. |
| z | char* | Как s, также принимает None. |
| z# | char*+int | Как s#, также принимает None. |
| (...) | as per ... | Строит Python-кортеж из C-значений. |
| [...] | as per ... | Строит Python-список из C-значений. |
| {...} | as per ... | Строит Python-словарь из C-значений, чередуя ключи и значения. |
Код {...} строит словари из четного числа C-значений, чередуя ключи и значения. Например, Py_BuildValue("{issi}", 23, "zig", "zag", 42) возвращает словарь как Python'овский {23: 'zig', 'zag': 42}.
Python
- Объектно-ориентированное программирование в Python: классы, наследование, инкапсуляция и полиморфизм
- Функция print() в Python: практическое руководство с примерами
- Мастерство метода strip() в Python: подробное руководство и практические примеры
- Мастерство метода str.count() в Python: как подсчитывать символы и подстроки с примерами
- Мастерство строк форматирования format() в Python: понятные примеры
- Мастерство метода String.find() в Python: синтаксис, примеры и альтернативы
- Освоение лямбда-функций Python: практические примеры и лучшие практики
- Функция round() в Python: подробное объяснение с практическими примерами
- Освоение функции map() в Python: синтаксис, примеры и лучшие практики
- Python timeit(): измерение времени выполнения с практическими примерами