Категории

Язык
Интерпретатор
Платформа
ЯП
Проект

1 ноября 2014 г. 1:08 (ред. 1 ноября 2014 г. 10:09)
В статье на примере обвязки pyavdesk будет рассмотрен пример сопряжения Python с библиотекой, программный интерфейс которой спроектирован в дружелюбной к обвязкам манере.
Для тех, кто не знаком с модулем ctypes, входящим в стандартную поставку Питона, можно кратко описать его как средство для взаимодействия с функциями сторонних библиотек из вашего python-приложения. Это, конечно, далеко не единственный возможный вариант применения модуля, но нам в данной статье интересен именно он.

Итак, при помощи этого модуля вы можете создавать обвязки разделяемых (или динамически связываемых в Windows) библиотек, экспортирующих полезные вам функции.

Нужно отметить, что для создания обвязок можно использовать не только ctypes (см. SWIG, Python/C API, cffi и даже Cython), однако рекомендуемый метод сочетает в себе несколько преимуществ, которые вы наверняка оцените по достоинству, а именно: быстрота и относительная простота разработки, удобство поддержки и переносимость.

Теперь, после того, как некое представление о ctypes составлено, переходим к основной части. Код нашего модуля доступен из репозитория pyavdesk, код основной библиотеки dwavdapi, написанной на Си, закрыт, но это не беда — о принципах по которым построен её дружелюбный к автоматизации программный интерфейс я расскажу вам на словах (некоторые дополнительные сведения об интерфейсе всё же можно почерпнуть из статьи Антона и кода php-расширения).

Какими же качествами должен обладать программный интерфейс библиотеки, чтобы её можно было называть дружелюбной к обвязыванию? Несомненно теми же, что и просто хороший программный интерфейс: логичностью и структурированностью. Однако для того, чтобы обвязки создавались как можно более безболезненно, потребуется довести структурированность до высочайшего уровня, и просто хорошие имена функций и структур превратить в хорошие имена функций и структур, подчиняющиеся определенным правилам, например:


dwavdapi_group_init();
dwavdapi_group_add();
dwavdapi_group_destroy();

dwavdapi_station_init();
dwavdapi_station_add();
dwavdapi_station_destroy();

Заметьте, функции именуются однотипно по правилу библиотека_субъект_действие. Что это даёт, кроме того, что вы можете предугадать, как будут называться функции, скажем, для субъекта admin? По меньшей мере, в обвязках на языках поддерживающих объектно-ориентированный стиль разбросать функциональность по классам, возможно даже с иерархиями (пример базового класса, инкапсулирующий логику формирования обращений к функциям библиотеки), а в языках без таковой использовать макросы или подобное.

Но в порыве структурирования нельзя так быстро остановиться, нужно пойти дальше. Следующим шагом должно стать упорядочивание параметров функций выполняющих одинаковые по смыслу действия. Согласитесь, нет смысла передавать handle объекта в add-функцию в одном случае первым, в другом — вторым — ничего кроме недоумения пользователя это не вызовет.

Если в библиотеке используются связные списки, для навигации по ним лучше предусмотреть функции (возможно даже общие для разных типов списков), которые будет удобно использовать в обвязках. Например: dwavdapi_list_next, dwavdapi_list_current_data.

Что ещё не помешает хорошей библиотеке? Переход в отладочный режим и получение номера версии. Вроде бы очевидные вещи, но иногда про них можно забыть.

С отладочным режимом всё просто: журнал — зачастую незаменимая вещь при поиске ошибок в библиотеке, и вкупе с журналом вашей обвязки поможет ответить на многие вопросы. Обвязка, например, может заносить в журнал данные о вызове функции библиотеки и её результате.

Ориентируясь по номеру версии библиотеки, обвязка сможет отключать, подключать, варьировать доступную пользователю функциональность, или вовсе отказываться работать.

Теперь обратимся к нашему модулю. На что следует обратить внимание при использовании ctypes.

Прежде всего, не стоит просто бездумно оборачивать функции только ради того, чтобы они были доступны в Питоне, если вам этого достаточно, то используйте SWIG. Поразмыслите, прикиньте, почти наверняка вы сможете предложить конечным пользователям вашего модуля нечто большее: усовершенствовать существующие абстракции, создать новые, добавить функциональности. Если вдруг в основной библиотеке использована какая-то жуткая терминология из какой-то сильно умной спецификации, не обязательно ей следовать, гораздо лучше ввести новые, но доступные для пользователя понятия.

Далее, если вы собираетесь работать непосредственно со структурами библиотеки, вам потребуется в том или ином виде объявить их у себя в модуле — инструментами по импорту данных из заголовочных файлов ctypes не располагает. Для упрощения работы со структурами можно объявить некий базовый для них класс, который в дальнейшем сможет помочь в заполнении их данными. При указании типов данных, помните, что поддержки некоторых из них может не оказаться на целевой ОС, определение же других может варьироваться (см. time_t).

Создавайте вспомогательные функции/методы обёртки над ctypes для устранения дублирующегося кода.

Для функций, возвращающих результат аргументом, используйте в связке тип c_void_p и функцию by_ref():


result = ctypes.c_void_p()
self._lib_call('fucname', ctypes.byref(result))

Проектируя систему исключений, стоит выделить отдельные их типы для ошибок из библиотеки, ошибок взаимодействия модуля с библиотекой и, наконец, ошибок самого модуля. Это поможет вам и вашим пользователям лучше понять на каком уровне произошла ошибка.

Покрывайте код модуля тестами. Нередки случаи, когда тесты модуля помогают обнаружить ошибки/течь в самих библиотеках.

И ещё одно, чуть ли не главное, но на закуску: пишите документацию. Это касается и библиотеки, и модуля.

Добавка: В красках о процессе проектирования одного из аспектов обсуждаемой в этой статье библиотеки можно узнать из нашего с Антоном старинного подкаста:



Делайте обёртки для хороших библиотек.
Делайте их на Питоне.