Skip to content

Home Администрирование Реляционная сериализация
Реляционная сериализация

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

SQLite

Иногда полезно сохранять и работать с данными более структурированным способом, с учетом отношений между ними. Здесь мы будем говорить о семействе инструментов хранения информации, которые называются реляционными базами данных, или СУРБД (системы управления реляционными базами данных). Мы полагаем, что ранее вам уже приходилось использовать такие реляционные базы данных, как MySQL, PostgreSQL или Oracle. Если это так, у вас не должно возникать проблем при чтении этого раздела.

Согласно информации, что приводится на веб-сайте, SQLite - «это библиотека программного обеспечения, реализующая самодостаточный, безсерверный, не требующий настройки механизм базы данных SQL с поддержкой транзакций». Что все это означает? Этот механизм базы данных работает не в виде отдельного процесса на сервере, а в том же самом процессе, что и ваш программный код, и вы можете обращаться к нему как к библиотеке. Данные находятся в файле, а не во множестве каталогов, разбросанных по нескольким файловым системам. И вместо того, чтобы настраивать имя хоста, номер порта, имя пользователя, пароль и так далее, для организации доступа к данным вы просто указываете в своем программном коде имя файла базы данных, созданного библиотекой SQLite. Это предложение также означает, что SQLite является базой данных с достаточно широкими возможностями. Проще говоря, это предложение указывает на два главных преимущества SQLite: простота в использовании и обладание возможностями, присущими «настоящим» базам данных. Еще одно преимущество состоит в ее распространенности. Поддержка SQLite обеспечивается большинством языков программирования в большинстве основных операционных систем.

Теперь, когда вы знаете причины, которые могут побудить к использованию этой базы данных, посмотрим, как ею пользоваться. Мы взяли следующие определения таблиц из примера, где использовалась платформа Django. Предположим, что у нас имеется файл с именем inventory . sql , содержащий следующий текст:

Тогда мы могли бы создать базу данных SQLite следующей командой:

Конечно, здесь мы предполагаем, что вы уже установили SQLite. В системах Ubuntu и debian установка выполняется простой командой apt-get install sqliteS. В системах Red Hat следует выполнить команду yum install sqlite. Для других дистрибутивов Linux, не имеющих установочных пакетов, других систем UNIX или для Windows вы можете загрузить исходные тексты и скомпилированные файлы по адресу http:// www. sqlite. org/ download. html.

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

Все, что нам потребовалось, — это импортировать библиотеку SQLite и затем вызвать функцию connect() в модуле sqliteS. Функция connect() возвращает объект соединения с базой данных, которому мы присвоили имя conn и который мы будем использовать в оставшейся части примера. Далее с помощью объекта соединения мы вbinолняем запрос, добавляющий данные в базу:

Ничего, как мы и ожидали.

Метод execute() возвращает объект курсора базы данных, поэтому мы решили дать ему имя cursor. Обратите внимание, что мы указали значения только для полей name и description и опустили значение для поля id, которое является первичным ключом. Через мгновение вы увидите, что это поле получило свое значение. Поскольку это запрос на добавление данных в базу, а не запрос на выборку, то мы не ждем от запроса результирующего набора данных; поэтому мы просто будем просматривать курсор и извлекать любые результаты, которые он может хранить:

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

Теперь, когда мы создали и заполнили базу данных SQLite, попробуем прочитать записанные данные обратно. Для начала запустим оболочку IPython, импортируем модуль sqliteS и создадим соединение с файлом базы данных:

Теперь мы вbinолним запрос select и получим курсор с результатами:

И, наконец, извлечем данные из курсора:

Это те самые данные, которые были добавлены выше. Значение полей name и description хранятся в Юникоде. А поле id заполнено целым числом. Обычно, когда производится вставка данных в базу и при этом не указывается значение поля первичного ключа, база данных сама заполнит его, автоматически получая следующее уникальное значение для этого поля.

Теперь, когда вы познакомились с основными приемами взаимодействия с базой данных SQLite, реализация соединения таблиц, обновления данных и более сложных операций — это, в значительной степени, вопрос времени. База данных SQLite обеспечивает отличную возможность сохранения данных, особенно когда данные используются единственным сценарием или только несколькими пользователями одновременно. Говоря другими словами, SQLite прекрасно подходит для решения небольших задач. Однако интерфейс модуля sqliteS остается слишком сложным.

Storm ORM

Несмотря на то, что простого SQL-интерфейса к базе данных вполне достаточно для извлечения, изменения, добавления и удаления данных в базе, тем не менее, часто бывает удобнее не отказываться от простоты и удобства языка Python. За последние несколько лет в способах доступа к базам данных появилось новое направление - объектно-ориентированное представление данных, хранящихся в базе. Это направление называется объектно-реляционной проекцией (Object-Relational Mapping, ORM). В терминах ORM объект на языке программирования может соответствовать одной строке в одной таблице базы данных. Таблицы, связанные отношениями внешнего ключа, могут быть доступны в виде атрибутов такого объекта.

Storm - это инструмент ORM, который недавно был вbinущен как продукт, распространяемый с открытыми исходными текстами, компанией Canonical, которая ведет разработку дистрибутива Linux - Ubuntu. Storm - это относительно новый продукт среди средств доступа к базам данных для языка Python, но к нему уже проявляется пристальное внимание и мы полагаем, что он станет одним из основных средств ORM в языке Python.

Теперь мы попробуем использовать Storm для доступа к данным в базе, которая была определена в разделе «SQLite». Первое, что нам следует сделать, - это создать отображение для интересующих нас таблиц. Поскольку мы уже обращались к таблице inventory_operatingsystem и добавили в нее одну запись, мы продолжим работу с этой таблицей. Ниже показано, как выглядит отображение при использовании библиотеки Storm:

Это самое обычное определение класса. Здесь нет ничего сверхъестест
венного. Здесь не наследуется какой-то другой класс, кроме встроенного
типа Object. Зато имеется несколько атрибутов. Единственное, что вы
глядит немного странно, - это атрибут__ storm_Table__ . С его помощью

библиотека Storm определяет, для доступа к какой таблице будет ис
пользоваться этот объект. Пока все выглядит достаточно просто и впол
не обычно, и, тем не менее, во всем этом все-таки есть капелька магии.
Например, атрибут name отображается на поле name в таблице inventory_
operatingsystem, а атрибут description отображается на поле description
в той же таблице. Как? Магия. Любой атрибут, присутствующий в клас
се проекции Storm, автоматически отображается на одноименное поле
в таблице, имя которой определяется атрибутом___________ storm_Table .

А что, если нам не нужно, чтобы атрибут description объекта отображался на поле description? Тогда просто передайте методу storm, locals. Type имя требуемого поля в именованном аргументе name. Например, изменив определение атрибута description на такое: dsc = storm, locals. Unicode(name='description'), вы тем самым свяжете атрибут dsc объекта OperatingSystem с тем же самым полем (то есть с полем description). Но тогда на описание нужно будет ссылаться не как на атрибут mapped_Object. description, а как на атрибут mapped_Object. dsc.

Теперь, когда у нас имеется класс проекции на таблицу в базе данных, попробуем добавить в нее еще одну строку. В дополнение к нашему древнему дистрибутиву Linux на ядре 2.0.34 мы добавим Windows 3.1.1:

В этом примере мы импортировали модули storm, locals, storm_model и os. Затем мы создали экземпляр класса OperatingSystem и присвоили значения его атрибутам name и description. (Обратите внимание: в качестве значений этих атрибутов мы использовали строки Юникода.) Затем мы создали объект базы данных, вызвав функцию create_database(), и передали этому методу путь к файлу нашей базы данных SQLite, inventory. DB. Вы могли бы подумать, что объект базы данных будет использоваться для добавления данных в базу, но это не так, по крайней мере, не напрямую. Сначала нам нужно создать объект Store, передав объект базы данных конструктору. После этого мы можем добавить объект operating_system в объект store. В заключение вызывается метод commit объекта store, чтобы подтвердить добавление объекта оре-rating_system в базу данных.

Мы также хотели бы убедиться, что вставленные данные действительно были записаны в базу данных. Поскольку это база данных SQLite, можно было бы просто воспользоваться инструментом командной строки sqliteS. Но если сделать это, то у нас не будет причин написать программный код, извлекающий данные из базы с помощью Storm. Итак, ниже приводится простая утилита, которая извлекает и выводит все записи из таблицы inventory_operatingsystem (хотя и в довольно уродливом виде):

Первые несколько строк в этом примере поразительно напоминают первые несколько строк предыдущего примера. Отчасти это сходство обусловлено тем, что мы просто скопировали программный код из одного файла в другой. Впрочем, это не главное. Основная же причина заключается в том, что в обоих случаях необходимо выполнить одни и те же подготовительные действия, прежде чем сценарии смогут «общаться» с базой данных. Здесь используются те же инструкции импортирования, что и в предыдущем примере. У нас имеется объект DB, который возвращает функция create_database(). У нас имеется объект store, созданный конструктором Store(), которому был передан объект DB. Но теперь вместо добавления объекта в хранилище (в объект store) мы вызываем метод find() объекта store. Этот конкретный вызов метода find() (то есть store. find(storm_model. OperatingSystem)) возвращает множество всех объектов storm_model.OperatingSystem. Поскольку класс OperatingSystem является проекцией на таблицу inventory_operatingsystem,

