Следующий наш пример является модулем расширения C, который интегрирует функции getenv и putenv из стандартной библиотеки C, предназначенные для работы с переменными окружения, для использования в сценариях Python. В примере 20.8 представлен файл на языке C, достигающий поставленной цели за счет создания связующего слоя вручную.
Пример 20.8. PP4E\Integrate\Extend\Cenviron\cenviron.c
/*************************************************************************
* Модуль расширения на C для Python с именем "cenviron". Обертывает
* библиотечные функции getenv/putenv для использования в программах Python.
**************************************************************************/ #include <Python.h>
#include <stdlib.h>
#include <string.h> /***********************/ /* 1) функции модуля */ /***********************/
static PyObject * /* возвращаемый объект */
wrap_getenv(PyObject *self, PyObject *args) /* self не используется */
{ /* args — из python */
char *varName, *varValue;
PyObject *returnObj = NULL; /* пи11=исключение */
if (PyArg_Parse(args, "(s)", &varName)) { /* Python -> C */
varValue = getenv(varName); /* вызов getenv из библ. C */
if (varValue != NULL)
returnObj = Py_BuildValue("s", varValue); /* C -> Python */ else
PyErr_SetString(PyExc_SystemError, "Error calling getenv");
}
return returnObj;
}
static PyObject *
wrap_putenv(PyObject *self, PyObject *args) {
char *varName, *varValue, *varAssign;
PyObject *returnObj = NULL;
if (PyArg_Parse(args, "(ss)", &varName, &varValue))
{
varAssign = malloc(strlen(varName) + strlen(varValue) + 2); sprintf(varAssign, "%s=%s", varName, varValue);
if (putenv(varAssign) == 0) {
Py_INCREF(Py_None); /* успешный вызов C */
returnObj = Py_None; /* ссылка на None */
} else
PyErr_SetString(PyExc_SystemError, "Error calling putenv");
} return returnObj;
} /**************************/ /* 2) таблица регистрации */ /**************************/
static PyMethodDef cenviron_methods[] = {
{"getenv", wrap_getenv, METH_VARARGS, "getenv doc"}, {"putenv", wrap_putenv, METH_VARARGS, "putenv doc"}, {NULL, NULL, 0, NULL}
};
/*************************/ /* 3) определение модуля */ /*************************/
static struct PyModuleDef cenvironmodule = {
PyModuleDef_HEAD_INIT,
"cenviron", /* имя модуля */
"cenviron doc", /* описание модуля, может быть NULL */
-1, /* размер структуры для каждого экземпляра, -1=глоб. перем. */ cenviron_methods /* ссылка на таблицу методов */
};
/***************************/
/* 4) инициализация модуля */
/***************************/
PyMODINIT_FUNC
PyInit_cenviron() /* вызывается первой инструкцией импорта */
{ /* имя имеет значение при динамической загрузке */
return PyModule_Create(&cenvironmodule);
}
Хотя этот пример достаточно представителен, тем не менее, сейчас он менее полезен, чем когда он был включен в первое издание этой книги, — как мы узнали во второй части, можно не только получать значения переменных окружения из таблицы os.environ, но и автоматически вызывать функцию C putenv, присваивая значения ключам в этой таблице, чтобы экспортировать новые значения в слой программного кода C. То есть обращение os.environ[‘key’] загрузит значение переменной окружения ‘key’, а операция os.environ[‘key’]=value присвоит значение переменной окружения как в Python, так и в C.
Второе действие — присвоение в C — было добавлено в Python после выхода первого издания книги. Кроме дополнительного показа приемов программирования расширений этот пример все же служит практической цели: даже сегодня изменения переменных окружения в программном коде на C, связанном с процессом Python, не видны при обращении к os.environ в программном коде Python. То есть после запуска программы таблица os.environ отражает только последующие изменения, сделанные в программном коде Python.
Кроме того, несмотря на то, что в настоящее время в модуле os Python имеются обе функции, putenv и getenv, их интеграция выглядит неполной. Изменения в os.environ приводят к вызову os.putenv, но прямой вызов os.putenv не изменяет содержимое os.environ, поэтому значения в os. environ могут не соответствовать действительности. А функция os.getenv в настоящее время просто обращается к os.environ и поэтому может не замечать изменения в окружении, выполненные уже после запуска процесса, за пределами программного кода Python. Такое положение вещей может изредка приводить к проблемам, но все равно этот модуль расширения на C нельзя назвать полностью бесполезным. Для правильного взаимодействия переменных окружения со встроенным программным кодом C необходимо вызывать функции или библиотеки языка C непосредственно (по крайней мере, пока Python опять не изменит эту модель!).
Файл cenviron.c, представленный в примере 20.8, создает модуль Python с именем cenviron, который идет немного дальше, чем предыдущий пример, — он экспортирует две функции, явно определяет описания некоторых исключений и обращается к счетчику ссылок объекта Python None (он не создается заново, поэтому необходимо добавить ссылку перед отправкой его в Python). Как и раньше, чтобы добавить этот модуль в Python, следует откомпилировать и скомпоновать этот программный код в объектный файл. В примере 20.9 представлен make-файл для Cygwin, который компилирует исходный программный код на языке C в модуль, готовый для динамического связывания при импортировании.
Пример 20.9. PP4E\Integrate\Extend\Cenviron\makefile.cenviron
#################################################################### # Компилирует cenviron.c в cenviron.dll — разделяемый объектный файл # в Cygwin, динамически загружаемый первой инструкцией импорта.
####################################################################
PYLIB = /usr/local/bin
PYINC = /usr/local/include/python3.1
gcc cenviron.c -g -I$(PYINC) -shared -L$(PYLIB) -lpython3.1 -o $@ clean:
rm -f *.pyc cenviron.dll
Для сборки введите в оболочке команду make -f makefile.cenviron. Перед запуском сценария убедитесь, что файл .dll находится в каталоге, включенном в путь поиска Python (текущий рабочий каталог тоже годится):
…/PP4E/Integrate/Extend/Cenviron$ python
> >> import cenviron
> >> cenviron.getenv(‘USER’) # подобно os.environ[key], но загружает заново ‘mark’
> >> cenviron.putenv(‘USER’, ‘gilligan’) # подобно os.environ[key]=value
> >> cenviron.getenv(‘USER‘) # программный код на C тоже видит изменения ‘gilligan‘
Как и прежде, cenviron является настоящим объектом модуля Python после импорта со всей информацией, обычно прикрепляемой к модулям, и в случае ошибок корректно возбуждает исключения и передает описание ошибок:
> >> dir(cenviron)
[‘__doc__’, ‘__file__’, ‘__name__’, ‘__packge__’, ‘getenv’, ‘putenv’]
> >> cenviron.__file__
‘cenviron.dll’
>>> cenviron.__name__
‘cenviron’
>>> cenviron.getenv
<built-in function getenv>
>>> cenviron
<module ‘cenviron’ from ‘cenviron.dll’>
>>> cenviron.getenv(‘HOME’)
‘/home/mark’
>>> cenviron.getenv(‘NONESUCH’)
SystemError: Error calling getenv
Ниже приводится пример задачи, которая решается с помощью этого модуля (но необходимо, чтобы вызовы getenv осуществлялись присоединенным кодом C, а не Python; перед запуском этого сеанса я изменил значение переменной USER в командной оболочке с помощью команды export):
…/PP4E/Integrate/Extend/Cenviron$ python
>>> import os
>>> os.environ[‘USER‘] # инициализируется из оболочки
‘skipper’
>>> from cenviron import getenv, putenv # прямые вызовы функций C
>>> getenv(‘USER’)
‘skipper’
>>> putenv(‘USER’, ‘gilligan’) # изменится для C, но не для Python
>>> getenv(‘USER’)
# ой! — значения не загружаются заново
# то же самое
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011