Sue Jones
...\PP4E\Preview> python update_db_file.py ...\PP4E\Preview> python dump_db_file.py
bob =>
{‘pay’: 30000, ‘job’: ‘dev’, ‘age’: 42, ‘name’: ‘Bob Smith’} sue =>
{‘pay’: 44000.0, ‘job’: ‘hdw’, ‘age’: 45, ‘name’: ‘Sue Jones’}
tom =>
{‘pay’: 0, ‘job’: None, ‘age’: 50, ‘name’: ‘Tom Tom'}
Sue Jones
В настоящий момент, чтобы внести в базу данных какие-либо изменения, необходимо написать отдельный программный код, в виде сценария или в интерактивной оболочке (далее в этой главе будут представлены более удачные решения с универсальным интерфейсом командной строки, графическим интерфейсом и веб-интерфейсом). Но, как бы то ни было, наш текстовый файл является базой данных, содержащей записи. Однако, как мы увидим в следующем разделе, мы только что проделали массу ненужной работы.
Модуль pickle
Реализация базы данных на основе текстового файла, представленная в предыдущем разделе, вполне работоспособна, но она имеет ряд существенных ограничений. Во-первых, чтобы получить доступ всего к одной записи, необходимо загрузить всю базу данных из файла, а после некоторых изменений обратно в файл необходимо записывать всю базу данных. Данное ограничение можно обойти, если каждую запись сохранять в отдельном файле, но это еще больше осложнит реализацию программы.
Во-вторых, решение на основе текстового файла предполагает, что символы, выполняющие роль разделителей записей, не должны появляться в самих данных: если, к примеру, данные могут содержать последовательность символов =>, предложенное решение окажется непригодным. Мы могли бы обойти это ограничение, сохраняя записи в формате XML, а для загрузки данных используя инструменты для работы с форматом XML, входящие в состав Python, с которыми мы познакомимся далее в этой книге. Использование тегов XML позволило бы избежать конфликтов с фактическими данными в текстовом виде, но необходимость создания и парсинга XML снова привела бы к усложнению программы.
Хуже всего, пожалуй, то, что решение на основе текстового файла оказывается слишком сложным, не будучи при этом достаточно универсальным: оно привязано к организации базы данных в виде словаря словарей и не способно работать с базами данных, имеющими другую структуру, без существенного расширения реализации. Было бы здорово, если бы существовал универсальный инструмент, способный преобразовывать любые типы данных Python в формат, который можно было бы сохранять в файл за один шаг.
Именно для этого разработан модуль pickle. Этот модуль преобразует объект Python, находящийся в оперативной памяти, в последовательность или в строку байтов, которую можно записать в любой объект, подобный файлу. Кроме того, модуль pickle знает, как восстановить оригинальный объект в памяти, получив последовательность байтов, то есть мы получаем обратно тот же самый объект. В некотором смысле модуль pickle позволяет избежать необходимости разрабатывать специальные форматы представления данных - последовательный формат, реализованный в этом модуле, достаточно универсален и эффективен для большинства применений. При использовании модуля pickle отпадает необходимость вручную преобразовывать объекты перед сохранением и анализировать сложный формат представления данных, чтобы получить исходные объекты. Прием, основанный на использовании модуля pickle, напоминает прием, основанный на использовании формата XML, но он является не только более характерным для Python, но и более простым в реализации.
Другими словами, применение модуля pickle позволит нам сохранять и извлекать объекты Python за один шаг, благодаря чему мы сможем обрабатывать записи, используя привычный синтаксис языка Python. Невзирая на сложность реализации, модуль pickle удивительно прост в использовании. В примере 1.5 демонстрируется, как с помощью этого модуля можно сохранять записи в файле.
from initdata import db import pickle
dbfile = open(‘people-pickle’, ‘wb’) # в версии 3.X следует использовать pickle.dump(db, dbfile) # двоичный режим работы с файлами, так как
dbfile.close() # данные имеют тип bytes, а не str
Если запустить этот сценарий, он сохранит всю базу данных (словарь словарей, который создается сценарием из примера 1.1) в файл с именем
import pickle
dbfile = open(‘people-pickle’, ‘rb’) # в версии 3.X следует использовать db = pickle.load(dbfile) # двоичный режим работы с файлами
for key in db:
print(key, ‘=>\n ‘, db[key]) print(db[‘sue’][‘name’])
Ниже приводится пример запуска этих двух сценариев из командной строки. Естественно, эти сценарии можно запустить и в среде IDLE, чтобы в интерактивном сеансе открыть и исследовать файл, созданный модулем pickle:
...\PP4E\Preview> python make_db_pickle.py ...\PP4E\Preview> python dump_db_pickle.py
bob =>
{‘pay’: 30000, ‘job’: ‘dev’, ‘age’: 42, ‘name’: ‘Bob Smith’}
sue =>
{‘pay’: 40000, ‘job’: ‘hdw’, ‘age’: 45, ‘name’: ‘Sue Jones’} tom =>
{‘pay’: 0, ‘job’: None, ‘age’: 50, ‘name’: ‘Tom’}
Sue Jones
Внесение изменений в базу данных, сохраненную с помощью модуля pickle, выполняется точно так же, как и при использовании текстового файла, созданного вручную, за исключением того, что все необходимые преобразования выполняются стандартным модулем. Как это делается, демонстрирует пример 1.7.
import pickle
dbfile = open(‘people-pickle’, ‘rb’) db = pickle.load(dbfile) dbfile.close()
db[‘sue’][‘pay’] *= 1.10 db[‘tom’][‘name’] = ‘Tom Tom’
dbfile = open(‘people-pickle’, ‘wb’) pickle.dump(db, dbfile) dbfile.close()
Обратите внимание, что после изменения записи в файл сохраняется вся база данных целиком, как и при использовании простого текстового файла; это может занимать продолжительное время, если база данных имеет значительный объем, но мы пока не будем беспокоиться об этом. Ниже приводится пример запуска сценариев
...\PP4E\Preview> python update_db_pickle.py ...\PP4E\Preview> python dump_db_pickle.py
bob =>
{‘pay’: 30000, ‘job’: ‘dev’, ‘age’: 42, ‘name’: ‘Bob Smith’}
sue =>
{‘pay’: 44000.0, ‘job’: ‘hdw’, ‘age’: 45, ‘name’: ‘Sue Jones’} tom =>
{‘pay’: 0, ‘job’: None, ‘age’: 50, ‘name’: ‘Tom Tom'}
Sue Jones
Как мы узнаем в главе 17, модуль pickle поддерживает объекты практически любых типов - списки, словари, экземпляры классов, вложенные структуры и многие другие. Там же мы узнаем о текстовых и двоичных протоколах преобразования сохраняемых данных. В Python 3 для представления сохраненных данных все протоколы используют объекты типа bytes, чем обусловлена необходимость открывать файлы pickle в двоичном режиме, независимо от используемого протокола. Кроме того, как будет показано далее в этой главе, модуль pickle и его формат представления данных используется модулем shelve и базами данных ZODB, а в случае экземпляров классов сохраняются не только данные в объектах, но и их поведение.
Модуль pickle фактически является гораздо более универсальным, чем можно было бы заключить из представленных примеров. Поскольку сериализованные данные принимаются любыми объектами, поддерживающими интерфейс, совместимый с файлами, методы dump и load модуля pickle могут использоваться для передачи объектов Python через различные среды распространения информации. С помощью сетевых сокетов, например, можно организовать передачу сериализованных объектов Python по сети и тем самым обеспечить альтернативу более тяжелым протоколам, таким как SOAP и XML-RPC.
Работа модуля pickle с отдельными записями
Как упоминалось выше, один из потенциальных недостатков примеров, представленных в этом разделе до настоящего момента, состоит в том, что они могут оказаться слишком медленными при работе с очень большими базами данных: так как для изменения единственной записи необходимо загружать и сохранять базу данных целиком, при таком решении значительная часть времени будет тратиться впустую. Мы могли бы избежать этого, предусмотрев сохранение каждой записи базы данных в отдельном файле. Следующие три примера демонстрируют, как это можно реализовать, - сценарий из примера 1.8 сохраняет каждую запись в отдельном файле, где в качестве имени файла используется уникальный ключ записи, к которому добавляется расширение
from initdata import bob, sue, tom import pickle
for (key, record) in [(‘bob’, bob), (‘tom’, tom), (‘sue’, sue)]: recfile = open(key + ‘.pkl’, ‘wb’) pickle.dump(record, recfile) recfile.close()
Следующий сценарий, представленный в примере 1.9, выводит содержимое всей базы данных, используя модуль glob для подстановки имен файлов и тем самым для выбора всех файлов с расширением
import pickle, glob
for filename in glob.glob(‘*.pkl’): # для ‘bob’,’sue’,’tom’
recfile = open(filename, ‘rb’) record = pickle.load(recfile) print(filename, ‘=>\n ‘, record)
suefile = open(‘sue.pkl’, ‘rb’)
print(pickle.load(suefile)[‘name’]) # извлечь имя Сью
Наконец, сценарий в примере 1.10 обновляет содержимое базы данных, извлекая запись из ее файла, изменяя объект в памяти и затем сохраняя его обратно в файл с помощью модуля pickle. На этот раз для внесения изменений необходимо извлечь и сохранить единственную запись, а не всю базу данных.
import pickle
suefile = open(‘sue.pkl’, ‘rb’)
sue = pickle.load(suefile)
suefile.close()
sue[‘pay’] *= 1.10
suefile = open(‘sue.pkl’, ‘wb’)
pickle.dump(sue, suefile)
suefile.close()
Ниже приводится пример запуска сценариев, реализующих решение, когда каждая запись сохраняется в отдельном файле. Результаты получаются те же, что и прежде, но теперь уникальные ключи в базе данных играют роль имен файлов. При такой реализации файловая система превращается в своеобразный словарь верхнего уровня - имена файлов обеспечивают прямой доступ к отдельным записям.
...\PP4E\Preview> python make_db_pickle_recs.py ...\PP4E\Preview> python dump_db_pickle_recs.py
bob.pkl =>
{‘pay’: 30000, ‘job’: ‘dev’, ‘age’: 42, ‘name’: ‘Bob Smith’} sue.pkl =>
{‘pay’: 40000, ‘job’: ‘hdw’, ‘age’: 45, ‘name’: ‘Sue Jones’} tom.pkl =>
{‘pay’: 0, ‘job’: None, ‘age’: 50, ‘name’: ‘Tom’}
Sue Jones
...\PP4E\Preview> python update_db_pickle_recs.py ...\PP4E\Preview> python dump_db_pickle_recs.py
bob.pkl =>
{‘pay’: 30000, ‘job’: ‘dev’, ‘age’: 42, ‘name’: ‘Bob Smith’} sue.pkl =>
{‘pay’: 44000.0, ‘job’: ‘hdw’, ‘age’: 45, ‘name’: ‘Sue Jones’} tom.pkl =>
{‘pay’: 0, ‘job’: None, ‘age’: 50, ‘name’: ‘Tom’}
Sue Jones
Модуль shelve
Сохранение объектов в файлах, как было показано в предыдущем разделе, является оптимальным решением для многих приложений. Фактически некоторые приложения используют модуль pickle для передачи объектов Python через сетевые сокеты, как более простую альтернативу сетевым протоколам веб-служб, таким как SOAP и XML-RPC (они также поддерживаются в Python, но являются более тяжеловесными по сравнению с модулем pickle).
Кроме того, если допустить, что файловая система способна хранить необходимое нам количество файлов, реализация, сохраняющая каждую запись в отдельном файле, устраняет необходимость загрузки и сохранения всей базы данных при каждом изменении. Однако, когда действительно необходимо иметь возможность доступа к записям по ключу, можно использовать более высокоуровневый инструмент: модуль shelve.
Модуль shelve автоматически сохраняет и загружает объекты из хранилища, обеспечивающего доступ по ключу. Хранилища напоминают словари; их необходимо открывать, и они автоматически сохраняются после завершения программы. Поскольку хранилища обеспечивают доступ к хранимым записям по ключу, отпадает необходимость создавать отдельные файлы для каждой записи - модуль shelve автоматически разделяет записи и извлекает и обновляет только те записи, к которым осуществляется доступ или которые изменяются. Таким образом модуль shelve обеспечивает решение, напоминающее решение, сохраняющее каждую запись в отдельном файле, но более простое в использовании.
Интерфейс модуля shelve так же прост, как и интерфейс модуля pickle: хранилища, создаваемые модулем shelve, идентичны словарям с дополнительными методами open и close. В программном коде объекты хранилищ действительно выглядят, как словари, содержимое которых сохраняется после завершения программы. А все операции по отображению содержимого хранилища в файл и из файла выполняются интерпретатором Python. Например, сценарий в примере 1.11 демонстрирует, как можно сохранить объекты из словаря в хранилище, созданном с помощью модуля shelve.
from initdata import bob, sue import shelve
db = shelve.open(‘people-shelve’) db[‘bob’] = bob db[‘sue’] = sue db.close()
Этот сценарий создаст в текущем каталоге один или более файлов, имена которых начинаются с префикса
import shelve
db = shelve.open(‘people-shelve’) for key in db:
print(key, ‘=>\n ‘, db[key]) print(db[‘sue’][‘name’]) db.close()