Роман Сузи - Язык программирования Python
Под протоколом здесь понимается набор методов, которые должен поддерживать тот или иной класс для организации операций со своими экземплярами. Эти методы доступны не только из Python (например, len(a) дает длину последовательности), но и из кода на C (PySequence_Length()).
Написание модуля расширения
Если необходимость встроить 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 aren
equivalent to a single call with the concatenation of all then
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() -> stringn ...");
static PyObject * md5_hexdigest(md5object *self) {
// Реализация метода на C
}
PyDoc_STRVAR(hexdigest_doc, "hexdigest() -> stringn...");
// Здесь было определение метода 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.
Пример встраивания интерпретатора в программу на C
Интерпретатор Python может быть встроен в программу на C с использованием C API. Это лучше всего демонстрирует уже работающий пример: