Язык программирования Python

       

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


Если необходимость встроить 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)\n\ \n\ Update the md5 object with the string arg. Repeated calls are\n\ equivalent to a single call with the concatenation of all the\n\ 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\n\ ...");

static PyObject * md5_hexdigest(md5object *self) { // Реализация метода на C }

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

// Здесь было определение метода 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.


Содержание раздела