Python/Объектно-ориентированное программирование на Python
Введение
[править]Принципы ООП
[править]Согласно Алану Кэю — автору языка программирования Smalltalk — объектно-ориентированным может называться язык, построенный с учетом следующих принципов[1]:
- Все данные представляются объектами
- Программа является набором взаимодействующих объектов, посылающих друг другу сообщения
- Каждый объект имеет собственную часть памяти и может иметь в составе другие объекты
- Каждый объект имеет тип
- Объекты одного типа могут принимать одни и те же сообщения (и выполнять одни и те же действия)
Объекты, типы и классы
[править]Определение класса
[править]Для определения класса используется оператор class
:
class имя_класса(надкласс1, надкласс2, ...):
# определения атрибутов и методов класса
У класса могут быть базовые (родительские) классы (надклассы), которые, если они есть, указываются в скобках после имени определяемого класса.
Минимально возможное определение класса выглядит так:
class A:
pass
В терминологии Python члены класса называются атрибутами, функции класса — методами, а поля класса — свойствами (или просто атрибутами).
Определения методов аналогичны определениям функций, но (за некоторыми исключениями, о которых ниже) методы всегда имеют первый аргумент, называемый по общепринятому соглашению self
:
class A:
def m1(self, x):
# блок кода метода
Определения атрибутов — это обычные операторы присваивания, которые связывают некоторые значения с именами атрибутов.
class A:
attr1 = 2 * 2
В языке Python класс не является чем-то статическим, поэтому добавить атрибуты можно и после определения:
class A:
pass
def my_method(self, x):
return x * x
A.m1 = my_method
A.attr1 = 2 * 2
Создание экземпляра
[править]Для создания объекта — экземпляра класса (то есть, инстанцирования класса), достаточно вызвать класс по имени и задать параметры конструктора:
class Point:
def __init__(self, x, y, z):
self.coord = (x, y, z)
def __repr__(self):
return "Point(%s, %s, %s)" % self.coord
>>> p = Point(0.0, 1.0, 0.0)
>>> p
Point(0.0, 1.0, 0.0)
Переопределив классовый метод __new__
, можно управлять процессом создания экземпляра. Этот метод вызывается до метода __init__
и должен вернуть новый экземпляр либо None
(в последнем случае будет вызван __new__
родительского класса). Метод __new__
используется для управления созданием неизменчивых (immutable) объектов, управления созданием объектов в случаях, когда __init__
не вызывается, например, при десериализации (unpickle). Следующий код демонстрирует один из вариантов реализации шаблона Одиночка:
>>> class Singleton(object):
obj = None # Атрибут для хранения единственного экземпляра
def __new__(cls, *dt, **mp): # класса Singleton.
if cls.obj is None: # Если он еще не создан, то
cls.obj = object.__new__(cls, *dt, **mp) # вызовем __new__ родительского класса
return cls.obj # вернем синглтон
...
>>> obj = Singleton()
>>> obj.attr = 12
>>> new_obj = Singleton()
>>> new_obj.attr
12
>>> new_obj is obj # new_obj и obj - это один и тот же объект
True
Конструктор, инициализатор, деструктор
[править]Специальные методы вызываются при создании экземпляра класса (конструктор), при инициализировании экземпляра класса (инициализатор) и при удалении класса (деструктор). В языке Python реализовано автоматическое управление памятью, поэтому конструктор и деструктор требуются достаточно редко, для ресурсов, требующих явного освобождения.
Следующий класс имеет конструктор, инициализатор и деструктор:
class Line:
def __new__(cls): # Конструктор
return super(Line, cls).__new__(cls)
def __init__(self, p1, p2): # Инициализатор
self.line = (p1, p2)
def __del__(self): # Деструктор
print("Удаляется линия %s - %s" % self.line)
>>> l = Line((0.0, 1.0), (0.0, 2.0))
>>> del l
Удаляется линия (0.0, 1.0) - (0.0, 2.0)
>>>
В момент вызова деструктора (например, по завершении программы) среда исполнения может быть уже достаточно «истощённой»Шаблон:Что, поэтому в деструкторе следует делать только самое необходимое. Кроме того, не обработанные в деструкторе исключения игнорируются.
Время жизни объекта
[править]Обычно время жизни объекта, определённого в программе на Python, не выходит за рамки времени выполнения процесса этой программы.
Для преодоления этого ограничения объект можно сохранить, а после — восстановить. Как правило, при записи объекта производится его сериализация, а при чтении — десериализация.
>>> import shelve
>>> s = shelve.open("somefile.db")
>>> s['myobject'] = [1, 2, 3, 4, 'свечка']
>>> s.close()
>>> import shelve
>>> s = shelve.open("somefile.db")
>>> print s['myobject']
[1, 2, 3, 4, '\xd1\x81\xd0\xb2\xd0\xb5\xd1\x87\xd0\xba\xd0\xb0']
Инкапсуляция и доступ к свойствам
[править]Инкапсуляция является одним из ключевых понятий ООП. Все значения в Python являются объектами, инкапсулирующими код (методы) и данные и предоставляющими пользователям общедоступный интерфейс. Методы и данные объекта доступны через его атрибуты.
Сокрытие информации о внутреннем устройстве объекта выполняется в Python на уровне соглашения между программистами о том, какие атрибуты относятся к общедоступному интерфейсу класса, а какие — к его внутренней реализации. Одиночное подчеркивание в начале имени атрибута говорит о том, что атрибут не предназначен для использования вне методов класса (или вне функций и классов модуля), однако, атрибут все-таки доступен по этому имени. Два подчеркивания в начале имени дают несколько большую защиту: атрибут перестает быть доступен по этому имени. Последнее используется достаточно редко.
Есть существенное отличие между такими атрибутами и личными (private) членами класса в таких языках как C++ или Java: атрибут остается доступным, но под именем вида _ИмяКласса__ИмяАтрибута
, а при каждом обращении Python
будет модифицировать имя в зависимости от того, через экземпляр какого класса происходит обращение к атрибуту. Таким образом, родительский и дочерний классы могут иметь атрибут с именем, например, «__f», но не будут мешать друг другу.
>>> class parent(object):
def __init__(self):
self.__f = 2
def get(self):
return self.__f
....
>>> class child(parent):
def __init__(self):
self.__f = 1
parent.__init__(self)
def cget(self):
return self.__f
....
>>> c = child()
>>> c.get()
2
>>> c.cget()
1
>>> c.__dict__
{'_child__f': 1, '_parent__f': 2} # на самом деле у объекта "с" два разных атрибута
Особым случаем является наличие двух подчеркиваний в начале и в конце имени атрибута. Они используются для специальных свойств и функций класса (например, для перегрузки операции). Такие атрибуты доступны по своему имени, но их использование зарезервировано для специальных атрибутов, изменяющих поведение объекта.
Доступ к атрибуту может быть как прямой:
class A(object):
def __init__(self, x): # атрибут получает значение в инициализаторе
self.x = x
a = A(5)
print a.x
>>> 5
Так и с использованием свойств с заданными методами для получения, установки и удаления атрибута:
class A(object):
def __init__(self, x):
self._x = x
def getx(self): # метод для получения значения
return self._x
def setx(self, value): # метод для присваивания нового значения
self._x = value
def delx(self): # метод для удаления атрибута
del self._x
x = property(getx, setx, delx, "Свойство x") # определяем x как свойство
a = A(5)
print a.x # Синтаксис доступа к атрибуту при этом прежний
>>> 5
Разумеется, первый способ хорош только если значение атрибута является атомарной операцией по изменению состояния объекта. Если же это не так, то второй способ позволит выполнить все необходимые действия в соответствующих методах.
Существуют два способа централизованно контролировать доступ к атрибутам. Первый основан на перегрузке методов __getattr__()
, __setattr__()
, __delattr__()
, а второй — метода __getattribute__()
. Второй метод помогает управлять чтением уже существующих атрибутов.
Эти способы позволяют организовать полностью динамический доступ к атрибутам объекта или, что используется очень часто, имитации несуществующих атрибутов. По такому принципу функционируют, например, все системы RPC для Python, имитируя методы и свойства, реально существующие на удаленном сервере.
Полиморфизм
[править]В компилируемых языках программирования полиморфизм достигается за счёт создания виртуальных методов, которые в отличие от невиртуальных можно перегрузить в потомке. В Python все методы являются виртуальными, что является естественным следствием разрешения доступа на этапе исполнения. (Следует отметить, что создание невиртуальных методов в компилируемых языках связано с меньшими накладными расходами на их поддержку и вызов).
>>> class Parent(object):
def isParOrPChild(self) : return True
def who(self) : return 'parent'
>>> class Child(Parent):
def who(self): return 'child'
>>> x = Parent()
>>> x.who(), x.isParOrPChild()
('parent', True)
>>> x = Child()
>>> x.who(), x.isParOrPChild()
('child', True)
Явно указав имя класса, можно обратиться к методу родителя (как впрочем и любого другого объекта).
>>> class Child(Parent):
def __init__(self):
Parent.__init__(self)
В общем случае для получения класса-предка применяется функция super
.
class Child(Parent):
def __init__(self):
super(Child, self).__init__()
Используя специально предусмотренное исключение NotImplementedError
, можно имитировать чисто виртуальные методы:
>>> class abstobj(object):
def abstmeth(self):
raise NotImplementedError('Method abstobj.abstmeth is pure virtual')
>>> abstobj().abstmeth()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in method
NotImplementedError: Method abstobj.abstmeth is pure virtual
Или, с использованием декоратора, так:
>>> def abstract(func):
def closure(*dt, **mp):
raise NotImplementedError("Method %s is pure virtual" % func.__name__)
return closure
>>> class abstobj(object):
@abstract
def abstmeth(self): pass
>>> abstobj().abstmeth()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in method
NotImplementedError: Method abstobj.abstmeth is pure virtual
Изменяя атрибут __class__
, можно перемещать объект вверх или вниз
по иерархии наследования (впрочем, как и к любому другому типу)
>>> c = child()
>>> c.val = 10
>>> c.who()
'child'
>>> c.__class__ = Parent
>>> c.who()
'parent'
>>> c.val
10
Однако, в этом случае никакие преобразования типов не делаются, поэтому забота о согласованности данных
всецело лежит на программисте. Кроме того, присваивание атрибуту __class__
не должно применяться по поводу и без. Прежде чем решиться на
его использование, необходимо рассмотреть менее радикальные варианты реализации изменения объекта, то есть по сути шаблона проектирования State.
Более того, полиморфизм в Python вообще не связан с наследованием, поэтому его можно считать сигнатурно-ориентированным полиморфизмом (signature-oriented polymorphism)[2]. Например, чтобы экземпляру класса «прикинуться» файловым объектом, ему достаточно реализовать методы, относящиеся к файлам (обычно .read()
, .readlines()
, .close()
и т. п.).
Переопределение встроенных типов
[править]Встроенные типы и их методы имеют синтаксическую поддержку в языке Python или другие особые «привилегии». Конечно, любая операция может быть представлена синтаксисом вызова функции, однако, для частого применения это неудобно.
Воспользоваться точно такой же синтаксической поддержкой может и любой определённый пользователем класс. Для этого нужно лишь реализовать методы со специальными именами. Самый простой пример — переопределить функцию:
>>> class Add:
... def __call__(self, x, y): # переопределение метода,
... return x + y # который отвечает за операцию вызова функции
...
>>> add = Add()
>>> add(3, 4) # это эквивалентно add.__call__(3, 4)
7
Аналогично поддаются переопределению все операции встроенных типов.
Ещё один пример связан с вычислением длины объекта с помощью функции len()
. Эта встроенная функция вызывает специальный метод:
>>> class wrongList(list): # определяем собственный класс для списка
... def __len__(self): # который всегда считает, что имеет нулевую длину
... return 0
...
>>> w = wrongList([1,2,3])
>>> len(w) # это эквивалентно w.__len__()
0
Методы __getitem__,__setitem__,__delitem__,__contains__
позволяют создать интерфейс для словаря или списка(dict
).
Достаточно просто переопределить и числовые типы. Скажем, следующий класс использует инфиксную операцию *
:
class Multiplyable:
def __init__(self, value):
self.value = value
def __mul__(self, y):
return self.value * y
def __rmul__(self, x):
return x * self.value
def __imul__(self, y):
return Multiplyable(self.value * y)
def __str__(self):
return "Multiplyable(%s)" % self.value
>>> m = Multiplyable(1)
>>> print m
Multiplyable(1)
>>> m *= 3
>>> print m
Multiplyable(3)
Последний из методов — .__str__()
— отвечает за представление экземпляра класса при печати оператором print
и в других подобных случаях.
Аналогичные методы имеются и у соответствующих встроенных типов:
>>> int.__add__
<slot wrapper '__add__' of 'int' objects>
>>> [].__getitem__
<built-in method __getitem__ of list object at 0x00DA3D28>
>>> class a(object):pass
>>> a.__call__
<method-wrapper '__call__' of type object at 0x00DDC318>
Не все из них существуют на самом деле: большая часть имитируется интерпретатором Python для удобства программиста. Такое поведение позволяет экономить время при наиболее важных операциях (например, сложение целых не приводит к поиску и вызову метода __add__
у класса int
) и память не расходуется на этот поиск и вызов, но приводит к невозможности изменения методов у встроенных классов.
Отношения между классами
[править]Наследование и множественное наследование
[править]При описании предметной области классы могут образовывать иерархию, в корне которой стоит базовый класс, а нижележащие классы (подклассы) наследуют свои атрибуты и методы, уточняя и расширяя поведение вышележащего класса (надкласса). Обычно принципом построения классификации является отношение «IS-A» («есть» — между экземпляром и классом) и «AKO» («a kind of» — «разновидность» — между классом и суперклассом)[3].
Python поддерживает как одиночное наследование, так и множественное, позволяющее классу быть производным от любого количества базовых классов.
>>> class Par1(object): # наследуем один базовый класс - object
def name1(self): return 'Par1'
>>> class Par2(object):
def name2(self): return 'Par2'
>>> class Child(Par1, Par2): # создадим класс, наследующий Par1, Par2 (и, опосредованно, object)
pass
>>> x = Child()
>>> x.name1(), x.name2() # экземпляру Child доступны методы из Par1 и Par2
'Par1','Par2'
В Python (из-за «утиной типизации») отсутствие наследования ещё не означает, что объект не может предоставлять тот же самый интерфейс.
Множественное наследование в Python применяется в основном для добавления примесей (mixins) — специальных классов, вносящих некоторую черту поведения или набор свойств[4].
Порядок разрешения доступа к методам и полям
[править]За достаточно простым в использовании механизмом доступа к атрибутам в w:Python кроется довольно сложный алгоритм. Далее будет приведена последовательность действий, производимых интерпретатором при разрешении запроса object.field
(поиск прекращается после первого успешно завершённого шага, иначе происходит переход к следующему шагу).
- Если у
object
есть метод__getattribute__
, то он будет вызван с параметром'field'
(либо__setattr__
или__delattr__
в зависимости от действия над атрибутом) - Если у
object
есть поле__dict__
, то ищетсяobject.__dict__['field']
- Если у
object.__class__
есть поле__slots__
, то'field'
ищется вobject.__class__.__slots__
- Проверяется
object.__class__.__dict__['fields']
- Производится рекурсивный поиск по
__dict__
всех родительских классов (при множественном наследовании поиск производится в режиме deep-first, в том порядке как базовые классы перечислены в определении класса-потомка). Алгоритм поиска разный для «классических» и «новых» классов. - Если у
object
есть метод__getattr__
, то вызывается он с параметром'field'
- Вызывается исключение
AttributeError
.
Если поиск окончен успешно, то проверяется, является ли атрибут классом «нового стиля».
Если является, то проверяется наличие у него метода __get__
(либо __set__
или __delete__
, в зависимости
от действия над атрибутом), если метод найден, то происходит следующий вызов
object.field.__get__(object)
и возвращается его результат
(такие атрибуты называется в Python атрибутами со связанным поведением (binded behavior)
и используются, например, для создания свойств[5]).
Эта последовательность распространяется только на пользовательские атрибуты.
Системные атрибуты, такие как __dict__
, __len__
, __add__
и другие,
имеющие специальные поля в С-структуре описания класса находятся сразу.
«Новые» и «классические» классы
[править]В версиях до 2.2 некоторые объектно-ориентированные возможности Python были заметно ограничены. Например, было невозможно наследовать встроенные классы и классы из модулей расширения. Свойства (property) не выделялись явно. Начиная с версии 2.2, объектная система Python была существенно
переработана и дополнена. Однако для совместимости со старыми версиями
Python было решено сделать две объектные модели: «классические» типы (полностью совместимые
со старым кодом) и «новые»[6]. В версии Python3 поддержка «старых» классов будет удалена.
Для построения «нового» класса достаточно унаследовать его от другого
«нового». Если нужно создать «чистый» класс, то можно унаследоваться от object
— родительского типа для всех «новых» классов.
class OldStyleClass: pass # класс "старого" типа
class NewStyleClass(object): pass # и "нового"
Все стандартные классы — классы «нового» типа.[7]
Агрегация. Контейнеры. Итераторы
[править]Агрегация, когда один объект входит в состав другого, или отношение «HAS-A» («имеет»), реализуется в Python с помощью ссылок. Python имеет несколько встроенных типов контейнеров: список, словарь, множество. Можно определить собственные классы контейнеров со своей логикой доступа к хранимым объектам. (Следует заметить, что в Python агрегацию можно считать разновидностью ассоциации, так реально объекты не вложены друг в друга в памяти и, более того, время жизни элемента может не зависеть от времени жизни контейнера.)
Следующий класс из модуля utils.py среды web.py является примером контейнера-словаря, дополненного возможностью доступа к значениям при помощи синтаксиса доступа к атрибутам:
class Storage(dict):
def __getattr__(self, key):
try:
return self[key]
except KeyError, k:
raise AttributeError, k
def __setattr__(self, key, value):
self[key] = value
def __delattr__(self, key):
try:
del self[key]
except KeyError, k:
raise AttributeError, k
def __repr__(self):
return '<Storage ' + dict.__repr__(self) + '>'
Вот как он работает:
>>> v = Storage(a=5)
>>> v.a
5
>>> v['a']
5
>>> v.a = 12
>>> v['a']
12
>>> del v.a
Для доступа к контейнерам очень удобно использовать итераторы:
>>> cont = dict(a=1, b=2, c=3)
>>> for k in cont:
... print k, cont[k]
...
a 1
c 3
b 2
Ассоциация и слабые ссылки
[править]Отношение использования («USE-A») экземпляров одного класса другими является достаточно общим отношением. При использовании один класс обычно зависит от интерфейса другого класса (хотя эта зависимость может быть и взаимной). Если один объект использует другой, он обязательно содержит ссылку на него. Объекты могут ссылаться и друг на друга. В этом случае возникают циклические ссылки. Если ссылающиеся друг на друга объекты удалить, то они уже не могут быть удалены интерпретатором Python с помощью механизма подсчета ссылок. Удалением таких объектов занимается сборщик мусора.
Ассоциацию объектов без присущих ссылкам проблем можно осуществить с помощью слабых ссылок. Слабые ссылки не препятствуют удалению объекта.
Для работы со слабыми ссылками применяется модуль weakref
.
Метаклассы
[править]Обычных возможностей объектно-ориентированного программирования хватает далеко не всегда. В некоторых случаях требуется изменить сам характер системы классов: расширить язык новыми типами классов, изменить стиль взаимодействия между классами и окружением, добавить некоторые дополнительные аспекты, затрагивающие все используемые в приложении классы, и т. п.
При объявлении метакласса за основу можно взять класс type
. Пример:
# описание метакласса
class myobject(type):
# небольшое вмешательство в момент выделения памяти для класса
def __new__(cls, name, bases, dict):
print "NEW", cls.__name__, name, bases, dict
return type.__new__(cls, name, bases, dict)
# небольшое вмешательство в момент инициализации класса
def __init__(cls, name, bases, dict):
print "INIT", cls.__name__, name, bases, dict
return super(myobject, cls).__init__(name, bases, dict)
# порождение класса на основе метакласса (заменяет оператор class)
MyObject = myobject("MyObject", (), {})
# обычное наследование другого класса из только что порожденного
class MySubObject(MyObject):
def __init__(self, param):
print param
# получение экземпляра класса
myobj = MySubObject("parameter")
Разумеется, вместо оператора print
код метакласса может выполнять более полезные функции: регистрировать класс, передавать действия с классами на удаленную систему, использовать классы для других целей (например, как декларации или ограничения) и т. п.
Методы
[править]Метод
[править]Синтаксис описания метода ничем не отличается от описания функции, разве что его положением внутри класса и характерным первым формальным параметром self
, с помощью которого внутри метода можно ссылаться на сам экземпляр класса (название self является соглашением, которого придерживаются программисты на Python):
class MyClass(object):
def mymethod(self, x):
return x == self._x
Статический метод
[править]Статические методы в Python являются синтаксическими аналогами статических функций
в основных языках программирования. Они не получают ни экземпляр (self
),
ни класс (cls
) первым параметром.
Для создания статического метода (только «новые» классы могут иметь статические методы) используется декоратор staticmethod
>>> class D(object):
@staticmethod
def test(x):
return x == 0
...
>>> D.test(1) # доступ к статическому методу можно получать и через класс
False
>>> f = D()
>>> f.test(0) # и через экземпляр класса
True
Статические методы реализованы с помощью свойств (property).
Метод класса
[править]Классовые методы в Python занимают промежуточное положение между
статическими и обычными. В то время как обычные методы получают первым
параметром экземпляр класса, а статические не получают ничего, в классовые
методы передается класс. Возможность создания классовых методов является
одним из следствий того, что в Python классы также являются объектами.
Для создания классового (только «новые» классы могут иметь классовые методы) метода можно использовать
декоратор classmethod
>>> class A(object):
def __init__(self, int_val):
self.val = int_val + 1
@classmethod
def fromString(cls, val): # вместо self принято использовать cls
return cls(int(val))
...
>>> class B(A):pass
...
>>> x = A.fromString("1")
>>> print x.__class__.__name__
A
>>> x = B.fromString("1")
>>> print x.__class__.__name__
B
Классовые методы достаточно часто используются для перегрузки конструктора. Классовые методы, как и статические, реализуются через свойства (property).
Мультиметоды
[править]Примером для иллюстрации сути мультиметода может служить функция add()
из модуля operator
:
>>> import operator as op
>>> print op.add(2, 2), op.add(2.0, 2), op.add(2, 2.0), op.add(2j, 2)
4 4.0 4.0 (2+2j)
В языке Python достаточно легко реализовать и определённые пользователем мультиметоды[8]. Например, эмулировать мультиметоды можно с помощью модуля multimethods.py (из Gnosis Utils) :
from multimethods import Dispatch
class Asteroid(object): pass
class Spaceship(object): pass
def asteroid_with_spaceship(a1, s1): print "A-><-S"
def asteroid_with_asteroid(a1, a2): print "A-><-A"
def spaceship_with_spaceship(s1, s2): print "S-><-S"
collide = Dispatch()
collide.add_rule((Asteroid, Spaceship), asteroid_with_spaceship)
collide.add_rule((Asteroid, Asteroid), asteroid_with_asteroid)
collide.add_rule((Spaceship, Spaceship), spaceship_with_spaceship)
collide.add_rule((Spaceship, Asteroid), lambda x,y: asteroid_with_spaceship(y,x))
a, s1, s2 = Asteroid(), Spaceship(), Spaceship()
collision1 = collide(a, s1)[0]
collision2 = collide(s1, s2)[0]
Устойчивость объектов
[править]Объекты всегда имеют своё представление в памяти компьютера и их время жизни не больше времени работы программы. Однако зачастую необходимо сохранять данные между запусками приложения и/или передавать их на другие компьютеры. Одним из решений этой проблемы является устойчивость объектов (англ. object persistence) которая достигается с помощью хранения представлений объектов (сериализацией) в виде байтовых последовательностей и их последующего восстановления (десериализация).
Модуль pickle
является наиболее простым способом «консервирования» объектов в Python.
Следующий пример показывает, как работает сериализация и десериализация:
# сериализация
>>> import pickle
>>> p = set([1, 2, 3, 5, 8])
>>> pickle.dumps(p)
'c__builtin__\nset\np0\n((lp1\nI8\naI1\naI2\naI3\naI5\natp2\nRp3\n.'
# де-сериализация
>>> import pickle
>>> p = pickle.loads('c__builtin__\nset\np0\n((lp1\nI8\naI1\naI2\naI3\naI5\natp2\nRp3\n.')
>>> print p
set([8, 1, 2, 3, 5])
Получаемая при сериализации строка может быть передана по сети, записана в файл или специальное хранилище объектов, а позже — прочитана. Сериализации поддаются не все объекты. Некоторые объекты (например, классы и функции) представляются своими именами, поэтому для десериализации требуется наличие тех же самых классов. Нужно отметить, что нельзя десериализовать данные из непроверенных
источников с помощью модуля pickle
, так как при этом возможны практически любые
действия на локальной системе. При необходимости обмениваться данными по незащищенным каналам
или с ненадежными источниками можно воспользоваться другими модулями для сериализации.
В основе сериализации объекта стоит представление его состояния. По умолчанию состояние объекта — это все, что записано в его полях. Пользовательские классы могут управлять сериализацией, предоставляя состояние объекта явным образом (методы __getstate__
, __setstate__
и др.).
На стандартном для Python механизме сериализации построена работа модуля shelve
(shelve (англ. глаг.) — ставить на полку; сдавать в архив). Модуль предоставляет функцию
open
. Объект, который она возвращает, работает аналогично словарю, но объекты сериализуются и сохраняются в файле:
>>> import shelve
>>> s = shelve.open("myshelve.bin")
>>> s['abc'] = [1, 2, 3]
>>> s.close()
# .....
>>> s = shelve.open("myshelve.bin")
>>> s['abc']
[1, 2, 3]
Сериализация pickle
— не единственная возможная, и подходит не всегда. Для сериализации, не зависящей от языка программирования, можно использовать, например, XML.
Примечания
[править]- ↑ Introduction to Object-Oriented Programming
- ↑ в списке рассылки comp.lang.python
- ↑ «AKO» и «IS-A»
- ↑ Beazley, 2009
- ↑ How-To Guide for Descriptors by R. Hettinger(недоступная ссылка — история) Проверено 2007-10-06 г. Архивировано из первоисточника 6 октября 2007.
- ↑ New-style Classes
- ↑ Объяснение Гвидо ван Россума об объединении типов и классов
- ↑ Charming Python: Multiple dispatch
Литература
[править]- David M. Beazley Python Essential Reference. — 4th Edition. — Addison-Wesley Professional, 2009. — 717 с. — ISBN 978-0672329784