Журнал ВРМ World

Мировая история развития технологий управления эффективностью бизнеса – обзоры зарубежных публикаций

Управление персистентностью Python

В статье рубрики рассказывается о сериализации объектов Python, или
Питоновском консервировании объектов, - очень интересной реализации концепции
персистентности.

Используйте сериализацию для хранения объектов Python

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

Что такое персистентность?

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

Например, вы могли бы хранить данные своего объекта в какой-нибудь разновидности форматированного текстового файла, как CSV-файл. Или вы могли бы использовать реляционную базу данных, такую как Gadfly, MySQL, PostgreSQL или DB2. Эти файловые форматы хорошо определены, а Python обладает надежными интерфейсами для всех этих механизмов хранения.

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

Тем самым, для некоторых приложений применение реляционных баз данных, возможно, не является идеальным. В частности, из-за того, что реляционные базы данных не понимают объекты. Наоборот, они навязывают свои собственные системы типов и свои собственные модели данных для отношений (таблиц), каждая из которых содержит набор записей (рядов), состоящих из фиксированного числа статически типизированных полей (столбцов). Если объектная модель для вашего приложения не преобразовывается легко в реляционную модель, у вас будут определенные сложности при преобразовании своего объекта в записи и обратно. Эту проблему часто именуют несогласованностью интерфейсов (impedence-mismatch problem).

Персистентность объектов

Если вы хотите прозрачно сохранять объекты Python, не теряя их тождественность (identity), тип и так далее, вам потребуется некоторая форма сериализации - процесса, который превращает произвольно сложные объекты в текстовое или бинарное представление этих объектов. Таким же образом вы должны быть в состоянии восстановить эту сериализованную форму объекта обратно в объект, такой же, как и оригинал. На Python процесс сериализации называется консервированием (pickling), и вы можете законсервировать/восстановить свои объекты в/из строки, файла на диске или любого объекта, подобного файлу. Ниже мы детально рассмотрим консервирование.

Предположим, что вам нравится идея хранить все как объект, избегая накладных расходов на преобразование объектов в какую-либо разновидность хранения, не основанную на объектах. Файлы консервированных объектов (pickle files) дают такую возможность, но иногда вам потребуется что-нибудь более надежное и расширяемое, чем просто эти файлы. Например, само по себе консервирование не решает проблему наименования и обнаружения файлов консервированных объектов, как и не поддерживает одновременный доступ к перманентным объектам (persistent objects). За такими свойствами обратитесь к чему-нибудь вроде ZOBD, объектной базе данных Z для Python. ZOBD - это надежная многопользовательская объектно-ориентированная система баз данных, способная хранить и управлять произвольно сложными объектами Python, включая поддержку транзакций и управление параллельным доступом (чтобы скачать ZOBD, см. Ресурсы). Весьма интересно, что даже ZOBD полагается на Питоновские встроенные возможности сериализации, и, чтобы эффективно использовать ZOBD, у вас должно быть полное понимание консервирования.

Другой интересный подход к решению проблемы персистентности, первоначально реализованный на Java, называется Prevayler (ссылку на статью о Prevayler, опубликованную на developerWorks, см. в Ресурсах). Недавно группа программистов Python перенесла Prevayler на Python, и итог их трудов под названием PyPerSyst находится на SourceForge (ссылка на этот проект приведена в Ресурсах). Концепция Prevayler/PyPerSyst также строится на встроенных возможностях сериализации языков Java и Python. PyPerSyst держит все систему объектов в памяти и обеспечивает восстановление системы после аварии, время от времени консервируя на диск мгновенное состояние (snapshot) системы и поддерживая лог команд, которые могут повторно применяться к последнему снимку. Но, несмотря на то, что приложения, использующие PyPerSyst, по этой причине ограничены доступной памятью (RAM), преимущество состоит в том, что полностью загруженная в память система объектов, "родных" для языка, является чрезвычайно быстродействующей, и ее гораздо легче реализовать, чем ту, которая, подобно ZOBD, разрешает больше объектов, чем можно одновременно держать в памяти.

После того, как мы кратко коснулись различных способов хранения перманентных объектов, давайте детально рассмотрим процесс консервирования. Хотя основной интерес состоит в нахождении путей поддержания объектов Python без обязательного преобразования их в какой-нибудь другой формат, многие проблемы по-прежнему остались неразрешенными: как эффективно законсервировать и восстановить как простые, так и сложные объекты, включая экземпляры классов, определенных пользователем (custom classes); как поддерживать ссылки на объекты, в том числе циклические и рекурсивные; и как управлять изменениями описания класса, не создавая проблем с ранее законсервированными экземплярами. Все эти вопросы будут освещены ниже при рассмотрении Питоновских возможностей сериализации.

Суп из консервированного Python

Поддержка Питоновского консервирования проистекает из модуля pickle и его "собрата" сPickle. Второй модуль был написан на C, чтобы обеспечить лучшую производительность, и рекомендован для большинства приложений. Мы продолжим обсуждение pickle, но на самом деле в примерах будет использоваться сPickle. Поскольку большинство примеров будет показано из оболочки Python, давайте начнем с демонстрации того, как импортировать сPickle, сохраняя возможность ссылаться на него как pickle:


>>> import cPickle as pickle

После того, как мы импортировали этот модуль, давайте посмотрим на интерфейс модуля pickle. Модуль pickle предоставляет следующие пары функций: dumps(object) возвращает строку, содержащую объект в формате консервирования; loads(string) возвращает объект, находящийся в строке консервирования; dump(object, file) записывает объект в файл, который может быть как действительно физическим файлом, так и любым подобным файлу объектом, имеющим метод write(), который принимает один строчный аргумент; load(file) возвращает объект, содержащийся в файле консервированного объекта.

По умолчанию dumps() и dump() создают консервированные объекты, используя печатаемое представление ASCII. Обе функции имеют конечный факультативный аргумент, который, если True, устанавливает, что консервированные объекты будут создаваться с использованием более быстрого и меньшего по размеру бинарного представления. Функции loads() и load() автоматически определяют, находится ли консервированный объект в бинарном или текстовом формате.

Листинг 1 иллюстрирует интерактивную сессию с использованием описанных функций dumps() и loads():

Листинг 1. Иллюстрация dumps()и loads()


