Опубликован: 22.12.2005 | Доступ: свободный | Студентов: 17754 / 610 | Оценка: 4.18 / 3.71 | Длительность: 16:16:00
ISBN: 978-5-9556-0109-0
Лекция 13:

Интеграция Python с другими языками программирования

< Лекция 12 || Лекция 13: 12345 || Лекция 14 >

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

Если необходимость встроить 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.

< Лекция 12 || Лекция 13: 12345 || Лекция 14 >
Денис Хохлов
Денис Хохлов

Будет ли адаптация лекций под Python 3?

Арсений Бердюгин
Арсений Бердюгин

скачал с оффсайта последнюю версию python под windows. запускаю примеры из лекции и оно ругается на синтаксис.

Синтаксис примеров в лекции не актуален?
 

Марина Дайнеко
Марина Дайнеко
Россия, Moscow, Nope, 2008
Иван Сельченков
Иван Сельченков
Россия