Збереження об'єкта (збереження даних)


233

Я створив такий об’єкт:

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

Я хотів би зберегти цей об’єкт. Як я можу це зробити?


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тому, що перший пишеться на С та набагато швидше. Існують деякі тонкі відмінності між ними, але в більшості ситуацій вони еквівалентні, а версія С забезпечить значно кращі показники. Перехід на нього не може бути простішим, просто змініть 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)

Основна перевага полягає в тому, що вам не потрібно знати, скільки об'єктів збережено, щоб потім їх знову завантажити (хоча це можливо і без цієї інформації , можливо, потрібен деякий трохи спеціалізований код). Дивіться відповіді на відповідне запитання Збереження та завантаження декількох об’єктів у файл маринування? для детальної інформації про різні способи зробити це. Особисто я , як @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
@Peterstone, є дуже вагома причина для розмежування обов'язків. Таким чином, немає обмежень у використанні даних процесу маринування. Ви можете зберігати його на диску, а також надсилати через мережеве з'єднання.
Харальд Шейріх

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 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коли саме робите щось? Зазвичай це ознака неправильної кількості аргументів при інстанціюванні екземпляра класу. Якщо це не є частиною робочого процесу вищевказаного питання, чи можете ви надіслати його як інше запитання, надіслати його електронною поштою або додати його як проблему на сторінці dillgithub?
Майк Маккернс

3
Для кожного, хто йде далі, ось опубліковане відповідне запитання @MikeLL - з відповіді це, мабуть, не було dillпроблемою.
мартіно

dilЯ все-таки дає мені MemoryError! так само cPickle, pickleі hickle.
Färid Alijani

4

Ви можете використовувати будь-який кеш, щоб зробити роботу за вас. Він враховує всі деталі:

  • Він використовує кріп як бекенд, який розширює 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.

Детальнішу інформацію можна знайти в документації


Як можна використати 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"))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.