Storm отыщет все подходящие записи в таблице inventory_operating-system и создаст объект OperatingSystem для каждой из них. Для каждого объекта OperatingSystem выводятся значения атрибутов id, name и description. Эти атрибуты являются проекциями на одноименные поля записей в базе данных.

В нашей базе данных уже имеется одна запись, добавленная в более раннем примере, приводившемся в разделе «SQLite». Давайте посмотрим, что получится, если запустить этот сценарий. Мы могли бы ожидать, что будет выведена одна запись, хотя она и была добавлена без использования библиотеки Storm:

Это в точности соответствует нашим ожиданиям. Теперь сначала попробуем запустить сценарий, добавляющий новую запись, а затем снова запустим сценарий, извлекающий данные. На этот раз он должен вывести старую запись, добавленную ранее (система на базе ядра Linux 2.0.34), и только что добавленную запись (Windows 3.1.1):

И снова мы получили именно то, что и ожидали получить.

Но что, если нам потребуется фильтровать данные? Предположим, что нам потребуется увидеть только те операционные системы, название которых начинается с последовательности символов «Lin». Ниже приводится фрагмент программного кода, который делает именно это:

Этот пример идентичен предыдущему примеру, где использовался метод store, find(), за исключением того, что в этом примере методу store. find() передается второй параметр: критерий поиска. Вызов Store. find(storm_model. OperatingSystem, storm_model. OperatingSystem. name.like(u'Lin%' )) сообщает библиотеке Storm, что требуется отыскать все объекты OperatingSystem, у которых значение атрибута name начинается со строки Юникода Lin. Каждое значение в наборе результатов выводится точно так же, как и в предыдущем примере.

И когда мы запустим этот фрагмент, мы увидим следующее:

В базе данных по-прежнему присутствует запись с названием операционной системы «Windows 3.1.1», но она была отфильтрована, потому что не начинается со строки «Lin».

SQLAIchemy ORM

В то время как библиотека Storm только начинает обретать сторонников и находится на стадии формирования сообщества, библиотека SQLAIchemy уже является доминирующим средством ORM для языка Python. Своим подходом к решению проблемы она напоминает Storm. Вероятно, лучше было бы сказать, что «библиотека Storm своим подходом к решению проблемы напоминает SQLAIchemy», поскольку библиотека SQLAIchemy появилась раньше. Но, как бы то ни было, для демонстрации SQLAIchemy мы воспользуемся все той же таблицей inventory_operatingsystem, для работы с которой только что использовали библиотеку Storm.

Ниже приводится определение таблицы и объекта для отображения таблицы inventory_operatingsystem:

Самое существенное различие между примерами использования Storm и SQLAlchemy заключается в определении таблицы, которое используется библиотекой SQLAlchemy для создания проекции вместе с классом таблицы.

Теперь, когда у нас имеется определение таблицы, можно написать программный код, вbinолняющий запрос всех записей из таблицы:

Если запустить этот фрагмент теперь, когда в таблице уже имеются некоторые данные, записанные туда в предыдущих примерах, мы увидим следующее:

Если бы нам потребовалось создать еще одну запись, мы легко могли бы сделать это, просто создав объект OperatingSystem и добавив его в объект

В результате в таблицу будет добавлена другая запись с операционной системой Linux на другом ядре, более современном. Запустив сценарий, запрашивающий все записи, еще раз, мы получим:

Фильтрация результатов в SQLAlchemy выполняется также просто. Например, если бы нам потребовалось выбрать все объекты Operating-System, в которых значение атрибута name начинается с последовательности символов «Lin», мы могли бы написать следующий сценарий:

И мы могли бы получить следующие результаты:

Это был лишь краткий обзор возможностей библиотеки SQLAlchemy. За дополнительной информацией об использовании SQLAlchemy обращайтесь на веб-сайт http:// www. sqlalchemy. org/. Или приобретите книгу Рика Коупленда (Rick Coupland) «Essential SQLAlchemy» (O'Reilly).

Тут мы рассмотрели несколько различных инструментов, позволяющих сохранять данные для последующего использования. Иногда вам будет требоваться нечто простое и легковесное, как модуль pickle. Иногда вам будет требоваться нечто более полнофункциональное, как SQLAlchemy ORM. Как уже было показано, при использовании языка Python в вашем распоряжении имеется множество решений, от очень простых до мощных и сложных.

Комментарии (0)

RSS feed Comments

Написать комментарий

smaller | bigger

busy
 

Регистрация




Top