Шаг 99.
Python: сборник рецептов.
Файлы и ввод-вывод. Сериализация объектов Python

    На этом шаге мы поговорим о модуле pickle.

Задача

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

Решение

    Наиболее распространенный подход к сериализации данных - это использование модуля pickle. Чтобы сохранить объект в файл, сделайте так:

import pickle

data = ... # Какой-то объект Python 
f = open('somefile', 'wb') 
pickle.dump(data, f)

    Чтобы сохранить объект в строку, используйте pickle.dumps():

s = pickle.dumps(data)

    Чтобы воссоздать объект из потока байтов (byte stream), используйте либо pickle.load(), либо pickle.loads(). Например:

#  Восстановление из файла 
f = open('somefile', 'rb') 
data = pickle.load(f)

#  Восстановление из строки 
data = pickle.loads(s)


Обсуждение

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

>>> import pickle
>>> f = open('somedata', 'wb')
>>> pickle.dump([1, 2, 3, 4], f)
>>> pickle.dump('hello', f)
>>> pickle.dump({'Apple', 'Pear', 'Banana'}, f)
>>> f.close()
>>> f = open('somedata', 'rb')
>>> pickle.load(f)
[1, 2, 3, 4]
>>> pickle.load(f)
'hello'
>>> pickle.load(f)
{'Pear', 'Banana', 'Apple'}
>>> 

    Вы можете сериализировать функции, классы и экземпляры, однако получающиеся данные кодируют только имена ссылок на связанные объекты кода. Например:

>>> import math
>>> import pickle
>>> pickle.dumps(math.cos)
b'\x80\x04\x95\x10\x00\x00\x00\x00\x00\x00\x00\x8c\x04math\x94\x8c\x03cos\x94\x93\x94.'
>>>

    Когда данные десериализуются, то предполагается, что требуемый источник доступен. Модули, классы и функции будут автоматически импортированы при необходимости. Для приложений, где данные Python разделяются между интерпретаторами или разными компьютерами, это потенциально может оказаться проблемой, поскольку все машины должны иметь доступ к одному и тому же исходному коду. Функцию pickle.load() никогда нельзя использовать на данных из непроверенных источников. В качестве побочного эффекта загрузки pickle автоматически загрузит модули и создаст экземпляры. Однако злоумышленник, который знает принцип работы pickle, может создать специальные данные, которые заставят Python выполнить произвольные системные команды. Так что pickle можно использовать только внутренними данными и интерпретаторами, которые могут каким-то образом проводить аутентификацию друг друга.

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

# countdown.py
import time
import threading


class Countdown:
    def __init__(self, n):
        self.n = n
        self.thr = threading.Thread(target=self.run)
        self.thr.daemon = True
        self.thr.start()

    def run(self):
        while self.n > 0:
            print('T-minus', self.n)
            self.n -= 1
            time.sleep(5)

    def __getstate__(self):
        return self.n

    def __setstate__(self, n):
        self.__init__(n)
Архив с файлом можно взять здесь.

   

    Попробуйте применить pickle:

>>> import countdown
>>> c = countdown.Countdown(30)
T-minus 30
T-minus 29
T-minus 28
T-minus 27
T-minus 26
T-minus 25
.   .   .

>>> #  Выполнить через некоторое время
>>> #  во время вывода значений
>>> f = open('cstate.p', 'wb')
>>> import pickle
>>> pickle.dump(c, f)
>>> f.close()
>>> 

    Теперь выйдите из Python и после перезапуска попробуйте вот это:

>>> import pickle
>>> f = open('cstate.p', 'rb')
>>> pickle.load(f)
<countdown.Countdown object at 0x000001F18CD1D160> 
T-minus 17
T-minus 16
T-minus 15
T-minus 14
T-minus 13
T-minus 12
.   .   .

    Вы должны увидеть, как поток волшебным образом возрождается к жизни, поднимаясь на том же месте, где он был, когда вы его сериализовали.

    Модуль pickle не особенно эффективен для сериализации крупных структур данных, таких как бинарные массивы, созданные библиотеками типа numpy или модуля array. Если вы перемещаете большие объемы данных в массивах туда-сюда, вам лучше просто сохранять массивы в файлы или использовать более стандартизованную кодировку, такую как HDF5 (поддерживается не входящими в поставку Python библиотеками).

    Модуль pickle по своей природе привязан к Python и исходному коду, поэтому вам не стоит использовать его для долговременного хранения данных. Например, если исходный код изменится, все ваши сохраненные данные могут "сломаться" и стать нечитаемыми. Если честно, для хранения данных в базах данных и архивных хранилищах вам лучше использовать более стандартные кодировки, такие как XML, CSV или JSON. Они поддерживаются большим количеством языков программирования и более адаптируемы к изменениям в вашем исходном коде.

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


https://docs.python.org/3/library/pickle.html.

    Со следующего шага мы начнем рассматривать кодирование и обработку данных.




Предыдущий шаг Содержание Следующий шаг