Объектно-ориентированное программирование на Python

Материал из Циклопедии
Перейти к навигации Перейти к поиску

Объектно-ориентированное программирование на Python — программирование на Python с использованием парадигмы ООП: с самого начала Python проектировался как объектно-ориентированный язык программирования[1].

Введение[править]

Принципы ООП[править]

Согласно Алану Кэю — автору языка программирования Smalltalk — объектно-ориентированным может называться язык, построенный с учетом следующих принципов[2]:

  • Все данные представляются объектами
  • Программа является набором взаимодействующих объектов, посылающих друг другу сообщения
  • Каждый объект имеет собственную часть памяти и может иметь в составе другие объекты
  • Каждый объект имеет тип
  • Объекты одного типа могут принимать одни и те же сообщения (и выполнять одни и те же действия)

Объекты, типы и классы[править]

Определение класса[править]

Для определения класса используется оператор class: <source lang="python">

   class имя_класса(надкласс1, надкласс2, ...):
       # определения атрибутов и методов класса

</source> У класса могут быть базовые (родительские) классы (надклассы), которые, если они есть, указываются в скобках после имени определяемого класса.

Минимально возможное определение класса выглядит так: <source lang="python">

   class A:
       pass

</source> В терминологии Python члены класса называются атрибутами, функции класса — методами, а поля класса — свойствами (или просто атрибутами).

Определения методов аналогичны определениям функций, но (за некоторыми исключениями, о которых ниже) методы всегда имеют первый аргумент, называемый по общепринятому соглашению self: <source lang="python">

   class A:
       def m1(self, x):
           # блок кода метода

</source> Определения атрибутов — обычные операторы присваивания, которые связывают некоторые значения с именами атрибутов. <source lang="python">

   class A:
       attr1 = 2 * 2

</source> В языке Python класс не является чем-то статическим, поэтому добавить атрибуты можно и после определения: <source lang="python">

   class A:
       pass
   def my_method(self, x):
       return x * x
   A.m1 = myMethod
   A.attr1 = 2 * 2

</source>

Создание экземпляра[править]

Для создания объекта — экземпляра класса (то есть, инстанцирования класса), достаточно вызвать класс по имени и задать параметры конструктора: <source lang="python">

   class Point:
        def __init__(self, x, y, z):
            self.coord = (x, y, z)
        def __repr__(self):
            return "Point(%s, %s, %s)" % self.coord

</source>

<source lang="python"> >>> p = Point(0.0, 1.0, 0.0) >>> p Point(0.0, 1.0, 0.0) </source>

Переопределив классовый метод __new__, можно управлять процессом создания экземпляра. Этот метод вызывается до метода __init__ и должен вернуть новый экземпляр либо None (в последнем случае будет вызван __new__ родительского класса). Метод __new__ используется для управления созданием неизменчивых (immutable) объектов, управления созданием объектов в случаях, когда __init__ не вызывается, например, при десериализации (unpickle). Следующий код демонстрирует один из вариантов реализации шаблона Одиночка: <source lang="python"> >>> 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 </source>

Инициализатор, конструктор и деструктор[править]

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

Следующий класс имеет конструктор, инициализатор и деструктор: <source lang="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

</source>

<source lang="python"> >>> l = Line((0.0, 1.0), (0.0, 2.0)) >>> del l Удаляется линия (0.0, 1.0) - (0.0, 2.0) >>> </source>

В момент вызова деструктора (например, по завершении программы) среда исполнения может быть уже достаточно «истощённой»[что?], поэтому в деструкторе следует делать только самое необходимое. Кроме того, не обработанные в деструкторе исключения игнорируются.

Время жизни объекта[править]

Обычно время жизни объекта, определённого в программе на Python, не выходит за рамки времени выполнения процесса этой программы.

Для преодоления этого ограничения объект можно сохранить, а после — восстановить. Как правило, при записи объекта производится его сериализация, а при чтении — десериализация.

<source lang="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'] </source>

Инкапсуляция и доступ к свойствам[править]

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

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

Есть существенное отличие между такими атрибутами и личными (private) членами класса в таких языках как C++ или Java: атрибут остается доступным, но под именем вида _ИмяКласса__ИмяАтрибута, а при каждом обращении Python будет модифицировать имя в зависимости от того, через экземпляр какого класса происходит обращение к атрибуту. Таким образом, родительский и дочерний классы могут иметь атрибут с именем, например, «__f», но не будут мешать друг другу. <source lang="python"> >>> 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} # на самом деле у объекта "с" два разных атрибута </source>

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

Доступ к атрибуту может быть как прямой: <source lang="python"> class A(object):

   def __init__(self, x):          # атрибут получает значение в конструкторе
       self.x = x

a = A(5) print a.x a.x = 5 </source>

Так и с использованием свойств с заданными методами для получения, установки и удаления атрибута: <source lang="python"> 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 # Синтаксис доступа к атрибуту при этом прежний a.x = 5 </source>

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

Существуют два способа централизованно контролировать доступ к атрибутам. Первый основан на перегрузке методов __getattr__(), __setattr__(), __delattr__(), а второй — метода __getattribute__() . Второй метод помогает управлять чтением уже существующих атрибутов.

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

Полиморфизм[править]

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

<source lang="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) </source>

Явно указав имя класса, можно обратиться к методу родителя (как впрочем и любого другого объекта). <source lang="python"> >>> class Child(Parent):

       def __init__(self):
           Parent.__init__(self)

</source>

В общем случае для получения класса-предка применяется функция super.

<source lang="python"> class Child(Parent):

   def __init__(self):
       super(Child, self).__init__()

</source>

Используя специально предусмотренное исключение NotImplementedError, можно имитировать чисто виртуальные методы:

<source lang="python"> >>> 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

</source>

Или, с использованием декоратора, так:

<source lang="python"> >>> 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

</source>

Изменяя атрибут __class__, можно перемещать объект вверх или вниз по иерархии наследования (впрочем, как и к любому другому типу)

<source lang="python"> >>> c = child() >>> c.val = 10 >>> c.who() 'child' >>> c.__class__ = Parent >>> c.who() 'parent' >>> c.val 10 </source>

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

Более того, полиморфизм в Python вообще не связан с наследованием, поэтому его можно считать сигнатурно-ориентированным полиморфизмом (signature-oriented polymorphism)[3]. Например, чтобы экземпляру класса «прикинуться» файловым объектом, ему достаточно реализовать методы, относящиеся к файлам (обычно .read(), .readlines(), .close() и т. п.).

Имитация встроенных типов[править]

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

Воспользоваться точно такой же синтаксической поддержкой может и любой определённый пользователем класс. Для этого нужно лишь реализовать методы со специальными именами. Самый простой пример — имитировать функцию: <source lang="python"> >>> class Add: ... def __call__(self, x, y): # определение метода, ... return x + y # который отвечает за операцию вызова функции ... >>> add = Add() >>> add(3, 4) # это эквивалентно add.__call__(3, 4) 7 </source>

Аналогично поддаются имитации все операции встроенных типов. Ещё один пример связан с вычислением длины объекта с помощью функции len(). Оказывается, эта встроенная функция вызывает специальный метод: <source lang="python"> >>> class wrongList(list): # определяем собственный класс для списка ... def __len__(self): # который всегда считает, что имеет нулевую длину ... return 0 ... >>> w = wrongList([1,2,3]) >>> len(w) # это эквивалентно w.__len__() 0 </source>

Методы __getitem__,__setitem__,__delitem__,__contains__ позволяют создать интерфейс для словаря или списка(dict).

Достаточно просто имитировать и числовые типы. Скажем, следующий класс использует инфиксную операцию *: <source lang="python"> 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) </source>

Последний из методов — .__str__() — отвечает за представление экземпляра класса при печати оператором print и в других подобных случаях.

Аналогичные методы имеются и у соответствующих встроенных типов: <source lang="python"> >>> 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> </source>

Не все из них существуют на самом деле: большая часть имитируется интерпретатором Python для удобства программиста. Такое поведение позволяет экономить время при наиболее важных операциях (например, сложение целых не приводит к поиску и вызову метода __add__ у класса int) и память, но приводит к невозможности изменения методов у встроенных классов.

Отношения между классами[править]

Наследование и множественное наследование[править]

При описании предметной области классы могут образовывать иерархию, в корне которой стоит базовый класс, а нижележащие классы (подклассы) наследуют свои атрибуты, уточняя и расширяя поведение вышележащего класса (надкласса). Обычно принципом построения классификации является отношение «IS-A» («есть» — между экземпляром и классом) и «AKO» («a kind of» — «разновидность» — между классом и суперклассом)[4].

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

<source lang="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' </source>

В Python (из-за «утиной типизации») отсутствие наследования ещё не означает, что объект не может предоставлять тот же самый интерфейс.

Множественное наследование в Python применяется в основном для добавления примесей (mixins) — специальных классов, вносящих некоторую черту поведения или набор свойств[5].

Порядок разрешения доступа к методам и полям[править]

За достаточно простым в использовании механизмом доступа к атрибутам в Python кроется довольно сложный алгоритм. Далее будет приведена последовательность действий, производимых интерпретатором при разрешении запроса object.field (поиск прекращается после первого успешно завершённого шага, иначе происходит переход к следующему шагу).

  1. Если у object есть метод __getattribute__, то будет вызван он с параметром 'field' (либо __setattr__ или __delattr__ в зависимости от действия над атрибутом)
  2. Если у object есть поле __dict__, то ищется object.__dict__['field']
  3. Если у object.__class__ есть поле __slots__, то 'field' ищется в object.__class__.__slots__
  4. Проверяется object.__class__.__dict__['fields']
  5. Производится рекурсивный поиск по __dict__ всех родительских классов (при множественном наследовании поиск производится в режиме deep-first, в том порядке как базовые классы перечислены в определении класса-потомка). Алгоритм поиска разный для «классических» и «новых» классов.
  6. Если у object есть метод __getattr__, то вызывается он с параметром 'field'
  7. Возбуждается исключение AttributeError .

Если поиск окончен успешно, то проверяется, является ли атрибут классом «нового стиля». Если является, то проверяется наличие у него метода __get__ (либо __set__ или __delete__, в зависимости от действия над атрибутом), если метод найден, то происходит следующий вызов object.field.__get__(object) и возвращается его результат (такие атрибуты называется в Python атрибутами со связанным поведением (binded behavior) и используются, например, для создания свойств[6]).

Эта последовательность распространяется только на пользовательские атрибуты. Системные атрибуты, такие как __dict__, __len__, __add__ и другие, имеющие специальные поля в С-структуре описания класса находятся сразу.

«Новые» и «классические» классы[править]

В версиях до 2.2 некоторые объектно-ориентированные возможности Python были заметно ограничены. Например, было невозможно наследовать встроенные классы и классы из модулей расширения. Свойства (property) не выделялись явно. Начиная с версии 2.2, объектная система Python была существенно переработана и дополнена. Однако для совместимости со старыми версиями Python было решено сделать две объектные модели: «классические» типы (полностью совместимые со старым кодом) и «новые»[7]. В версии Python3000 поддержка «старых» классов будет удалена.
Для построения «нового» класса достаточно унаследовать его от другого «нового». Если нужно создать «чистый» класс, то можно унаследоваться от object — родительского типа для всех «новых» классов. <source lang="python"> class OldStyleClass: pass # класс "старого" типа class NewStyleClass(object): pass # и "нового" </source> Все стандартные классы — классы «нового» типа.[8]

Агрегация. Контейнеры. Итераторы[править]

Агрегация, когда один объект входит в состав другого, или отношение «HAS-A» («имеет»), реализуется в Python с помощью ссылок. Python имеет несколько встроенных типов контейнеров: список, словарь, множество. Можно определить собственные классы контейнеров со своей логикой доступа к хранимым объектам. (Следует заметить, что в Python агрегацию можно считать разновидностью ассоциации, так реально объекты не вложены друг в друга в памяти и, более того, время жизни элемента может не зависеть от времени жизни контейнера.)

Следующий класс из модуля utils.py среды web.py является примером контейнера-словаря, дополненного возможностью доступа к значениям при помощи синтаксиса доступа к атрибутам: <source lang="python">

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) + '>'

</source>

Вот как он работает:

<source lang="python"> >>> v = Storage(a=5) >>> v.a 5 >>> v['a'] 5 >>> v.a = 12 >>> v['a'] 12 >>> del v.a </source>

Для доступа к контейнерам очень удобно использовать итераторы:

<source lang="python"> >>> cont = dict(a=1, b=2, c=3) >>> for k in cont: ... print k, cont[k] ... a 1 c 3 b 2 </source>

Ассоциация и слабые ссылки[править]

Отношение использования («USE-A») экземпляров одного класса другими является достаточно общим отношением. При использовании один класс обычно зависит от интерфейса другого класса (хотя эта зависимость может быть и взаимной). Если один объект использует другой, он обязательно содержит ссылку на него. Объекты могут ссылаться и друг на друга. В этом случае возникают циклические ссылки. Если ссылающиеся друг на друга объекты удалить, то они уже не могут быть удалены интерпретатором Python с помощью механизма подсчета ссылок. Удалением таких объектов занимается сборщик мусора.

Ассоциацию объектов без присущих ссылкам проблем можно осуществить с помощью слабых ссылок. Слабые ссылки не препятствуют удалению объекта.

Для работы со слабыми ссылками применяется модуль weakref.

Метаклассы[править]

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

При объявлении метакласса за основу можно взять класс type. Пример: <source lang="python">

  1. описание метакласса

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)
  1. порождение класса на основе метакласса (заменяет оператор class)

MyObject = myobject("MyObject", (), {})

  1. обычное наследование другого класса из только что порожденного

class MySubObject(MyObject):

   def __init__(self, param):
       print param
  1. получение экземпляра класса

myobj = MySubObject("parameter") </source> Разумеется, вместо оператора print код метакласса может выполнять более полезные функции: регистрировать класс, передавать действия с классами на удаленную систему, использовать классы для других целей (например, как декларации или ограничения) и т. п.

Методы[править]

Метод[править]

Синтаксис описания метода ничем не отличается от описания функции, разве что его положением внутри класса и характерным первым формальным параметром self, с помощью которого внутри метода можно ссылаться на сам экземпляр класса (название self является соглашением, которого придерживаются программисты на Python): <source lang="python"> class MyClass(object):

   def mymethod(self, x):
       return x == self._x

</source>

Статический метод[править]

Статические методы в Python являются синтаксическими аналогами статических функций в основных языках программирования. Они не получают ни экземпляр (self), ни класс (cls) первым параметром. Для создания статического метода (только «новые» классы могут иметь статические методы) используется декоратор staticmethod

<source lang="python"> >>> class D(object):

      @staticmethod
      def test(x):
          return x == 0

... >>> D.test(1) # доступ к статическому методу можно получать и через класс False >>> f = D() >>> f.test(0) # и через экземпляр класса True </source>

Статические методы реализованы с помощью свойств (property).

Метод класса[править]

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

<source lang="python"> >>> 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 </source>

Классовые методы достаточно часто используются для перегрузки конструктора. Классовые методы, как и статические, реализуются через свойства (property).

Мультиметоды[править]

Примером для иллюстрации сути мультиметода может служить функция add() из модуля operator:

<source lang="python"> >>> 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) </source>

В языке Python достаточно легко реализовать и определённые пользователем мультиметоды[9]. Например, эмулировать мультиметоды можно с помощью модуля multimethods.py (из Gnosis Utils) : <source lang="python"> 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]

</source>

Устойчивость объектов[править]

Объекты всегда имеют своё представление в памяти компьютера и их время жизни не больше времени работы программы. Однако зачастую необходимо сохранять данные между запусками приложения и/или передавать их на другие компьютеры. Одним из решений этой проблемы является устойчивость объектов (англ. object persistence) которая достигается с помощью хранения представлений объектов (сериализацией) в виде байтовых последовательностей и их последующего восстановления (десериализация).

Модуль pickle является наиболее простым способом «консервирования» объектов в Python.

Следующий пример показывает, как работает сериализация и десериализация:

<source lang="python">

  1. сериализация

>>> 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.'

  1. де-сериализация

>>> 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]) </source>

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

В основе сериализации объекта стоит представление его состояния. По умолчанию состояние объекта — это все, что записано в его полях. Пользовательские классы могут управлять сериализацией, предоставляя состояние объекта явным образом (методы __getstate__, __setstate__ и др.).

На стандартном для Python механизме сериализации построена работа модуля shelve (shelve (англ. глаг.) — ставить на полку; сдавать в архив). Модуль предоставляет функцию open. Объект, который она возвращает, работает аналогично словарю, но объекты сериализуются и сохраняются в файле:

<source lang="python"> >>> import shelve >>> s = shelve.open("myshelve.bin") >>> s['abc'] = [1, 2, 3] >>> s.close()

  1. .....

>>> s = shelve.open("myshelve.bin") >>> s['abc'] [1, 2, 3] </source>

Сериализация pickle — не единственная возможная, и подходит не всегда. Для сериализации, не зависящей от языка программирования, можно использовать, например, XML.

Примечания[править]

Литература[править]

  • David M. Beazley Python Essential Reference. — 4th Edition. — Addison-Wesley Professional, 2009. — 717 с. — ISBN 978-0672329784.

Ссылки[править]