Zapisywanie obiektu (utrwalanie danych)

233

Stworzyłem taki obiekt:

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

Chciałbym zapisać ten obiekt. Jak mogę to zrobić?

Peterstone
źródło
1
Zobacz przykład dla osób, które tu przyjeżdżają, na prosty przykład użycia marynaty.
Martin Thoma,
@MartinThoma: Dlaczego (pozornie) wolisz tę odpowiedź od zaakceptowanej ( pytania powiązanego )?
martineau,
W momencie, gdy łączyłem, akceptowana odpowiedź nie miała protocol=pickle.HIGHEST_PROTOCOL. Moja odpowiedź daje również alternatywy dla marynaty.
Martin Thoma,

Odpowiedzi:

449

Możesz użyć picklemodułu w standardowej bibliotece. Oto podstawowe zastosowanie tego w twoim przykładzie:

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

Możesz także zdefiniować własne proste narzędzie, takie jak następujące, które otwierają plik i zapisują do niego pojedynczy obiekt:

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

Aktualizacja

Ponieważ jest to tak popularna odpowiedź, chciałbym poruszyć kilka nieco zaawansowanych tematów użytkowania.

cPickle(lub _pickle) vspickle

Prawie zawsze lepiej jest używać cPicklemodułu, pickleponieważ ten pierwszy jest napisany w C i jest znacznie szybszy. Istnieją pewne subtelne różnice między nimi, ale w większości sytuacji są one równoważne, a wersja C zapewnia znacznie lepszą wydajność. Przejście na to nie może być łatwiejsze, wystarczy zmienić importinstrukcję na:

import cPickle as pickle

W Pythonie 3 cPicklezmieniono nazwę _pickle, ale robienie tego nie jest już konieczne, ponieważ picklemoduł robi to teraz automatycznie - zobacz Jaka jest różnica między marynatą a _pickle w pythonie 3? .

Podsumowaniem jest to, że możesz użyć czegoś takiego jak poniżej, aby upewnić się, że Twój kod będzie zawsze używał wersji C, gdy jest ona dostępna zarówno w Pythonie 2, jak i 3:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

Formaty strumieni danych (protokoły)

picklepotrafi odczytywać i zapisywać pliki w kilku różnych, specyficznych dla Pythona formatach, zwanych protokołami, jak opisano w dokumentacji , „Protokół w wersji 0” jest ASCII, a zatem „czytelny dla człowieka”. Wersje> 0 są binarne, a najwyższy dostępny zależy od używanej wersji Pythona. Wartość domyślna zależy również od wersji Python. W Pythonie 2 domyślna była wersja protokołu 0, ale w Python 3.8.1 jest to wersja protokołu 4. W Pythonie 3.x moduł został pickle.DEFAULT_PROTOCOLdodany, ale nie istnieje w Pythonie 2.

Na szczęście istnieje skrót do pisania pickle.HIGHEST_PROTOCOLprzy każdym wywołaniu (zakładając, że tego właśnie chcesz i zwykle robisz), po prostu użyj literalnej liczby -1- podobnie do odwołania do ostatniego elementu sekwencji za pomocą indeksu ujemnego. Zamiast pisać:

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

Możesz po prostu napisać:

pickle.dump(obj, output, -1)

Tak czy inaczej, protokół należy określić tylko raz, jeśli utworzono Picklerobiekt do użycia w wielu operacjach trawienia:

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

Uwaga : jeśli pracujesz w środowisku z różnymi wersjami Pythona, prawdopodobnie będziesz chciał jawnie użyć (tj. Kodu stałego) określonego numeru protokołu, który wszystkie mogą odczytać (późniejsze wersje mogą generalnie czytać pliki wcześniejszych wersji) .

Wiele obiektów

Choć plik marynata może zawierać dowolną liczbę marynowanych obiektów, jak pokazano w powyższych próbkach, gdy pojawia się nieznany numer z nich, często łatwiej jest je wszystkie przechowywać w jakiś sposób zmienny rozmiarze pojemniku, jak list, tuplelub dicti zapisu wszystkie do pliku w jednym wywołaniu:

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

i przywróć listę i wszystko na niej później za pomocą:

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

Główną zaletą jest to, że nie trzeba wiedzieć, ile instancji obiektów jest zapisywanych, aby załadować je później (chociaż robienie tego bez tych informacji jest możliwe, wymaga nieco specjalistycznego kodu). Zobacz odpowiedzi na powiązane pytanie Zapisywanie i ładowanie wielu obiektów w pliku pikli? po szczegóły na różne sposoby to zrobić. Osobiście I jak @Lutz Prechelt za odpowiedź najlepszy. Oto jest dostosowany do przykładów tutaj:

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))
martineau
źródło
1
Jest to dla mnie rzadkie, ponieważ wyobrażałem sobie, że będzie łatwiejszy sposób na zapisanie obiektu ... Coś w rodzaju „saveobject (company1, c: \ mypythonobjects)
Peterstone,
4
@Peterstone: Jeśli chciałbyś przechowywać tylko jeden obiekt, potrzebowałbyś tylko o połowę mniej kodu niż w moim przykładzie - celowo napisałem to tak, jak to zrobiłem, aby pokazać, w jaki sposób można zapisać więcej niż jeden obiekt (a później przeczytać ponownie z) ten sam plik.
martineau,
1
@Peterstone, istnieje bardzo dobry powód do rozdzielenia obowiązków. W ten sposób nie ma ograniczeń co do sposobu wykorzystania danych z procesu wytrawiania. Możesz zapisać go na płycie lub wysłać przez połączenie sieciowe.
Harald Scheirich,
3
@martinaeau, było to w odpowiedzi na perstones Uwaga, że ​​jedna powinna mieć tylko jedną funkcję do zapisania obiektu na dysku. Odpowiedzialność pikli polega tylko na przekształceniu obiektu w dane, które mogą być przetwarzane jako porcja. Odpowiedzialność za zapisywanie plików należy do obiektów plików. Utrzymując rzeczy oddzielić jeden umożliwia ponowne wykorzystanie np wyższy jest w stanie przesłać dane marynowane po drugiej stronie połączenia sieciowego lub przechowywanie go w bazie danych, wszystkie obowiązki oddzielić od rzeczywistych danych <-> konwersja obiektu
Harald Scheirich
1
Usuwasz company1i company2. Dlaczego też nie usuwasz Companyi nie pokazujesz, co się dzieje?
Mike McKerns,
49