Welcome To PyCrust 0.7.2 - The Flakiest Python Shell
Sponsored by Orbtech - Your source for Python programming expertise.
Python 2.2.1 (#1, Aug 27 2002, 10:22:32)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux-i386
Type "copyright", "credits" or "license" for more information.
>>> import cPickle as pickle
>>> t1 = ('this is a string', 42, [1, 2, 3], None)
>>> t1
('this is a string', 42, [1, 2, 3], None)
>>> p1 = pickle.dumps(t1)
>>> p1
"(S'this is a string'\nI42\n(lp1\nI1\naI2\naI3\naNtp2\n."
>>> print p1
(S'this is a string'
I42
(lp1
I1
aI2
aI3
aNtp2
.
>>> t2 = pickle.loads(p1)
>>> t2
('this is a string', 42, [1, 2, 3], None)
>>> p2 = pickle.dumps(t1, True)
>>> p2
'(U\x10this is a stringK*]q\x01(K\x01K\x02K\x03eNtq\x02.'
>>> t3 = pickle.loads(p2)
>>> t3
('this is a string', 42, [1, 2, 3], None)

Заметьте, что расшифровать текстовый формат консервированного объекта (text pickle format) не слишком сложно. Действительно, все задействованные условные обозначения документированы в модуле pickle. Также следует отметить, что для простых объектов, использующихся в нашем примере, использование бинарного формата консервированного объекта (binary pickle format) не привело к большому выигрышу в размере. Однако, в реальной системе со сложными объектами, при использовании бинарного формата вы получите заметное улучшение в размере и скорости.

Далее мы рассмотрим некоторые примеры, в которых dump() и load() используются для работы с файлами и объектами, подобными файлам. Эти функции действуют во многом аналогично dumps() и loads(), с которыми мы только что познакомились - с одной дополнительной возможностью - функция dump() позволяет выгружать несколько объектов один за другим в один и тот же файл. Последующие вызовы load() будут извлекать эти объекты в том же самом порядке. Листинг 2 демонстрирует эту возможность в действии:

Листинг 2. Пример dump() и load()


>>> a1 = 'apple'
>>> b1 = {1: 'One', 2: 'Two', 3: 'Three'}
>>> c1 = ['fee', 'fie', 'foe', 'fum']
>>> f1 = file('temp.pkl', 'wb')
>>> pickle.dump(a1, f1, True)
>>> pickle.dump(b1, f1, True)
>>> pickle.dump(c1, f1, True)
>>> f1.close()
>>> f2 = file('temp.pkl', 'rb')
>>> a2 = pickle.load(f2)
>>> a2
'apple'
>>> b2 = pickle.load(f2)
>>> b2
{1: 'One', 2: 'Two', 3: 'Three'}
>>> c2 = pickle.load(f2)
>>> c2
['fee', 'fie', 'foe', 'fum']
>>> f2.close()

Мощь консервированных объектов

До сих пор освещались основы консервирования. В этом разделе мы рассмотрим некоторые нетривиальные проблемы, которые возникают при консервировании сложных объектов, включая экземпляры классов, определяемых пользователем. К счастью, Python, как вы увидите, довольно легко расправляется с такими задачами.

Переносимость

Консервированные объекты переносимы через пространство и время. Другими словами, формат файла консервированного объекта не зависит от архитектуры машины, что означает, что вы можете создать консервированный объект, например, под Linux и отправить его в программу Python, исполняемую под Windows и Mac OS. А если вы перейдете на более новую версию Python, вам не нужно беспокоиться о том, что вы, возможно, потеряете существующие консервированные объекты. Разработчики Python предусмотрели, что формат консервированного объекта будет совместим с более ранними версиями Python. К тому же подробная информация о текущем и поддерживаемом форматах предоставляется с модулем pickle:

Листинг 3. Получение информации о поддерживаемых форматах


>>> pickle.format_version
'1.3'
>>> pickle.compatible_formats
['1.0', '1.1', '1.2']

Многочисленные ссылки, один и тот же объект

Переменная на Python- это ссылка на объект. Вы можете иметь многочисленные переменные, ссылающиеся на один и тот же объект. Оказывается, что Python не испытывает никаких сложностей при поддержании этого поведения и с консервированными объектами, как следует из Листинга 4:

Листинг 4. Поддержание объектных ссылок


>>> a = [1, 2, 3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]
>>> c = pickle.dumps((a, b))
>>> d, e = pickle.loads(c)
>>> d
[1, 2, 3, 4]
>>> e
[1, 2, 3, 4]
>>> d.append(5)
>>> d
[1, 2, 3, 4, 5]
>>> e
[1, 2, 3, 4, 5]

Циклические и рекурсивные ссылки

Поддержка объектных ссылок, которая была только что продемонстрирована, простирается и на циклические ссылки, когда два объекта содержат ссылки друг друга, и рекурсивные ссылки, когда объект содержит ссылку на самого себя. Следующие два листинга подчеркивают эту возможность. Давайте рассмотрим сначала рекурсивную ссылку:

Листинг 5. Рекурсивная ссылка


>>> l = [1, 2, 3]
>>> l.append(l)
>>> l
[1, 2, 3, [...]]
>>> l[3]
[1, 2, 3, [...]]
>>> l[3][3]
[1, 2, 3, [...]]
>>> p = pickle.dumps(l)
>>> l2 = pickle.loads(p)
>>> l2
[1, 2, 3, [...]]
>>> l2[3]
[1, 2, 3, [...]]
>>> l2[3][3]
[1, 2, 3, [...]]

А теперь изучим циклическую ссылку:

Листинг 6. Циклическая ссылка


>>> a = [1, 2]
>>> b = [3, 4]
>>> a.append(b)
>>> a
[1, 2, [3, 4]]
>>> b.append(a)
>>> a
[1, 2, [3, 4, [...]]]
>>> b
[3, 4, [1, 2, [...]]]
>>> a[2]
[3, 4, [1, 2, [...]]]
>>> b[2]
[1, 2, [3, 4, [...]]]
>>> a[2] is b
1
>>> b[2] is a
1
>>> f = file('temp.pkl', 'w')
>>> pickle.dump((a, b), f)
>>> f.close()
>>> f = file('temp.pkl', 'r')
>>> c, d = pickle.load(f)
>>> f.close()
>>> c
[1, 2, [3, 4, [...]]]
>>> d
[3, 4, [1, 2, [...]]]
>>> c[2]
[3, 4, [1, 2, [...]]]
>>> d[2]
[1, 2, [3, 4, [...]]]
>>> c[2] is d
1
>>> d[2] is c
1

Заметьте, что мы получаем хоть и неприметно, но существенно различные результаты, если мы консервируем каждый объект отдельно, а не совместно внутри записи, как показано в Листинге 7:

Листинг 7. Консервирование по отдельности по сравнению с совместным консервированием внутри записи


>>> f = file('temp.pkl', 'w')
>>> pickle.dump(a, f)
>>> pickle.dump(b, f)
>>> f.close()
>>> f = file('temp.pkl', 'r')
>>> c = pickle.load(f)
>>> d = pickle.load(f)
>>> f.close()
>>> c
[1, 2, [3, 4, [...]]]
>>> d
[3, 4, [1, 2, [...]]]
>>> c[2]
[3, 4, [1, 2, [...]]]
>>> d[2]
[1, 2, [3, 4, [...]]]
>>> c[2] is d
0
>>> d[2] is c
0

Эквивалентны, но не всегда идентичны

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

Листинг 8. Восстановленные объекты как копии оригиналов


>>> j = [1, 2, 3]
>>> k = j
>>> k is j
1
>>> x = pickle.dumps(k)
>>> y = pickle.loads(x)
>>> y
[1, 2, 3]
>>> y == k
1
>>> y is k
0
>>> y is j
0
>>> k is j
1

В то же время мы видели, что Python способен поддерживать ссылки между объектами, которые консервируются как блок (unit). Однако, мы также узнали, что раздельные вызовы dump() лишают Python способности поддерживать ссылки на объекты вне консервируемого блока. Python, наоборот, создает копию объекта, на который ссылаются, и хранит его с консервируемым элементом (item). Для приложения, которое консервирует и восстанавливает иерархию одиночного объекта, это не является проблемой. Но это то, о чем не стоит забывать в других ситуациях.

Также нужно отметить возможность, позволяющую консервированным объектам поддерживать ссылки друг на друга, если все они законсервированы в один и тот же файл. Модули pickle и cPickle предоставляют объекты Pickler (и соответствующий Unpickler), которые могут отслеживать (track) объекты, которые уже были законсервированы. При использовании Pickler, разделяемые и циклические ссылки будут законсервированы по ссылке, а не по значению:

Листинг 9. Поддержание ссылок среди отдельно консервированных объектов


>>> f = file('temp.pkl', 'w')
>>> pickler = pickle.Pickler(f)
>>> pickler.dump(a)
<cPickle.Pickler object at 0x89b0bb8>
>>> pickler.dump(b)
<cPickle.Pickler object at 0x89b0bb8>
>>> f.close()
>>> f = file('temp.pkl', 'r')
>>> unpickler = pickle.Unpickler(f)
>>> c = unpickler.load()
>>> d = unpickler.load()
>>> c[2]
[3, 4, [1, 2, [...]]]
>>> d[2]
[1, 2, [3, 4, [...]]]
>>> c[2] is d
1
>>> d[2] is c
1

Неконсервируемые объекты

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

Листинг 10, Результат попытки законсервировать файловый объект


>>> f = file('temp.pkl', 'w')
>>> p = pickle.dumps(f)
Traceback (most recent call last):
  File "<input>", line 1, in ?
  File "/usr/lib/python2.2/copy_reg.py", line 57, in _reduce
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle file objects

Экземпляры класса

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

Когда экземпляры класса восстанавливаются, обычно их метод __init__() не вызывается заново. Наоборот, Python создает родовой экземпляр класса, использует атрибуты экземпляра, которые были законсервированы, и устанавливает атрибут экземпляра __class__ так, чтобы он указывал на оригинальный класс.

Классы нового стиля (new-style class), появившиеся в Python 2.2, опираются на слегка отличный механизм восстановления. Несмотря на то, что результат этого процесса по существу такой же, как и с классами старого стиля, Python использует функцию _reconstructor() модуля copy_reg, чтобы восстановить экземпляры классов нового стиля.

Если вы хотите изменить поведение консервирования по умолчанию для экземпляров класса нового или старого стиля, вы можете описать специальные методы класса, а именно: __getstate__() и __setstate__ () - которые будут вызываться Python во время сохранения и восстановления информации о состоянии для экземпляров этого класса. В следующих разделах будут приведены некоторые примеры использования этих специальных методов.

Пока давайте рассмотрим экземпляр простого класса. Для начала мы создали модуль Python persist.py, в котором содержится следующее описание класса нового стиля:

Листинг 11. Описание класса нового стиля


class Foo(object):

    def __init__(self, value):
        self.value = value

Теперь мы можем законсервировать экземпляр Foo и изучить его представление:

Листинг 12. Консервирование экземпляра Foo


>>> import cPickle as pickle
>>> from Orbtech.examples.persist import Foo
>>> foo = Foo('What is a Foo?')
>>> p = pickle.dumps(foo)
>>> print p
ccopy_reg
_reconstructor
p1
(cOrbtech.examples.persist
Foo
p2
c__builtin__
object
p3
NtRp4
(dp5
S'value'
p6
S'What is a Foo?'
sb.
>>>

Как мы видим, и имя класса, Foo, и полностью квалифицированное имя модуля, Orbtech.examples.persist, хранятся в консервированном объекте. Если бы мы законсервировали этот экземпляр в файл и восстановили бы его позже (или на другой машине), Python попытался бы импортировать модуль Orbtech.examples.persist, и если бы не смог это сделать, возбудил бы исключение. Подобные ошибки случились бы, если бы мы переименовали класс, модуль или переместили модуль в другой каталог.

Ниже приведена ошибка, которую выдаст Python, если мы переименуем класс Foo, а затем попытаемся загрузить ранее законсервированный экземпляр Foo:

Листинг 13. Попытка загрузить законсервированный экземпляр переименованного класса Foo


>>> import cPickle as pickle
>>> f = file('temp.pkl', 'r')
>>> foo = pickle.load(f)
Traceback (most recent call last):
  File "<input>", line 1, in ?
AttributeError: 'module' object has no attribute 'Foo'

Подобная ошибка возникнет, если мы переименуем модуль persist.py:

Листинг 14. Попытка загрузить консервированный экземпляр переименованного модуля persist.py


>>> import cPickle as pickle
>>> f = file('temp.pkl', 'r')
>>> foo = pickle.load(f)
Traceback (most recent call last):
  File "<input>", line 1, in ?
ImportError: No module named persist

Ниже, в разделе Эволюция схемы, мы увидим, как управлять изменениями такого вида, не разрушая существующие консервированные объекты.

Специальные методы состояния

Ранее мы упоминали о том, что несколько типов объектов, как, например, файловые объекты, не могут быть законсервированы. Один из способов управлять атрибутами экземпляра, которые не являются консервируемыми объектами, - это воспользоваться специальными методами, доступными для модифицирования состояния экземпляра класса: __getstate__() и __setstate__(). Ниже приведен пример нашего класса , который мы модифицировали, чтобы управлять атрибутом файлового объекта:

Листинг 15. Обработка атрибутов неконсервируемого экземпляра


class Foo(object):

    def __init__(self, value, filename):
        self.value = value
        self.logfile = file(filename, 'w')

    def __getstate__(self):
        """Return state values to be pickled."""
        f = self.logfile
        return (self.value, f.name, f.tell())

    def __setstate__(self, state):
        """Restore state from the unpickled state values."""
        self.value, name, position = state
        f = file(name, 'w')
        f.seek(position)
        self.logfile = f

Когда экземпляр Foo будет законсервирован, Python законсервирует только те значения, которые возвращены в него при вызове метода __getstate__() этого экземпляра. Подобным образом во время восстановления Python передаст в метод __setstate__() этого экземпляра восстановленные значения в качестве аргумента. Внутри метода _setstate_() мы можем воссоздать файловый объект, опираясь на имя и информацию о положении, которые мы законсервировали, и присвоить файловый объект атрибуту logfile этого экземпляра.

Эволюция схемы

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

В этом разделе мы рассмотрим, как предупреждать и обходить общие проблемы. Поскольку код экземпляра класса не законсервирован, вы можете добавлять, изменять или удалять методы, не влияя на существующие законсервированные экземпляры. По той же причине вы можете не беспокоиться об атрибутах класса. Но вы непременно должны гарантировать, что модуль кода, содержащий описание класса, доступен в восстанавливающей среде. И вы обязаны предусмотреть те изменения, которые могут вызвать проблемы при восстановлении: изменение имени класса, добавление или удаление атрибутов экземпляра и изменение имени или местонахождения модуля описания класса.

Изменение имени класса

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

Листинг 16. Изменение имени класса: способ добавления в первоначальное описание класса:


def __setstate__(self, state):
    self.__dict__.update(state)
    self.__class__ = NewClassName

Когда существующие экземпляры будут восстанавливаться, Python будет отыскивать первоначальное описание класса, будет вызван метод __setstate__(), и новому описанию класса будет назначен атрибут __class__ экземпляра. После того, как вы убедились, что все существующие экземпляры были восстановлены, обновлены и заново законсервированы, можете удалять из исходного модуля старое описание класса.

Добавление и удаление атрибутов

Напомним еще раз, специальные методы класса __getstate__() и __setstate__() дают нам контроль над состоянием каждого экземпляра и возможность обрабатывать изменения, внесенные в атрибуты экземпляра. Рассмотрим описание простого класса, в которое мы добавим и из которого удалим атрибуты. Ниже приведено первоначальное описание:

Листинг 17. Первоначальное описание класса


class Person(object):

    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname

Предположим, что мы создали и законсервировали экземпляры класса Person, а теперь решили, что в действительности мы просто хотим хранить один атрибут полного имени (name), а не выделять имя (first name) и фамилию (last name). Ниже показано, как изменить описание класса, которое переместит ранее законсервированные экземпляры в новое описание:

Листинг 18. Новое описание класса


class Person(object):

    def __init__(self, fullname):
        self.fullname = fullname

    def __setstate__(self, state):
        if 'fullname' not in state:
            first = ''
            last = ''
            if 'firstname' in state:
                first = state['firstname']
                del state['firstname']
            if 'lastname' in state:
                last = state['lastname']
                del state['lastname']
            self.fullname = " ".join([first, last]).strip()
        self.__dict__.update(state)

В этом примере мы добавили новый атрибут fullname и удалили два существующих атрибута: firstname и lastname. Когда ранее законсервированный экземпляр будет восстанавливаться, его предварительно законсервированное состояние будет передано в __setstate__() в качестве словаря, который будет включать значения для атрибутов firstname и lastname. Тогда мы комбинируем эти значения и назначаем их новому атрибуту fullname. Одновременно мы удаляем старые атрибуты из словаря состояния. После того, как все ранее законсервированные экземпляры будут обновлены и заново законсервированы, мы можем удалить метод __setstate__() из описания класса.

Модификации модуля

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

Заключение

Персистентность объектов зависит от возможностей сериализации используемого языка программирования. Для объектов Python под этим подразумевается консервирование (pickling). Питоновские законсервированные объекты являются прочным и надежным основанием для эффективного управления персистентностью объектов Python. Ниже, в разделе Ресурсы, вы найдете информацию о системах, которые построены на Питоновской возможности консервирования.

Ресурсы

Об авторе

Патрик О'Брайен - программист Python, консультант и преподаватель. Он автор PyCrust и разработчик проекта PythonCard. Совсем недавно Патрик руководил группой PyPerSyst, которая переносила Prevayler на Python. Сейчас он продолжает вести этот проект, но для новой интересной области. За более подробной информацией о Патрике и его работе обращайтесь на Web-сайт Orbtech или пишите ему на pobrien@orbtech.com.

Автор: Патрик О'Брайен (Patrick O'Brien), программист Python, Orbtech