Написание модуля расширения

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

Привязка к Python, хотя и может быть несколько автоматизирована, все же это процесс творческий. Дело в том, что если предполагается интенсивно использовать библиотеку в Python, ее привязку желательно сделать как можно более тщательно. Возможно, в ходе привязки будет сделана объектно–ориентированная надстройка или другие архитектурные изменения, которые позволят упростить использование библиотеки.

В качестве примера можно привести выдержку из исходного кода модуля md5, который реализует функцию для получения md5–дайджеста. Модуль приводится в целях иллюстрации (то есть, с сокращениями). Модуль вводит собственный тип данных, MD5Type, поэтому можно увидеть не только реализацию функций, но и способ описания встроенного типа. В рамках этого курса не изучить все тонкости программирования модулей расширения, главное понять дух этого занятия. На комментарии автора курса лекций указывает двойной слэш //:

// заголовочные файлы

#include "Python.h"

#include "md5.h"

// В частности, в заголовочном файле md5.h есть следующие определения:

// typedef unsigned char *POINTER;

// typedef unsigned int UINT4;

// typedef struct {

// UINT4 state[4]; /* state (ABCD) */

// UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */

// unsigned char buffer[64]; /* input buffer */

// } MD5_CTX;

// Структура объекта MD5type

typedef struct {

 PyObject_HEAD

 MD5_CTX md5; /* the context holder */

} md5object;

// Определение типа объекта MD5type

static PyTypeObject MD5type;

// Макрос проверки типа MD5type

#define is_md5object(v) ((v)->ob_type == &MD5type)

// Порождение объекта типа MD5type

static md5object * newmd5object(void) {

 md5object *md5p;

 md5p = PyObject_New(md5object, &MD5type);

 if (md5p == NULL)

  return NULL; // не хватило памяти

 MD5Init(&md5p->md5); // инициализация

 return md5p;

}

// Определения методов

// Освобождение памяти из–под объекта

static void md5_dealloc(md5object *md5p) { PyObject_Del(md5p); }

static PyObject * md5_update(md5object *self, PyObject *args) {

 unsigned char *cp;

 int len;

 // разбор строки аргументов. Формат указывает следующее:

 // s# - один параметр, строка (заданная указателем и длиной)

 // : - разделитель

 // update — название метода

 if (!PyArg_ParseTuple(args, "s#:update", &cp, &len))

  return NULL;

 MD5Update(&self->md5, cp, len);

 // Даже возврат None требует увеличения счетчика ссылок

 Py_INCREF(Py_None);

 return Py_None;

}

// Строка документации метода update

PyDoc_STRVAR(update_doc,

"update (arg)

Update the md5 object with the string arg. Repeated calls are

equivalent to a single call with the concatenation of all the

arguments.");

// Метод digest

static PyObject * md5_digest(md5object *self) {

 MD5_CTX mdContext;

 unsigned char aDigest[16];

 /* make a temporary copy, and perform the final */

 mdContext = self->md5;

 MD5Final(aDigest, &mdContext);

 // результат возвращается в виде строки

 return PyString_FromStringAndSize((char *)aDigest, 16);

}

// и строка документации

PyDoc_STRVAR(digest_doc, "digest() -> string ...");

static PyObject * md5_hexdigest(md5object *self) {

 // Реализация метода на C

}

PyDoc_STRVAR(hexdigest_doc, "hexdigest() -> string ...");

// Здесь было определение метода copy()

// Методы объекта в сборе.

// Для каждого метода указывается название, имя метода на C

// (с приведением к типу PyCFunction), способ передачи аргументов:

// METH_VARARGS (переменное кол–во) или METH_NOARGS (нет аргументов)

// В конце массива — метка окончания спиcка аргументов.

static PyMethodDef md5_methods[] = {

 {"update", (PyCFunction)md5_update, METH_VARARGS, update_doc},

 {"digest", (PyCFunction)md5_digest, METH_NOARGS, digest_doc},

 {"hexdigest", (PyCFunction)md5_hexdigest, METH_NOARGS, hexdigest_doc},

 {"copy", (PyCFunction)md5_copy, METH_NOARGS, copy_doc},

 {NULL, NULL} /* sentinel */

};

// Атрибуты md5–объекта обслуживает эта функция, реализуя метод

// getattr.

static PyObject * md5_getattr(md5object *self, char *name) {

 // атрибут–данное digest_size

 if (strcmp(name, "digest_size") == 0) {

  return PyInt_FromLong(16);

 }

 // поиск атрибута–метода ведется в списке

 return Py_FindMethod(md5_methods, (PyObject *)self, name);

}

// Строка документации к модулю md5

PyDoc_STRVAR(module_doc, "This module implements ...");

// Строка документации к классу md5

PyDoc_STRVAR(md5type_doc, "An md5 represents the object...");

// Структура для объекта MD5type с описаниями для интерпретатора

static PyTypeObject MD5type = {

 PyObject_HEAD_INIT(NULL)

 0, /*ob_size*/

 "md5.md5", /*tp_name*/

 sizeof(md5object), /*tp_size*/

 0, /*tp_itemsize*/

 /* methods */

 (destructor)md5_dealloc, /*tp_dealloc*/

 0, /*tp_print*/

 (getattrfunc)md5_getattr, /*tp_getattr*/

 0, /*tp_setattr*/

 0, /*tp_compare*/

 0, /*tp_repr*/

 0, /*tp_as_number*/

 0, /*tp_as_sequence*/

 0, /*tp_as_mapping*/

 0, /*tp_hash*/

 0, /*tp_call*/

 0, /*tp_str*/

 0, /*tp_getattro*/

 0, /*tp_setattro*/

 0, /*tp_as_buffer*/

 0, /*tp_xxx4*/

 md5type_doc, /*tp_doc*/

};

// Функции модуля md5:

// Функция new() для получения нового объекта типа md5type

static PyObject * MD5_new(PyObject *self, PyObject *args) {

 md5object *md5p;

 unsigned char *cp = NULL;

 int len = 0;

 // Разбор параметров. Здесь вертикальная черта

 // в строке формата означает окончание

 // списка обязательных параметров.

 // Остальное — как и выше: s# - строка, после : — имя

 if (!PyArg_ParseTuple(args, "|s#:new", &cp, &len))

  return NULL;

 if ((md5p = newmd5object()) == NULL)

  return NULL;

 // Если был задан параметр cp:

  if (cp)

   MD5Update(&md5p->md5, cp, len);

 return (PyObject *)md5p;

}

// Строка документации для new()

PyDoc_STRVAR(new_doc, "new([arg]) -> md5 object ...");

// Список функций, которые данный модуль экспортирует

static PyMethodDef md5_functions[] = {

 {"new", (PyCFunction)MD5_new, METH_VARARGS, new_doc},

 {"md5", (PyCFunction)MD5_new, METH_VARARGS, new_doc},

 {NULL, NULL} /* Sentinel */

};

// Следует заметить, что md5 — то же самое, что new. Эта функция оставлена для

// обратной совместимости со старым модулем md5

// Инициализация модуля

PyMODINIT_FUNC initmd5(void) {

 PyObject *m, *d;

 MD5type.ob_type = &PyType_Type;

 // Инициализируется модуль

 m = Py_InitModule3("md5", md5_functions, module_doc);

 // Получается словарь с именами модуля

 d = PyModule_GetDict(m);

 // Добавляется атрибут MD5Type (тип md5–объекта) к словарю

 PyDict_SetItemString(d, "MD5Type", (PyObject *)&MD5type);

 // Добавляется целая константа digest_size к модулю

 PyModule_AddIntConstant(m, "digest_size", 16);

}

На основе этого примера можно строить собственные модули расширения, ознакомившись с документацией по C/API и документом «Extending and Embedding» («Расширение и встраивание») из стандартной поставки Python. Перед тем, как приступать к созданию своего модуля, следует убедиться, что это целесообразно: подходящего модуля еще не создано и реализация в виде чистого Python неэффективна. Если создан действительно полезный модуль, его можно предложить для включения в поставку Python. Для этого нужно просто связаться с кем–нибудь из разработчиков по электронной почте или предложить модуль в виде «патча» через http://sourceforge.net.

Больше книг — больше знаний!

Заберите 30% скидку новым пользователям на все книги Литрес с нашим промокодом

ПОЛУЧИТЬ СКИДКУ