Сохранение объекта (сохранение данных)

Я создал такой объект:

company1.name = 'banana' 
company1.value = 40

Я хотел бы сохранить этот объект. Как я могу это сделать?

Ответ 1

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

import pickle

class Company(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

with open('company_data.pkl', 'wb') as output:
    company1 = Company('banana', 40)
    pickle.dump(company1, output, pickle.HIGHEST_PROTOCOL)

    company2 = Company('spam', 42)
    pickle.dump(company2, output, pickle.HIGHEST_PROTOCOL)

del company1
del company2

with open('company_data.pkl', 'rb') as input:
    company1 = pickle.load(input)
    print(company1.name)  # -> banana
    print(company1.value)  # -> 40

    company2 = pickle.load(input)
    print(company2.name) # -> spam
    print(company2.value)  # -> 42

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

def save_object(obj, filename):
    with open(filename, 'wb') as output:  # Overwrites any existing file.
        pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

# sample usage
save_object(company1, 'company1.pkl')

Обновить:

Поскольку это такой популярный ответ, я бы хотел коснуться нескольких немного продвинутых тем использования.

cPickle (или _pickle) против pickle

Почти всегда предпочтительнее использовать модуль cPickle а не pickle потому что первый написан на C и намного быстрее. Между ними есть некоторые тонкие различия, но в большинстве случаев они эквивалентны, и версия C обеспечит значительно более высокую производительность. Переключение на него не может быть проще, просто измените инструкцию import на это:

import cPickle as pickle

В Python 3 cPickle был переименован в _pickle, но делать это больше не нужно, поскольку модуль pickle теперь делает это автоматически - см. Какая разница между pickle и _pickle в python 3? ,

Вкратце вы можете использовать что-то вроде следующего, чтобы гарантировать, что ваш код всегда будет использовать версию C, когда она доступна в Python 2 и 3:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

Форматы потока данных (протоколы)

pickle может читать и записывать файлы в нескольких различных, специфичных для Python форматах, которые называются протоколами, как описано в документации. "Протокол версии 0" является ASCII и, следовательно, "читаемым человеком". Версии> 1 являются двоичными, и максимальная доступная версия зависит от того, какая версия Python используется. Значение по умолчанию также зависит от версии Python. В Python 2 по умолчанию был протокол версии 0, но в Python 3.7 это протокол версии 3. В Python 3.x к модулю был добавлен pickle.DEFAULT_PROTOCOL, но его нет в Python 2.

К счастью, есть сокращение для записи pickle.HIGHEST_PROTOCOL в каждом вызове (при условии, что вы хотите, и вы обычно делаете это), просто используйте буквальное число -1 - аналогично ссылке на последний элемент последовательности через отрицательный индекс. Итак, вместо того, чтобы писать:

pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

Вы можете просто написать:

pickle.dump(obj, output, -1)

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

pickler = pickle.Pickler(output, -1)
pickler.dump(obj1)
pickler.dump(obj2)
   etc...

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

Несколько объектов

Несмотря на то, что файл pickle может содержать любое количество объектов pickle, как показано в приведенных выше примерах, при их неизвестном количестве часто проще хранить их все в каком-либо контейнере переменного размера, таком как list, tuple или dict и запишите их все в файл за один вызов:

tech_companies = [
    Company('Apple', 114.18), Company('Google', 908.60), Company('Microsoft', 69.18)
]
save_object(tech_companies, 'tech_companies.pkl')

и восстановить список и все в нем позже с помощью:

with open('tech_companies.pkl', 'rb') as input:
    tech_companies = pickle.load(input)

Основным преимуществом является то, что вам не нужно знать, сколько экземпляров объектов сохранено, чтобы загрузить их позже (хотя это возможно без этой информации, это требует некоторого немного специализированного кода). См. Ответы на связанный вопрос. Сохранение и загрузка нескольких объектов в файл pickle? для деталей о различных способах сделать это. Лично мне нравится @Lutz Prechelt ответить лучше всего. Здесь это адаптировано к примерам здесь:

class Company:
    def __init__(self, name, value):
        self.name = name
        self.value = value

def pickled_items(filename):
    """ Unpickle a file of pickled data. """
    with open(filename, "rb") as f:
        while True:
            try:
                yield pickle.load(f)
            except EOFError:
                break

print('Companies in pickle file:')
for company in pickled_items('company_data.pkl'):
    print('  name: {}, value: {}'.format(company.name, company.value))

Ответ 2

Я думаю, что это довольно сильное предположение, что объект является class. Что, если это не class? Также существует предположение, что объект не был определен в интерпретаторе. Что, если это было определено в интерпретаторе? Кроме того, что, если атрибуты были добавлены динамически? Когда некоторые объекты python имеют атрибуты, добавленные к их __dict__ после создания, pickle не соблюдает добавление этих атрибутов (т.е. он "забывает", что они были добавлены, потому что pickle сериализуется путем ссылки на определение объекта).

Во всех этих случаях pickle и cPickle могут свести вас на ужас.

Если вы хотите сохранить object (произвольно созданный), где у вас есть атрибуты (добавленные в определении объекта или позже)... лучше всего использовать dill, который может сериализовать почти что угодно в питон.

Начнем с класса...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> with open('company.pkl', 'wb') as f:
...     pickle.dump(company1, f, pickle.HIGHEST_PROTOCOL)
... 
>>> 

Теперь выключите и перезапустите...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> with open('company.pkl', 'rb') as f:
...     company1 = pickle.load(f)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1378, in load
    return Unpickler(file).load()
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 858, in load
dispatch[key](self)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1090, in load_global
    klass = self.find_class(module, name)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1126, in find_class
    klass = getattr(mod, name)
AttributeError: 'module' object has no attribute 'Company'
>>> 

Упс... pickle не может справиться с этим. Попробуйте dill. Мы будем использовать другой тип объекта (a lambda) для хорошей меры.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill       
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> 
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>> 
>>> with open('company_dill.pkl', 'wb') as f:
...     dill.dump(company1, f)
...     dill.dump(company2, f)
... 
>>> 

И теперь прочитайте файл.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('company_dill.pkl', 'rb') as f:
...     company1 = dill.load(f)
...     company2 = dill.load(f)
... 
>>> company1 
<__main__.Company instance at 0x107909128>
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>>    

Это работает. Причина pickle терпит неудачу, а dill не означает, что dill рассматривает __main__ как модуль (по большей части), а также может определять значения классов вместо травления по ссылке (например, pickle делает). Причина dill может разжечь a lambda заключается в том, что она дает ему имя... тогда может произойти маринование магии.

На самом деле, есть более простой способ сохранить все эти объекты, особенно если у вас много объектов, которые вы создали. Просто выгрузите весь сеанс python и вернитесь к нему позже.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> 
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>> 
>>> dill.dump_session('dill.pkl')
>>> 

Теперь выключите компьютер, наслаждайтесь эспрессо или что-то еще, и вернитесь позже...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('dill.pkl')
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>> company2
<function <lambda> at 0x1065f2938>

Единственным серьезным недостатком является то, что dill не является частью стандартной библиотеки python. Поэтому, если вы не можете установить пакет python на свой сервер, вы не сможете его использовать.

Однако, если вы можете установить пакеты python в свою систему, вы можете получить последние dill с помощью git+https://github.com/uqfoundation/[email protected]#egg=dill. И вы можете получить последнюю версию с pip install dill.

Ответ 3

Вы можете использовать anycache, чтобы выполнить эту работу за вас. Он рассматривает все детали:

  • Он использует dill как бэкэнд, который расширяет модуль python pickle для обработки lambda и всех приятных python.
  • Он хранит разные объекты в разных файлах и перезагружает их правильно.
  • Ограничивает размер кеша
  • Разрешает очистку кеша
  • Разрешает совместное использование объектов между несколькими запусками
  • Позволяет учитывать входные файлы, которые влияют на результат.

Предполагая, что у вас есть функция myfunc, которая создает экземпляр:

from anycache import anycache

class Company(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

@anycache(cachedir='/path/to/your/cache')    
def myfunc(name, value)
    return Company(name, value)

Anycache вызывает myfunc в первый раз и рассотает результат на файл в cachedir с использованием уникального идентификатора (в зависимости от имени функции и его аргументов) в качестве имени файла. При любом последовательном прогоне загружается маринованный объект. Если cachedir сохраняется между прогонами python, маринованный объект берется из предыдущего запуска python.

Более подробную информацию см. в документации