Myślę, że dość silnym założeniem jest założenie, że obiekt jest class. Co jeśli to nie jest class? Istnieje również założenie, że obiekt nie został zdefiniowany w tłumaczu. Co jeśli został zdefiniowany w tłumaczu? A co, jeśli atrybuty zostały dodane dynamicznie? Kiedy niektóre obiekty Pythona mają dodane atrybuty do swoich __dict__po utworzeniu, picklenie szanuje dodania tych atrybutów (tzn. „Zapomina”, że zostały dodane - ponieważ pickleserializuje przez odniesienie do definicji obiektu).

We wszystkich tych przypadkach picklei cPicklemoże Cię okropnie zawieść.

Jeśli chcesz zapisać object(dowolnie utworzone), w którym masz atrybuty (dodane w definicji obiektu lub później)… najlepiej jest użyć dill, który może serializować prawie wszystko w pythonie.

Zaczynamy od klasy…

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

Teraz zamknij i uruchom ponownie ...

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

Ups… picklenie mogę sobie z tym poradzić. Try Chodźmy dill. Dla dokładności wrzucimy inny typ obiektu (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)
... 
>>> 

A teraz przeczytaj plik.

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

To działa. Powodem jest to, że się picklenie udaje, i dillto dlatego , że dilltraktuje się __main__jak moduł (w przeważającej części), a także może wytrawiać definicje klas zamiast wytrawiania przez odniesienie (podobnie jak picklerobi). Powodem, dla którego dillmożna marynować a, lambdajest to, że nadaje jej nazwę… wtedy może się zdarzyć magia trawienia.

W rzeczywistości istnieje łatwiejszy sposób na zapisanie wszystkich tych obiektów, zwłaszcza jeśli masz wiele utworzonych obiektów. Zrzuć całą sesję Pythona i wróć do niej później.

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

Teraz wyłącz komputer, wypij espresso lub cokolwiek innego i wróć później ...

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>

Jedyną poważną wadą jest to, że dillnie jest częścią standardowej biblioteki Pythona. Jeśli więc nie możesz zainstalować pakietu python na swoim serwerze, nie możesz go użyć.

Jeśli jednak jesteś w stanie zainstalować pakiety Pythona w swoim systemie, możesz uzyskać najnowsze dillz git+https://github.com/uqfoundation/dill.git@master#egg=dill. I możesz uzyskać najnowszą wydaną wersję za pomocą pip install dill.

Mike McKerns
źródło
Dostaję się, TypeError: __new__() takes at least 2 arguments (1 given)gdy próbuję użyć dill(co wygląda obiecująco) z dość złożonym obiektem, który zawiera plik audio.
MikeiLL
1
@MikeiLL: Dostajesz TypeErrormoment, kiedy robisz co dokładnie? Zazwyczaj jest to oznaka niepoprawnej liczby argumentów podczas tworzenia instancji klasy. Jeśli nie jest to część przepływu pracy powyższego pytania, czy możesz opublikować je jako inne pytanie, przesłać je do mnie e-mailem lub dodać jako problem na stronie dillgithub?
Mike McKerns
3
Dla każdego, kto podąża za nim, oto powiązane pytanie @MikeLL - z odpowiedzi najwyraźniej nie było dillproblemu.
martineau,
dilDaje mi MemoryErrorjednak! tak robi cPickle, picklei hickle.
Färid Alijani,
4

Możesz użyć Anycache, aby wykonać zadanie za Ciebie. Uwzględnia wszystkie szczegóły:

  • Używa kopru jako backendu, który rozszerza picklemoduł Pythona do obsługi lambdai wszystkich fajnych funkcji Pythona.
  • Przechowuje różne obiekty w różnych plikach i ładuje je poprawnie.
  • Ogranicza rozmiar pamięci podręcznej
  • Umożliwia czyszczenie pamięci podręcznej
  • Umożliwia współdzielenie obiektów między wieloma przebiegami
  • Pozwala na poszanowanie plików wejściowych, które wpływają na wynik

Zakładając, że masz funkcję, myfuncktóra tworzy instancję:

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 wywołuje myfuncpo raz pierwszy i wybiera wynik do pliku, cachedirużywając unikalnego identyfikatora (w zależności od nazwy funkcji i jej argumentów) jako nazwy pliku. Przy każdym kolejnym uruchomieniu ładowany obiekt jest ładowany. Jeśli cachedirzachowane jest między uruchomieniami Pythona, piklowany obiekt jest pobierany z poprzedniego uruchomienia Pythona.

W celu uzyskania dalszych informacji patrz dokumentacja

c0fec0de
źródło
W jaki sposób anycachemożna zapisać więcej niż jedną instancję, powiedzmy, a classlub kontenera takiego jak list(to nie był wynik wywołania funkcji)?
martineau
2

Szybki przykład company1z wykorzystaniem pytania z 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"))

Jednak, jak zauważono w tej odpowiedzi , marynata często zawodzi. Więc powinieneś naprawdę użyć 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"))
Anthony Ebert
źródło