Часто бывает, что хочется использовать функции из библиотеки на C, но для нее не написан модуль-обертка для Python, либо по какой-то причине нам не хочется использовать то что написано. Что ж, выход есть. Модуль ctypes (который включен в стандартную библиотеку Python начиная с версии 2.5, а до этого доступен в качестве стороннего модуля) позволяет нам вызывать практически что угодно откуда угодно.
Например, нам нужно узнать тип файла. Для этого в UNIX-системах есть механизм, который определяет тип файла по некой последовательности байтов в начале файла, называемой magic number. В системе есть база данных, сопоставляющая эти последовательности с типами файлов. Например, файлы в формате GIF всегда содержат строку “GIF8? в начале файла, плюс несколько байтов далее, содержащих информацию о версии формата, количестве цветов и т.д. Эта база данных содержится в текстовом файле, который называется magic, и может быть расположен в разных местах, в зависимости от операционной системы или дистрибутива. Например, в системе FreeBSD этот файл обычно лежит в /usr/share/misc/, в версии Linux, которую я использую - в /usr/share/misc/file/. Можно, конечно, написать свой механизм работы с этим файлом, и самостоятельно искать соответствия, однако мы — ленивые программисты, и пойдем другим путем :)
В системе есть программа file, являющаяся интерфейсом пользователя к базе данных magic. Тип файла определить можно запуском этой команды с параметром - именем файла, либо передав ей содержимое файла на стандартный ввод.
$ file --mime image.gif
image.gif: image/gif
$ cat image.gif|file - --mime
/dev/stdin: image/gif
Программа file использует библиотеку libmagic.so, которая как раз и является встроенным системным средством работы с файлом magic, её-то мы и задействуем. Для начала посмотрим руководство по библиотеке — man libmagic. Руководство содержит описание функций библиотеки. Не буду тут подробно описывать интерфейс библиотеки, думаю вы с этим справитесь сами. Опишу лишь механизм использования этой библиотеки из python через ctypes.
Простейшая функция интерфейса к libmagic выглядит примерно так:
import ctypes def get_magic_type(string): magic = ctypes.cdll.LoadLibrary('libmagic.so.1') cookie = magic.magic_open() magic.magic_load(cookie, None) result = magic.magic_buffer(cookie, string, len(string)) mimetype = ctypes.c_char_p(result) magic.magic_close(cookie) return mimetype.value
Проверим как работает:
>>> data = open("image.gif").read() >>> get_magic_type(data) 'image/gif' Эта функция принимает данные, тип которых мы хотим определить, в виде строки. Вызов ctypes.cdll.LoadLibrary загружает нужную битблиотеку — в данном случае она известна системе как libmagic.so.1, и связывает ее пространство имен с переменной magic. При обращении к атрибутам этой переменной фактически происходит обращение к переменным и функциям из этой библиотеки, и работать с ними нужно так, как подразумевает интерфейс этой библиотеки. При этом есть одна тонкость, о которой стоит упомянуть. Язык C - это язык со строгой статической типизацией. Функции библиотеки часто принимают и возвращают указатели определенного типа, и это следует учитывать, приводя тип данных в программе на python в соответствующий тип данных, который принимает функция в соответствие с документацией. Если это не сделано, то модуль ctypes старается привести типы данных. В вызове magic.magic_buffer(cookie, string, len(string)) так и происходит. Однако, возможно это не всегда будет то что вам нужно, и типы нужно привести самостоятельно. Для облегчения задачи можно присвоить нужной функции атрибут argtypes, содержащий список типов данных для каждого позиционного аргумента. Упомянутый выше вызов возвращает указатель на строку, так что в переменной result будет содержаться целое число — значение указателя. Чтобы получить саму строку, мы приводим это значение к соответствующему типу — mimetype = ctypes.c_char_p(result) — после чего можно получить значение, на которое ссылается указатель — mimetype.value . Как и с аргументами, можно заранее определить тип возвращаемого значения, присвоив атрибуту restype нужной функции значение нужного типа, например так: magic.magic_buffer.restype = ctypes.c_char_p. В этом случае переменнаяresult сразу получит сам указатель, а не число.
Скорее всего, для production-ready решения придется добавить в функцию несколько проверок и обработку ошибок (библиотечные функции magic_error или magic_errno), добавить более тонкую настройку (magic_setflags), возможно использовать еще какие-то возможности библиотеки, но в данном примере я этого не делаю для простоты изложения.
И еще. Помните, что когда вы практически напрямую вызываете функции из библиотеки на C, вы можете столкнуться с разными фатальными ошибками, присущими программам и библиотекам, написанным на этом языке, например segmentation fault, что редко случается при программировании на “чистом” Python c использованием проверенных и стабильных модулей. Не то чтобы C плохой, просто придя в этот монастырь вам придется учесть, что у него есть устав. Рассматривайте программирование с использованием ctypes как сильно облегченный вариант программирования на голом C. Будьте аккуратны. Я предупредил.
В качестве бонуса рассмотрим вариант валидации в формах Dajngo загруженного по HTTP файла изображения. Многие считают достаточным проверить значение content-type для содержимого соответствующего поля формы. Однако, это значение приходит в данных POST, и, следовательно, его легко подделать. В манипуляторах Django валидация происходит через попытку загрузить содержимое в объект Image пакета PIL — если нам приехал мусор, то данная операция вызовет исключение. Я же сделаю валидацию с использованием написанной выше функции.
from django import newforms as forms class ImageForm(forms.Form): valid_image_types = ('image/jpeg', 'image/gif', 'image/png') image = forms.Field(widget=forms.FileInput) def clean_image(self): data = self.data.get('image', None) if data: mimetype = get_magic_type(data['content']) if mimetype not in self.valid_image_types: raise forms.ValidationError(_('This file is not valid image.')) return data
Постоянные ссылки
При копировании ссылка на TeaM RSN обязательна!
Оставить комментарий
Вы должны войти, чтобы оставить комментарий.