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

233

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

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

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

Peterstone
источник
1
Смотрите пример для людей, которые приходят сюда для простого примера, как использовать рассол.
Мартин Тома,
@MartinThoma: Почему вы (по-видимому) предпочитаете этот ответ принятому ( связанного вопроса )?
Мартино,
В то время, когда я связывался, принятого ответа не было protocol=pickle.HIGHEST_PROTOCOL. Мой ответ также дает альтернативу мариновать.
Мартин Тома

Ответы:

449

Вы можете использовать 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 и, следовательно, «читаемым человеком». Версии> 0 являются двоичными, и максимальная доступная версия зависит от того, какая версия Python используется. Значение по умолчанию также зависит от версии Python. В Python 2 по умолчанию была версия протокола 0, но в Python 3.8.1 это версия протокола 4. В 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, вам, вероятно, захочется явно использовать (т. Е. Использовать жесткий код) конкретный номер протокола, который все они могут прочитать (более поздние версии обычно могут читать файлы, созданные более ранними версиями) ,

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

В то время как рассол файл может содержать любое количество соленых объектов, как показано в примерах выше, когда есть неизвестное число из них, часто проще хранить их в каком - то контейнер переменно размера, подобно 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))
Мартино
источник
1
Это редкость для меня, потому что я представлял, что есть более простой способ сохранить объект ... Что-то вроде 'saveobject (company1, c: \ mypythonobjects)
Peterstone
4
@Peterstone: Если вы хотите сохранить только один объект, вам понадобится примерно вдвое меньше кода, чем в моем примере - я специально написал это так, чтобы показать, как можно сохранить более одного объекта (и позже прочитать его). из) тот же файл.
Мартино
1
@ Питерстоун, есть очень веская причина для разделения обязанностей. Таким образом, нет никаких ограничений на то, как используются данные процесса травления. Вы можете сохранить его на диске или отправить по сетевому соединению.
Харальд Шейрих
3
@martinaeau, это было в ответ на замечание perstones о том, что для сохранения объекта на диске должна быть только одна функция. Ответственность за соленые огурцы заключается только в том, чтобы превратить объект в данные, которые могут быть обработаны как кусок. Запись вещей в файл является обязанностью файловых объектов. Отделяя вещи, можно обеспечить более высокое повторное использование, например, возможность посылать протравленные данные по сетевому соединению или сохранять их в базе данных, все обязанности отделены от фактической конвертации данных <->
Харальд Шейрих
1
Вы удаляете company1и company2. Почему бы вам не удалить Companyи не показать, что происходит?
Майк Маккернс
49

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

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

Если вы хотите сохранить object(произвольно созданный), где у вас есть атрибуты (добавленные в определении объекта или после) ... лучше всего использовать dill, который может сериализовать практически все в 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 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. Мы добавим другой тип объекта (а 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можно мариновать, 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/dill.git@master#egg=dill. И вы можете получить последнюю версию с pip install dill.

Майк Маккернс
источник
Я получаю TypeError: __new__() takes at least 2 arguments (1 given)при попытке использовать dill(что выглядит многообещающе) с довольно сложным объектом, который включает в себя аудиофайл.
MikeiLL
1
@MikeiLL: Ты получаешь, TypeErrorкогда делаешь что, точно? Обычно это признак неверного количества аргументов при создании экземпляра класса. Если это не является частью рабочего процесса, описанного выше, не могли бы вы опубликовать его как другой вопрос, отправить его мне по электронной почте или добавить как проблему на dillстранице github?
Майк Маккернс
3
Для всех, кто подписался , вот соответствующий вопрос, который разместил @MikeLL - судя по всему, это не dillпроблема.
Мартино
dilЯ MemoryErrorдаю мне все же! так же cPickle, pickleи hickle.
Ферид Алиджани
4

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

  • Он использует укроп в качестве бэкэнда, который расширяет pickleмодуль Python для обработки 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.

Для дальнейших подробностей смотрите документацию

c0fec0de
источник
Как можно использовать anycacheдля сохранения более одного экземпляра, скажем, classконтейнера или, например, такого list(который не был результатом вызова функции)?
Мартино
2

Быстрый пример использования company1из вашего вопроса, с python3.

import pickle

# Save the file
pickle.dump(company1, file = open("company1.pickle", "wb"))

# Reload the file
company1_reloaded = pickle.load(open("company1.pickle", "rb"))

Однако, как отмечается в этом ответе , засолка часто дает сбой. Таким образом, вы должны действительно использовать dill.

import dill

# Save the file
dill.dump(company1, file = open("company1.pickle", "wb"))

# Reload the file
company1_reloaded = dill.load(open("company1.pickle", "rb"))
Энтони Эберт
источник