Опубликован: 22.12.2005 | Уровень: для всех | Доступ: платный
Лекция 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 >
Сергей Крупко
Сергей Крупко

Добрый день.

Я сейчас прохожу курс  повышения квалификации  - "Профессиональное веб-программирование". Мне нужно получить диплом по этому курсу. Я так полагаю нужно его оплатить чтобы получить диплом о повышении квалификации. Как мне оплатить этот курс?

 

Павел Ялганов
Павел Ялганов

Скажите экзамен тоже будет ввиде теста? или там будет какое то практическое интересное задание?

Максим Чиндясов
Максим Чиндясов
Россия, Нижний Новгород
Ольга Коваль
Ольга Коваль
Беларусь, Минск