пятница, 23 марта 2007 г.

ctypes: использование произвольных функций из библиотек на C из Python

Часто бывает, что хочется использовать функции из библиотеки на 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'</code


Эта функция принимает данные, тип которых мы хотим определить, в виде строки. Вызов 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

2 комментария:

  1. Хм. Лично для меня только одна новость- то что это входит теперь в стандартную постаку py2.5 ( каюсь - новщества в 2.5. поленился вдумчиво изучить). А для тех кто не вкурсе - все расписано очень доходчиво. Единственное я бы дополнил статью данными о том ,какой выигрыш ты получил в данном конкретном случае. Такой вариант работает быстрее чем с использованием PIL ? Насколько процентов? Это было бы понагладней IMHO/

    ОтветитьУдалить
  2. How To Register & Claim 1xbet Korean Casino - Legalbet.co.kr
    How To Register & Claim 1xbet korean 1xbet Korean Casino at Legalbet.co.kr. 바카라 사이트 We use cookies. By using this website, you consent to our use deccasino of this website.

    ОтветитьУдалить

Постоянные читатели