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

URI

Html (ЖЖ)

BB-код (Для форумов)

Оставить комментарий

Вы должны войти, чтобы оставить комментарий.