Шаг 2: сохранение записей на длительное время

shag 2 sohranenie zapisej na dlitelnoe vremya «Программирование на Python»: краткий очерк

К настоящему моменту мы остановились на представлении нашей базы данных в виде словаря и попутно рассмотрели некоторые способы реализации структур данных в языке Python. Однако, как уже упоминалось выше, объекты, с которыми мы имели дело до сих пор, временные объекты; они располагаются в оперативной памяти и исчезают бесследно после завершения работы интерпретатора Python или программы, создавшей их. Чтобы обеспечить долговременное хранение базы данных, ее необходимо сохранить в каком-нибудь файле.

Текстовые файлы

Один из способов обеспечить сохранность данных между запусками программы заключается в сохранении всех данных в простом файле, в виде отформатированного текста. Выработав определенную договоренность о формате представления данных, которая будет использоваться инструментами чтения и записи, мы сможем реализовать любую схему хранения.

Тестовый сценарий создания данных

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

Пример 1.1. PP4E\Preview\initdata.py

#  инициализировать данные для последующего сохранения в файлах

#  записи

bob = {‘name’: ‘Bob Smith’, ‘age’: 42, ‘pay’: 30000, ‘job’: ‘dev’}

sue = {‘name’: ‘Sue Jones’, ‘age’: 45, ‘pay’: 40000, ‘job’: ‘hdw’}

tom = {‘name’: ‘Tom’, ‘age’: 50, ‘pay’: 0, ‘job’: None}

#  база данных db = {} db[‘bob’] = bob db[‘sue’] = sue db[‘tom’] = tom

if __name__ == ‘__main__’: # если запускается, как сценарий

for key in db:

print(key, ‘=>\n ‘, db[key])

Как обычно, проверка переменной __name__ в конце примера 1.1 возвращает true, только если файл был запущен как самостоятельный сценарий, а не был импортирован как модуль. Если запустить пример как самостоятельный сценарий (например, из командной строки, щелчком на ярлыке или из среды IDLE), будет выполнен программный код теста в теле условной инструкции, который выведет содержимое базы данных в поток стандартного вывода (напомню, что функция print использует этот поток по умолчанию).

Ниже приводится пример запуска сценария из командной строки в ОС Windows. В окне Командная строка (Command Prompt) выполните команду cd, чтобы перейти в каталог со сценарием. На других платформах используйте аналогичную программу-консоль:

\PP4E\Preview> python initdata.py bob =>

{‘job’: ‘dev’, ‘pay’: 30000, age’: 42, name’: ‘Bob Smith’} sue =>

{‘job’: ‘hdw’, ‘pay’: 40000, age’: 45, name’: ‘Sue Jones’} tom =>

{‘job’: None, ‘pay’: 0, ‘age’: 50, ‘name’: ‘Tom’}

Соглашения об именовании файлов

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

     Текст …\PP4E\Preview> в первой строке предыдущего примера обозначает приглашение к вводу в командной строке и может отличаться в разных платформах. Вам необходимо ввести лишь текст, следующий за этим приглашением (python initdata.py).

     Во всех примерах в этой книге текст системной подсказки к вводу содержит путь к каталогу в загружаемом пакете с примерами, внутри которого должна запускаться указанная команда. При запуске сценария из командной строки убедитесь, что текущим рабочим каталогом является каталог PP4E\Preview. Это может иметь значение для примеров, использующих файлы в рабочем каталоге.

     Кроме того, подписи, предшествующие примерам с листингами программного кода из файлов, сообщают, где находится файл в пакете с примерами. Так, подпись к примеру 1.1 выше сообщает, что полное имя сценария в дереве каталогов имеет вид PP4E\Preview\ initdata.py.

Мы будем пользоваться этими соглашениями на протяжении всей книги — в предисловии описано, как получить примеры, если вы собираетесь работать с ними. Иногда, особенно в части книги о системном программировании, я буду указывать в приглашении к вводу более полный путь к каталогу, если это будет необходимо, чтобы уточнить контекст выполнения (например, префикс «C:\» в Windows или дополнительные имена каталогов).

Замечания по поводу сценариев

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

     На некоторых платформах может потребоваться вводить полный путь к каталогу с программой на языке Python. Если путь к выполняемому файлу интерпретатора Python отсутствует в системном пути поиска, замените в Windows, например, команду python на C:\ Python31\python (здесь предполагается, что вы пользуетесь версией Python 3.1).

     В большинстве систем Windows вообще не обязательно вводить команду python — чтобы запустить сценарий, вполне достаточно ввести только имя файла, поскольку интерпретатор Python обычно регистрируется, как программа для открытия файлов с расширением «.py».

     Кроме того, файлы сценариев можно запускать в стандартной среде IDLE (откройте файл и запустите его, воспользовавшись меню Run (Запустить) в окне редактирования файла) или похожим способом в любой другой среде разработки программ на языке Python IDE (например, в Komodo, Eclipse, NetBeans или Wing IDE).

     Если вы собираетесь запускать файл в Windows щелчком мыши на ярлыке, не забудьте добавить вызов функции input() в конец сценария, чтобы окно с выводом программы не закрылось после ее завершения. В других системах, чтобы обеспечить возможность запуска сценария щелчком на ярлыке, может потребоваться добавить в его начало строку #! и сделать файл выполняемым с помощью команды chmod.

Далее я буду исходить из предположения, что вы в состоянии запустить программный код на языке Python каким-либо способом. Однако если вы столкнетесь с трудностями, за полной информацией о способах запуска программ на языке Python обращайтесь к другим книгам, таким как «Изучаем Python».

Сценарий записи/чтения данных

Теперь осталось лишь реализовать сохранение всех данных из памяти в файле. Добиться этого можно несколькими способами. Самый простой из них заключается в том, чтобы сохранять записи по одной и вставлять между ними разделители, которые можно будет использовать при загрузке данных, чтобы отделять записи друг от друга. В примере 1.2 показан один из способов воплощения этой идеи.

Пример 1.2. PP4E\Preview\make_db_file.py

Сохраняет в файл базу данных, находящуюся в оперативной памяти, используя собственный формат записи; предполагается, что в данных отсутствуют строки endrec.’, ‘enddb.’ и ‘=>’; предполагается, что база данных является словарем словарей; внимание: применение функции eval может быть опасным — она выполняет строки как программный код; с помощью функции eval() можно также реализовать сохранение словарей-записей целиком; кроме того, вместо вызова print(key,file=dbfile) можно использовать вызов dbfile.write(key + ‘\n’);

dbfilename = ‘people-file’

ENDDB = ‘enddb.’

ENDREC = ‘endrec.’

RECSEP = ‘=>’

def storeDbase(db, dbfilename=dbfilename):

сохраняет базу данных в файлdbfile = open(dbfilename, ‘w’) for key in db: print(key, file=dbfile) for (name, value) in db[key].items(): print(name + RECSEP + repr(value), file=dbfile) print(ENDREC, file=dbfile)

print(ENDDB, file=dbfile) dbfile.close()

def loadDbase(dbfilename=dbfilename):

восстанавливает данные, реконструируя базу данныхdbfile = open(dbfilename) import sys sys.stdin = dbfile db = {} key = input() while key != ENDDB: rec = {} field = input() while field != ENDREC:

name, value = field.split(RECSEP) rec[name] = eval(value) field = input() db[key] = rec key = input() return db

if __name__ == ‘__main__’: from initdata import db storeDbase(db)

Это достаточно сложная программа, отчасти потому, что в ней реализованы обе операции, сохранения и загрузки, а отчасти потому, что эти операции реализованы не самым простым способом. Как будет показано ниже, существуют более простые способы сохранения объектов в файл и чтения их из файла по сравнению с форматированием и парсингом данных вручную.

Однако для реализации простых задач такой подход вполне оправдан. При запуске примера 1.2 как сценария база данных будет сохранена в файл. Он ничего не выводит на экран, но мы можем проверить содержимое файла базы данных в интерактивной оболочке после выполнения сценария внутри IDLE или в окне консоли (так как файл базы данных появится в текущем рабочем каталоге):

\PP4E\Preview> python make_db_file.py

\PP4E\Preview> python

>>> for line in open(‘people-file’):

print(line, end=’’) bob job=>’dev’ pay=>30000 age=>42 name=>’Bob Smith’ endrec.

sue job=>’hdw’ pay=>40000 age=>45 name=>’Sue Jones’ endrec.

tom job=>None pay=>0 age=>50 name=>’Tom’ endrec.

enddb.

Этот файл хранит содержимое базы данных с дополнительными элементами форматирования. Сами данные берутся из тестовой базы данных, созданной модулем, представленным в примере 1.1, который импортируется программным кодом самопроверки в примере 1.2. С точки зрения практического применения, пример 1.2 сам мог бы импортироваться и использоваться для сохранения различных баз данных.

Обратите внимание, что форматирование сохраняемых данных выполняется с помощью функции repr, а обратное преобразование прочитанных данных — с помощью функции eval, которая интерпретирует входную строку как программный код на языке Python. Это позволяет сохранять и воссоздавать такие виды данных, как объект None, но этот способ небезопасен. Не следует использовать функцию eval, если нет уверенности, что база данных не содержит злонамеренный программный код. Однако в нашем случае нет причин для волнений.

Вспомогательные сценарии

Ниже приводятся дополнительные сценарии, которые можно использовать для тестирования. Сценарий в примере 1.3 выполняет загрузку базы данных из файла.

Пример 1.3. PP4E\Preview\dump_db_file.py

from make_db_file import loadDbase

db = loadDbase() for key in db: print(key, ‘=>\n ‘, db[key]) print(db[‘sue’][‘name’])

А сценарий в примере 1.4 загружает базу данных, вносит в нее изменения и сохраняет ее обратно в файл.

Пример 1.4. PP4E\Preview\update_db_file.py

from make_db_file import loadDbase, storeDbase db = loadDbase()

db[‘sue’][‘pay’] *= 1.10 db[‘tom’][‘name’] = ‘Tom Tom’ storeDbase(db)

Ниже приводится пример запуска сценариев dump_db_file.py и update_ db_file.py из командной строки, где видно, что между запусками сценария dump_db_file.py изменяются оклад Сью и имя Тома. Обратите внимание, что после завершения каждого из сценариев данные сохраняются, — это обусловлено тем, что наши объекты просто загружаются и сохраняются в текстовом файле:

\PP4E\Preview> python dump_db_file.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

\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 демонстрируется, как с помощью этого модуля можно сохранять записи в файле.

Пример 1.5. PP4E\Preview\make_db_pickle.py

from initdata import db import pickle

dbfile = open(‘people-pickle’, ‘wb’) # в версии 3.X следует использовать pickle.dump(db, dbfile) # двоичный режим работы с файлами, так как

dbfile.close() # данные имеют тип bytes, а не str

Если запустить этот сценарий, он сохранит всю базу данных (словарь словарей, который создается сценарием из примера 1.1) в файл с именем peoplepickle в текущем рабочем каталоге. В процессе работы модуль pickle преобразовывает объект в строку. В примере 1.6 демонстрируется, как можно реализовать доступ к сохраненной базе данных после ее создания, — достаточно просто открыть файл и передать его модулю pickle, который восстановит объект из последовательного представления.

Пример 1.6. PP4E\Preview\dump_db_pickle.py

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.

Пример 1.7. PP4E\Preview\updatedbpickle.py

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()

Обратите внимание, что после изменения записи в файл сохраняется вся база данных целиком, как и при использовании простого текстового файла; это может занимать продолжительное время, если база данных имеет значительный объем, но мы пока не будем беспокоиться об этом. Ниже приводится пример запуска сценариев dump_db_pickle.py и update db pickle.py как и в предыдущем разделе, измененный оклад Сью и имя Тома сохраняются между вызовами сценариев, потому что записываются обратно в файл (но на этот раз с помощью модуля pickle):

\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 и XMLRPC.

Работа модуля pickle с отдельными записями

Как упоминалось выше, один из потенциальных недостатков примеров, представленных в этом разделе до настоящего момента, состоит в том, что они могут оказаться слишком медленными при работе с очень большими базами данных: так как для изменения единственной записи необходимо загружать и сохранять базу данных целиком, при таком решении значительная часть времени будет тратиться впустую. Мы могли бы избежать этого, предусмотрев сохранение каждой записи базы данных в отдельном файле. Следующие три примера демонстрируют, как это можно реализовать, — сценарий из примера 1.8 сохраняет каждую запись в отдельном файле, где в качестве имени файла используется уникальный ключ записи, к которому добавляется расширение .pkl (он создает файлы bob.pkl, sue.pkl и tom.pkl в текущем рабочем каталоге).

Пример 1.8. PP4E\Preview\make_db_pickle_recs.py

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 для подстановки имен файлов и тем самым для выбора всех файлов с расширением .pkl, присутствующих в текущем каталоге. Чтобы загрузить единственную запись, мы открываем файл этой записи и выполняем преобразование содержимого файла с помощью модуля pickle. Теперь, чтобы получить одну запись, нам необходимо загрузить файл единственной записи, а не всю базу данных.

Пример 1.9. PP4E\Preview\dump_db_pickle_recs.py

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. На этот раз для внесения изменений необходимо извлечь и сохранить единственную запись, а не всю базу данных.

Пример 1.10. PP4E\Preview\update_db_pickle_recs.py

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 и XMLRPC (они также поддерживаются в Python, но являются более тяжеловесными по сравнению с модулем pickle).

Кроме того, если допустить, что файловая система способна хранить необходимое нам количество файлов, реализация, сохраняющая каждую запись в отдельном файле, устраняет необходимость загрузки и сохранения всей базы данных при каждом изменении. Однако, когда действительно необходимо иметь возможность доступа к записям по ключу, можно использовать более высокоуровневый инструмент: модуль shelve.

Модуль shelve автоматически сохраняет и загружает объекты из хранилища, обеспечивающего доступ по ключу. Хранилища напоминают словари; их необходимо открывать, и они автоматически сохраняются после завершения программы. Поскольку хранилища обеспечивают доступ к хранимым записям по ключу, отпадает необходимость создавать отдельные файлы для каждой записи — модуль shelve автоматически разделяет записи и извлекает и обновляет только те записи, к которым осуществляется доступ или которые изменяются. Таким образом модуль shelve обеспечивает решение, напоминающее решение, сохраняющее каждую запись в отдельном файле, но более простое в использовании.

Интерфейс модуля shelve так же прост, как и интерфейс модуля pickle: хранилища, создаваемые модулем shelve, идентичны словарям с дополнительными методами open и close. В программном коде объекты хранилищ действительно выглядят, как словари, содержимое которых сохраняется после завершения программы. А все операции по отображению содержимого хранилища в файл и из файла выполняются интерпретатором Python. Например, сценарий в примере 1.11 демонстрирует, как можно сохранить объекты из словаря в хранилище, созданном с помощью модуля shelve.

Пример 1.11. PP4E\Preview\make_db_shelve.py

from initdata import bob, sue

import shelve

db = shelve.open(‘people-shelve’)

db[‘bob’] = bob

db[‘sue’] = sue db.close()

Этот сценарий создаст в текущем каталоге один или более файлов, имена которых начинаются с префикса people-shelve (в ОС Windows, под управлением Python 3.1, сценарий создаст файлы people-shelve.bak, people-shelve.dat и people-shelve.dir). Вы не должны удалять эти файлы (они составляют вашу базу данных!), а чтобы получить доступ к этому хранилищу в других сценариях, необходимо использовать то же самое имя базы. Сценарий в примере 1.12, например, повторно открывает хранилище и последовательно извлекает хранящиеся в нем записи.

Пример 1.12. PP4E\Preview\dump_db_shelve.py

import shelve

db = shelve.open(‘people-shelve’)

for key in db:

print(key, ‘=>\n ‘, db[key])

print(db[‘sue’][‘name’])

db.close()

Мы по-прежнему имеем словарь словарей, но на этот раз словарем верхнего уровня является хранилище, отображаемое в файл. Всякий раз, когда происходит обращение к элементу в хранилище, модуль shelve выполняет необходимые операции с файловой системой, обеспечивающей доступ по ключу, и использует модуль pickle для сериализации и десериализации объектов. Однако, с точки зрения программиста, хранилище — это всего лишь словарь, обладающий возможностью сохраняться между вызовами программы. Сценарий в примере 1.13 демонстрирует, как можно реализовать изменение данных в хранилище.

Пример 1.13. PP4E\Preview\update_db_shelve.py

from initdata import tom

import shelve

db = shelve.open(‘people-shelve’)

sue = db[‘sue’] # извлекает объект sue

sue[‘pay’] *= 1.50

db[‘sue’] = sue # изменяет объект sue

db[‘tom’] = tom # добавляет новую запись

db.close()

Обратите внимание, что в этом примере сначала по ключу извлекается объект sue, затем он изменяется в памяти и снова сохраняется в хранилище по ключу. Так действуют хранилища по умолчанию, однако более совершенные системы хранения, такие как ZODB, о которой рассказывается в главе 17, могут действовать иначе. Как мы узнаем позднее, метод shelve.open в подобных системах имеет дополнительный именованный аргумент writeback. Если в этом аргументе передать значение True, все загруженные записи будут сохраняться в кэше и автоматически записываться обратно в файл при закрытии хранилища. Благодаря этому не требуется вручную записывать изменения обратно в хранилище, но при этом увеличивается потребление памяти, а сама операция закрытия может занимать продолжительное время.

Обратите также внимание на необходимость явного закрытия хранилища. Нам не требуется указывать флаги режимов в вызове метода shelve. open (по умолчанию он создает новое хранилище, если это необходимо, и открывает существующее хранилище для чтения и записи), однако некоторые механизмы, обеспечивающие доступ к содержимому файлов по ключу, требуют вызова метода close, чтобы сбросить на диск выходные буферы с изменениями.

Наконец, ниже приводится пример запуска сценариев, опирающихся на использование модуля shelve, которые создают, изменяют и извлекают записи. Записи по-прежнему реализованы как словари, но сама база данных теперь является хранилищем, напоминающим словарь, который автоматически сохраняет свое содержимое в файле:

\PP4E\Preview> python make_db_shelve.py

\PP4E\Preview> python dump_db_shelve.py bob =>

{‘pay’: 30000, ‘job’: ‘dev’, ‘age’: 42, ‘name’: ‘Bob Smith’} sue =>

{‘pay’: 40000, ‘job’: ‘hdw’, ‘age’: 45, ‘name’: ‘Sue Jones’} Sue Jones

\PP4E\Preview> python update_db_shelve.py

\PP4E\Preview> python dump_db_shelve.py bob =>

{‘pay’: 30000, ‘job’: ‘dev’, ‘age’: 42, ‘name’: ‘Bob Smith’} sue =>

{‘pay’: 60000.0, ‘job’: ‘hdw’, ‘age’: 45, ‘name’: ‘Sue Jones’} tom =>

{‘pay’: 0, ‘job’: None, ‘age’: 50, ‘name’: ‘Tom’}

Sue Jones

После выполнения сценариев update_db_shelve.py и dump_db_shelve.py можно заметить, что была добавлена новая запись с ключом tom и на 50 процентов был увеличен оклад Сью. Эти изменения сохраняются между запусками сценариев, потому что записи-словари отображаются модулем shelve во внешний файл хранилища. (Этот сценарий особенно хорош для Сью — у нее могло бы появиться желание почаще запускать этот сценарий с помощью планировщика cron в Unix или поместив его в папку Автозагрузка (Startup) с помощью msconfig в Windows…)

Что в имени тебе моем?

Удивительно, но часто остается тайной, что свое название язык Python получил благодаря британскому телевизионному комедийному сериалу «Monty Pythons Flying Circus», появившемуся на экранах в 1970-х годах. Фольклор сообщества Python утверждает, что Гвидо ван Россум (Guido van Rossum), создатель Python, смотрел повторные показы этого сериала как раз в то время, когда подбирал название для нового языка программирования, который он разрабатывал. И, как говорят в шоу-бизнесе: «остальное уже история».

Такая наследственность часто является причиной появления в примерах и обсуждениях ссылок на комедийную игру. Например, в сценариях часто используется имя Brian; словами spam (консервированный фарш), lumberjack (лесоруб) и shrubbery (кустарник), получившими специальное значение, называют пользователей Python; а презентации иногда называют «испанской инквизицией». Как правило, когда пользователь Python начинает произносить фразы, не имеющие отношения к реальности, они оказываются заимствованными из сериала или фильмов с участием персонажа Monty Python. Некоторые из этих фраз могут встретиться даже в этой книге. Конечно, чтобы писать программы на языке Python, необязательно бежать и брать в прокате «The Meaning of Life» или «The Holy Grail», но и хуже от этого не будет.

Имя «Python» быстро прижилось, тем не менее его заимствование стало причиной интересных курьезов. Например, когда в 1994 году возникла телеконференция по Python, comp.lang.python, первые несколько недель она практически полностью была оккупирована желающими обсуждать темы, касающиеся телевизионной постановки. Позднее специальное приложение к журналу «Linux Journal», касающееся Python, стало сопровождаться фотографией Гвидо, облаченного в обязательную «красную форму».

В списке рассылки Python все еще появляются случайные письма от поклонников сериала. Например, в одном письме невинно предлагалось обмениваться сценариями с участием персонажа Monty Python. Если бы автор письма понимал назначение форума, то хотя бы указал, могут ли они выполняться в разных операционных системах или нет.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий